diff --git a/.claude/rules/c-runtime-memory.md b/.claude/rules/c-runtime-memory.md index b14f9e168..9cfbdc0e3 100644 --- a/.claude/rules/c-runtime-memory.md +++ b/.claude/rules/c-runtime-memory.md @@ -92,7 +92,7 @@ iteration, or a collector that quietly stops working). record, `store_put` refused to re-insert it, and the caller was told 1). - **`eigs_json_encode` borrows its argument and `eigs_json_parse_value` returns - an owned ref.** `eigs_json_encode(make_num(x))` leaks the `make_num`; a parsed + an owned ref.** `eigs_json_encode(make_float(x))` leaks the `make_float`; a parsed Value must be decref'd on *every* path including early returns, and the value read out of it before the decref. Three of the five call sites in `ext_http.c` had this wrong (#731) — if you touch one, re-audit the rest. @@ -148,11 +148,15 @@ iteration, or a collector that quietly stops working). - **Container elements are `EigsSlot` storage (#1665 arm D); read them only through `src/container.h`, and know which getter owns what.** Every rule below was bought by a real bug during the migration: - - `list_get_ref` / `dict_value_get_ref` return **NULL for a number** by - contract. Test `list_elem_type` / `slot_type` before dereferencing, and - never write `get_ref(...)->type == VAL_NUM` (always false). Ragged tensor - rows crashed `relu`/`gather` this way (B5), and the DMG in-place - register-write fast path silently never fired (B2, +38% Ir). + - `list_get_ref` / `dict_value_get_ref` return **NULL for an immediate + number**, a double or an SMI (`src/container.h`:133 and :137). A boxed + int (|i| >= 2^47, since #1758 R1.3) is a heap slot, so it comes back as a + non-NULL `VAL_INT` Value. Test `list_elem_type` / `slot_type` before + dereferencing. Never write `get_ref(...)->type == VAL_FLOAT` (always + false), and never read `get_ref(...)->type == VAL_INT` as "an int" (it is + true for a boxed int only, never for an SMI). Ragged tensor rows crashed + `relu`/`gather` this way (B5), and the DMG in-place register-write fast + path silently never fired (B2, +38% Ir). - A view (`list_get_view`, `slot_view`, `EIGS_VIEW`, `BUILTIN_*_VIEW`) is **stack storage**: never return it, store it, or incref-and-keep it, and a `BUILTIN_*_VIEW` compound literal dies at its enclosing block. A view in diff --git a/.github/workflows/phase2.yml b/.github/workflows/phase2.yml new file mode 100644 index 000000000..760b14991 --- /dev/null +++ b/.github/workflows/phase2.yml @@ -0,0 +1,81 @@ +name: phase2 + +# #1758: landed MAP rows, the pre-registered builtin table on matched +# capability profiles, the C readers and the stdlib differential. The shared +# phase2_check.py helper derives the complete declared MAP prefix. +on: + push: + branches: [phase2-numbers] + workflow_dispatch: + +permissions: + contents: read + +jobs: + phase2: + name: phase2 (MAP + builtins + readers + lib) + runs-on: ubuntu-latest + timeout-minutes: 60 + services: + db: + image: postgres:16 + env: + POSTGRES_USER: eigs + POSTGRES_PASSWORD: eigs_test + POSTGRES_DB: eigs_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U eigs" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + DATABASE_URL: postgres://eigs:eigs_test@127.0.0.1:5432/eigs_test + steps: + # Every branch and tag is needed: origin/main and v0.46.0. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - name: Fetch the approved builtin bar object + run: | + git fetch --no-tags origin 0a73adb0612f8612d75393891ae2ba2e1c803663 + git cat-file -e '0a73adb0612f8612d75393891ae2ba2e1c803663^{tree}' + - name: Install matched capability dependencies + run: | + sudo apt-get update + sudo apt-get install -y libpq-dev zlib1g-dev libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-ttf-2.0-0 + - name: Build tag, main and branch profiles serially; run the phase2 lane + run: | + set -euo pipefail + export XDG_CACHE_HOME="$RUNNER_TEMP/cache" + make --no-print-directory observer-anchor + anchor="$XDG_CACHE_HOME/eigs-oracle-anchor/$(sha256sum tools/observer_oracle/build_anchor.sh | cut -d' ' -f1)/src/eigenscript" + mkdir -p "$RUNNER_TEMP/main" "$RUNNER_TEMP/tag-profiles" + git archive origin/main | tar -x -C "$RUNNER_TEMP/main" + git archive v0.46.0 | tar -x -C "$RUNNER_TEMP/tag-profiles" + make -C "$RUNNER_TEMP/main" > "$RUNNER_TEMP/main-build.log" 2>&1 \ + || { tail -n 50 "$RUNNER_TEMP/main-build.log"; echo "::error::the origin/main build failed"; exit 1; } + # The table uses matching capability builds; a capability-unavailable + # transcript is a failure, including on the tag (ruling A3). + for profile in server zlib db; do + target="$profile" + [ "$profile" != db ] || target=server-db + make -C "$RUNNER_TEMP/tag-profiles" "$target" > "$RUNNER_TEMP/tag-$profile-build.log" 2>&1 \ + || { tail -n 50 "$RUNNER_TEMP/tag-$profile-build.log"; echo "::error::the tag $profile build failed"; exit 1; } + printf -v "TAG_BIN_${profile^^}" '%s' "$RUNNER_TEMP/tag-profiles/build/$target/eigenscript" + export "TAG_BIN_${profile^^}" + done + export CC_GUARD_LOG="$RUNNER_TEMP/cc-guard.log" + : > "$CC_GUARD_LOG" + for profile in server zlib db; do + target="$profile" + [ "$profile" != db ] || target=server-db + PATH="$PWD/tools/cc-guard-bin:$PATH" make "$target" + printf -v "BRANCH_BIN_${profile^^}" '%s' "$PWD/build/$target/eigenscript" + export "BRANCH_BIN_${profile^^}" + done + # Restore the release alias before the MAP and library checks. + PATH="$PWD/tools/cc-guard-bin:$PATH" make build + bash tools/cc_guard.sh --report + bash tools/phase2_ci.sh "$anchor" "$RUNNER_TEMP/main/src/eigenscript" src/eigenscript diff --git a/CLAUDE.md b/CLAUDE.md index 306ed7c9b..f85dcae76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ make tsan-http # TSan *with* ext_http+model+net (CI gate: EIGS_SUITE_SECTIONS=' make http # http+model variant — run tests/test_http_server.sh make zlib # DEFLATE decompression (`inflate`) via system zlib (-lz) make jit-smoke # standalone emitter tests (jit_smoke.c stubs all helpers) -bash tools/jit_diff.sh # JIT vs INTERPRETER differential over the corpus (tape-replayed; CI job) +bash tools/jit_diff.sh # JIT vs INTERPRETER differential over the corpus (CI job; a pair still unstable on a rerun is ledgered COULD_NOT_ADJUDICATE, never tape-replayed: #2122) make freestanding-check # 2-stage symbol gate for the EigenOS profile (docs/FREESTANDING.md) make freestanding-libc-diff # mini-libc/libm vs glibc oracle (src/freestanding/) make poison # 0xAA uninit-read hunter build; run the suite with MALLOC_PERTURB_=170 diff --git a/Makefile b/Makefile index 325dc486a..f3802ef19 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ LDFLAGS := -pie -Wl,-z,relro,-z,now -lm -lpthread endif SRC_DIR := src -SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_buf.c $(SRC_DIR)/builtins_host.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/fsutil.c $(SRC_DIR)/hash.c $(SRC_DIR)/alloc.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/lint_host.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/task.c $(SRC_DIR)/jit.c $(SRC_DIR)/jit_checked.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/tape_read.c $(SRC_DIR)/bundle.c $(SRC_DIR)/main.c +SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_int.c $(SRC_DIR)/builtins_buf.c $(SRC_DIR)/builtins_host.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/fsutil.c $(SRC_DIR)/hash.c $(SRC_DIR)/alloc.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/lint_host.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/task.c $(SRC_DIR)/jit.c $(SRC_DIR)/jit_checked.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/tape_read.c $(SRC_DIR)/bundle.c $(SRC_DIR)/main.c HOSTED_SOURCES := $(SOURCES) $(SRC_DIR)/ext_gfx.c MODEL_SRC := $(SRC_DIR)/model_io.c $(SRC_DIR)/model_infer.c $(SRC_DIR)/model_train.c BINARY := $(SRC_DIR)/eigenscript @@ -90,7 +90,7 @@ define AUX_REFRESH done endef -.PHONY: db-params-test all build server server-db full http net gfx zlib lib amalgamation tsan test test-changed precheck sandbox-intern-test install install-gfx clean coverage coverage-clean fuzz fuzz-run lsp lsp-asan dap jit-smoke embed-smoke embed-smoke-asan embed-smoke-asan-server embed-smoke-gfx embed-concurrent asan asan-server valgrind pgo poison freestanding-check freestanding-libc-diff asan-http asan-gfx tsan-server tsan-http nativefn-test arming-mt-test embed-roads print-% sigpipe-contract-test sigpipe-partial-test ui-sdl-input-gfx gfx-x11-regressions pool-off asan-pool-off pool-off-num pool-off-callenv jit-checked jit-checked-plant +.PHONY: db-params-test all build server server-db full http net gfx zlib lib amalgamation tsan test test-changed precheck sandbox-intern-test install install-gfx clean coverage coverage-clean fuzz fuzz-run lsp lsp-asan dap jit-smoke embed-smoke embed-smoke-asan embed-smoke-asan-server embed-smoke-gfx embed-concurrent asan asan-server valgrind pgo poison freestanding-check freestanding-libc-diff asan-http asan-gfx tsan-server tsan-http nativefn-test arming-mt-test embed-roads print-% sigpipe-contract-test sigpipe-partial-test ui-sdl-input-gfx gfx-x11-regressions pool-off asan-pool-off pool-off-num pool-off-callenv jit-checked jit-checked-plant smi-trap # ---- Per-variant objdir engine (#740) ------------------------------------- # The engine's rules are defined before `all`, so pin the default goal. @@ -196,7 +196,15 @@ SRC_V_jit-checked := $(HOSTED_SOURCES) FLAGS_jit-checked := $(CFLAGS) $(DEFS_OFF) -DEIGENSCRIPT_EXT_GFX=1 -DEIGS_JIT_CHECKED $(VERDEF) LIBS_jit-checked := $(LDFLAGS) -ldl -VARIANTS := release server server-db zlib asan asan-server tsan tsan-server valgrind poison pool-off asan-pool-off pool-off-num pool-off-callenv jit-checked +# #1758: compatibility target for the pre-R1.3 int-source gate. R1.3 retires +# EIGS_SMI_TRAP now that integer literals are producers; slot_as_ptr still +# checks tags through EIGS_SLOT_CHECKED. Historical R1.1/R1.2 calibrations +# build their pinned pre-flip source rather than treating this as a trap. +SRC_V_smi-trap := $(HOSTED_SOURCES) +FLAGS_smi-trap := $(CFLAGS) $(DEFS_OFF) -DEIGENSCRIPT_EXT_GFX=1 -DEIGS_SMI_TRAP -DEIGS_SLOT_CHECKED $(VERDEF) +LIBS_smi-trap := $(LDFLAGS) -ldl + +VARIANTS := release server server-db zlib asan asan-server tsan tsan-server valgrind poison pool-off asan-pool-off pool-off-num pool-off-callenv jit-checked smi-trap # Objects depend on Makefile+VERSION so a flag or version-string change # rebuilds; header edits are covered by the generated .d files. @@ -244,6 +252,50 @@ build/$(SLOT_TEST_VARIANT)/test_container_slots: tests/test_container_slots.c $( container-slot-test: build/$(SLOT_TEST_VARIANT)/test_container_slots @echo "Container slot API test built: $<" +# #1758 R1.1: the C slot test (docs/design/phase2-numbers.md 2.5). It includes +# the single-file amalgamation so the static slot classifiers are reachable; +# tests/phase2/slot_layer.sh runs it and compares the entries it examined with +# the population tools/phase2_inventory.py derives. The release flavour turns +# on slot_as_ptr's refusal with -DEIGS_SLOT_CHECKED; ASan turns it on itself. +# Both carry -Wall -Wextra, and the runner fails on any warning: release +# compiles the amalgamation at -O0 and ASan at -O1, the two levels #1775 +# found warnings at (and -O0 is about 41 s against -O2's 162 s). +PHASE2_SLOT_VARIANT ?= release +PHASE2_SLOT_FLAGS_release := -Wall -Wextra $(WERROR_FLAGS) -O0 -g -DEIGS_SLOT_CHECKED +PHASE2_SLOT_FLAGS_asan := -Wall -Wextra $(ASAN_FLAGS) +build/phase2/eigenscript_all.c: $(filter-out $(CLI_ONLY),$(SOURCES)) $(wildcard $(SRC_DIR)/*.h) tools/amalgamate.sh Makefile + bash tools/amalgamate.sh build/phase2 +build/phase2/slot_layer_%: tests/phase2/slot_layer.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +.PHONY: phase2-slot-test +phase2-slot-test: build/phase2/slot_layer_$(PHASE2_SLOT_VARIANT) + @echo "phase2 slot test built: $<" + +# #1758 R1.6a: final reader C fixture, through the no-SDL amalgamation. +PHASE2_READERS_VARIANT ?= release +PHASE2_READERS_FIXTURE ?= readers +build/phase2/readers_%: tests/phase2/readers.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +# R1.6d-D1: raw stored payloads through the actual graphics/audio readers. +build/phase2/gfx_audio_boundary_%: tests/phase2/gfx_audio_boundary.c src/ext_gfx.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread -ldl +build/phase2/gfx_byte_boundary_%: tests/phase2/gfx_byte_boundary.c src/ext_gfx.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread -ldl +# R1.6z-D1 public getter bit-round-trip gate; compile outside timed MAP rows. +build/phase2/embed_buffer_get_raw_%: tests/phase2/embed_buffer_get_raw.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +# Q7 lands in R1.6e; an absent source is a build failure once selected. +build/phase2/q7_readers_%: tests/phase2/q7_readers.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +# N6: actual shared writer plus Python repr/bit/truncation oracle cases. +build/phase2/num_text_%: tests/phase2/num_text.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +# R1.7-D4: signed-zero constant keys are compared by bits. +build/phase2/const_dedup_bits_%: tests/phase2/const_dedup_bits.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread +.PHONY: phase2-readers-test +phase2-readers-test: build/phase2/$(PHASE2_READERS_FIXTURE)_$(PHASE2_READERS_VARIANT) + @echo "phase2 reader test built: $<" # #1992: side-effect-free observer normalization must stay bit-identical to # num_guard's non-strict VALUE mapping. Link the selected suite variant so # the test exercises the real thread-local flags and strict-mode state. @@ -480,13 +532,16 @@ observer-anchor: # observer-anchor builds, or reuses, in the user cache. FLOOR=, on the command # line or in the environment, names another floor; an empty FLOOR skips the floor # and the anchor. EXPECT=ieee,int64 fails when an expected capability is absent -# (#1758). CI calls score.py directly, through tools/observer_oracle/ci_lane.sh -# (the required check `observer oracle (floor)`). +# (#1758). Both pass --declared tests/phase2/oracle_expect.txt: Phase 2's known- +# wrong capability verdicts, each with its recover round (#1830); an undeclared +# wrong verdict or a stale declaration fails, and #1776 merges only when the file +# is empty (tools/phase2_cost.sh --final). CI calls score.py directly, through +# tools/observer_oracle/ci_lane.sh (the required check `observer oracle (floor)`). .PHONY: observer-oracle observer-oracle: FLOOR ?= tools/observer_oracle/floor.json observer-oracle: build $(if $(FLOOR),$(OBSERVER_ANCHOR)) - python3 tools/observer_oracle/score.py src/eigenscript $(if $(FLOOR),--floor $(FLOOR)) $(if $(EXPECT),--expect $(EXPECT)) + python3 tools/observer_oracle/score.py src/eigenscript $(if $(FLOOR),--floor $(FLOOR)) $(if $(EXPECT),--expect $(EXPECT)) --declared tests/phase2/oracle_expect.txt install-gfx: install @echo "NOTE: 'make install-gfx' is a compatibility alias for 'make install'." @@ -514,7 +569,7 @@ $(SRC_DIR)/lsp_stdlib_index.h: $(wildcard lib/*.eigs) tools/gen_lsp_stdlib_index # Builtin half of the same idea (#742): names from the registration seams + # ext_names.h, hover detail from the signature comments. Reserved report # forms are excluded using lexer.c. Commit the generated header for review. -$(SRC_DIR)/lsp_builtin_index.h: $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_host.c \ +$(SRC_DIR)/lsp_builtin_index.h: $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_int.c $(SRC_DIR)/builtins_host.c \ $(SRC_DIR)/hash.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/ext_names.h \ $(SRC_DIR)/lexer.c tools/gen_lsp_builtin_index.sh bash tools/gen_lsp_builtin_index.sh @@ -745,6 +800,12 @@ jit-checked: build/jit-checked/eigenscript $(call RELINK,jit-checked) @echo "EigenScript $(VERSION) (jit-checked: emitted slot witnesses) built. Binary: $(BINARY)" +# #1758 R1.1 (see FLAGS_smi-trap): build it in a scratch tree, then +# tools/phase2_check.sh --through R1.1 --smi-trap /src/eigenscript ... +smi-trap: build/smi-trap/eigenscript + $(call RELINK,smi-trap) + @echo "EigenScript $(VERSION) (smi-trap compatibility: checked slots; source trap retired in R1.3) built. Binary: $(BINARY)" + # Profile-guided optimization. Builds an instrumented binary, runs the # DMG cpu_instrs workload to collect branch/edge counters, then rebuilds @@ -898,6 +959,10 @@ build/$(HISTORY_BOUNDARY_VARIANT)/test_trace_history_boundary: tests/test_trace_ trace-history-boundary-test: build/$(HISTORY_BOUNDARY_VARIANT)/test_trace_history_boundary @echo "Trace history boundary test built: $<" +# R1.6d-D1: actual net_send client with raw stored bytes and a real socketpair. +build/phase2/net_byte_boundary_%: tests/phase2/net_byte_boundary.c src/ext_net.c build/phase2/eigenscript_all.c tools/werror_flags.txt + $(CC) $(PHASE2_SLOT_FLAGS_$*) -Ibuild/phase2 -o $@ $< -lm -lpthread + # Witnesses for tools/make_lock_check.sh (#1940). With AFTER set, the recipe # refuses to run unless that file exists, so a second make proves it started # only after the first make's recipe wrote its DONE file. SESSION records the diff --git a/README.md b/README.md index 682789107..ebfc3948a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,22 @@ EigenScript

+ +Width arithmetic uses explicit wrapping, checked and saturating builtins. +N5 converts integral floats, then checks the named type range without masking. +Width results are ints; checked overflow raises `value`. `u64` uses int64 bit +patterns. `fround` returns a float after double-to-float32-to-double conversion +(ints convert to double first). See [the width builtins](docs/BUILTINS.md#width-arithmetic-n9). + +```eigenscript +print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]] +print of (fround of 0.1) +``` +```output +[-9223372036854775808, 0] +0.10000000149011612 +``` +

CI Release @@ -33,6 +49,54 @@ evaluations have separate stop scopes (see `docs/EMBEDDING.md`). `vm_run_bytecode` raises a catchable `value` error naming a rejected chunk descriptor; a valid program may still return `null`. `sandbox_run` reports descriptor rejection in its structured `{ok: false, error: ...}` result. Its optional fourth limit, `max_work`, bounds cumulative bytecode instructions across function calls and callback re-entry (default 10,000,000), independently of loop and allocation limits. This meters VM work, not elapsed time: a blocking native callback must return before the sandbox can stop. +The positions and counts of `char_at`, `substr`, `get_at`, `set_at`, +`copy_into`, `fill` and the `list_*` mutation/slice builtins use exact conversion +(N5). So do handle fields, bytecode ABI/counts/bytes, sandbox limits and numeric +exit codes. Integral floats are accepted; fractions and values outside the +receiving C type raise `value` before indexing or effects. +`recv_timeout` rounds milliseconds half away from zero and raises beyond the +C long range, with no one-year cap. `usleep` rounds microseconds and raises +beyond the C int range. `record_history` returns its previous flag as an int; +nonzero numeric flags still enable history. `task_detach` and sandbox error lines return ints. +`read_bytes` produces int bytes; `write_bytes` requires exact bytes in 0..255 +and returns an int count. File caps and corpus/stream counts follow N5; an +integral stream count outside its cap still raises `limit`. Subprocess exit +statuses are ints, and timeout seconds round to representable poll milliseconds. + +```eigenscript +print of (substr of ["hello", 1.0, 2.0]) +try: + print of (substr of ["hello", 1.5, 2]) +catch e: + print of e.kind +print of (num_type of (record_history of 0.5)) +print of (usleep of 1.5) +try: + usleep of 1e300 +catch e: + print of e.kind +path is mktemp of null +written is write_bytes of [path, [65.0, 66.0]] +bytes is read_bytes of path +print of [num_type of written, num_type of bytes[0], str_from_bytes of bytes] +try: + write_bytes of [path, [65.5]] +catch e: + print of e.kind +remove_file of path +``` +```output +el +value +int +null +value +["int", "int", "AB"] +value +``` + +Runtime-published counts and indices are exact ints (S9-K1): `__loop_iterations__`, live and historical `when` assignment counts, aggregate `what` lengths, trajectory `window`, error `line`, and scheduler trace `seq` and task ids. Scheduler `tick` remains a continuous virtual time. + Sandbox execution does not update shared temporal history: assignment values, names, counts and observer snapshots stay outside that history even when recording is armed. Ordinary tape assignment records still emit. Host and trusted descriptor history recording resumes normally outside the sandbox; sandbox temporal reads remain refused. ## Try it in your browser @@ -164,6 +228,44 @@ Comparisons, `not` and predicates return `true`/`false`, a `bool`. A bool is not a number: `true + 1` and `(x > 0) == 1` raise, so test a bool directly (`if x > 0:`). See [docs/SPEC.md](docs/SPEC.md#booleans-comparison-and-logic). + +Bitwise operators and `bit_*` builtins return ints, including when an +operand is an integral float. They use Lua 5.4's 64-bit shift rules: `>>` +zero-fills; a negative count shifts in the opposite direction; a count of +64 or more, or -64 or less, gives zero. Each operand must be an exact int64; +a fractional or out-of-range float raises `value`. + +```eigenscript +print of [1 << 64, 1 << -1, -1 >> 1, 8 >> -1, 1 << 63] +print of [bit_shr of [-1, 1], bit_shl of [8, -1], num_type of (3.0 & 1)] +``` +```output +[0, 0, 9223372036854775807, 16, -9223372036854775808] +[9223372036854775807, 4, "int"] +``` + +`//` floors the quotient; `%` has the divisor's sign. `//=` applies the same +operation to its assignment target, like `/=`. Zero divisors raise `value` +for int, float and mixed operands. Two ints produce an int for +`//` and `%`; a float operand produces a float. `/` always produces a float. +Float and mixed `//` use Python’s `divmod` quotient correction: `1.0 // 0.1` +is `9.0`, and `1e-300 // -1e300` is `-1.0`. `//=` uses the same rule. + +```eigenscript +x is -7 +x //= 2 +print of [x, 7 // -2, -7 % 3, 7 % -3, -7.5 % 2] +print of [num_type of (7 // 2), num_type of (7.0 // 2)] +y is 1.0 +y //= 0.1 +print of [1.0 // 0.1, 1 // 0.1, 1e-300 // -1e300, y, num_type of y] +``` +```output +[-4, -4, 2, -2, 0.5] +["int", "float"] +[9.0, 9.0, -1.0, 9.0, "float"] +``` + ### Ask Your Code Every value quietly remembers how it has been changing — and you don't pay @@ -256,8 +358,14 @@ by `python3 reports/convergence_loops/report1990.py`. See > `improving` uses the ML loss convention that lower is better: the value > must be decreasing, with steps contracting toward a limit. Growth is never > improving, even when it rises toward a plateau. For -> non-numeric values the observer's entropy reading applies — a value -> locating itself *from the inside*, with no external goal. The full +> non-numeric values, `converged` and ordinarily `stable` compare bounded, +> detached assignment snapshots; the other predicates and `report` read +> entropy. A bounded snapshot containing a NaN buffer cannot converge; +> `stable` instead uses the entropy-window rule after a full bounded +> comparison window (S10-D1). Refusal clears that eligibility, preserving +> `false` / `true` / `false` for the matmul scenario's equality, stability +> and convergence. Finite mutation checks and oversized-snapshot refusals +> remain. The full model is in > [docs/OBSERVER.md](docs/OBSERVER.md); the precise predicate semantics > (what `converged` requires, how it differs from `equilibrium`, the > N-step window) are in [docs/PREDICATES.md](docs/PREDICATES.md). @@ -340,16 +448,26 @@ and returns a buffer when every tensor operand was one. `zeros of n` returns a buffer (`zeros of [rows, cols]` still returns the nested list) — numeric work wants the flat container, and that is the name it reaches for. -EigenScript numbers are finite by construction. Operations that would overflow +EigenScript has exact signed int64 values and finite float64 numbers. +Decimal integer literals fitting int64 are exact; a point, exponent or +out-of-range decimal literal gives a float. Hex integer literals wrap +modulo 2^64. `type of` returns `"num"` for both, and `num_type of` distinguishes `"int"` +from `"float"` (null for a non-number). Int `+ - *` raise `value` on +overflow; unary minus and `abs` raise on INT64_MIN. Comparisons between +ints and floats are exact, including beyond 2^53; mixed arithmetic converts +ints to floats. Float operations that would overflow to infinity saturate at `+/-1e308`. Strict mode is the default: an out-of-domain call (`sqrt of -1`), a `NaN` result or a wrong-typed builtin argument (`abs of "x"`) raises a catchable error. Run with `EIGS_STRICT=0` to -get the finite stand-ins instead: `NaN` becomes `0`, `sqrt of -1` is `0`, and -`asin`/`acos` clamp their inputs. The same rule applies when a raw tensor-kernel -result is read from a `buffer`; one stored element never changes meaning with -the operator that consumes it. Structural buffer equality and scalar reductions -normalize each input by this rule too, as do mixed buffer/list materialization -and numeric byte or sample conversion. A raised read stops subsequent work. +get the finite stand-ins instead: `NaN` becomes float `0.0`, `sqrt of -1` is `0.0`, and +`asin`/`acos` clamp their inputs. Buffer consumers apply the same normalization, +except `sum`, `get_at`, indexing, iteration, `dot` and `write_bytes`, which refuse non-finite +buffer elements in every strict mode (N13). +Structural buffer equality compares stored doubles directly with IEEE equality; +list/dict self-equality follows that rule through nested and cyclic containers. +The other scalar reductions normalize each input, as do mixed buffer/list materialization +and the remaining numeric byte or sample conversions. `write_bytes` instead +requires exact bytes in 0..255 (N5/Q1). A raised read stops subsequent work. `type_mismatch` error by default; `EIGS_STRICT=0` retains numeric-zero substitution. `str_from_bytes` stops at numeric NUL, without inspecting later elements; byte codecs consume the complete list. @@ -361,6 +479,132 @@ The optional model extension raises a catchable `value` error when model's `max_seq_len`. Training applies the same limit to the combined input and output lengths; callers must choose their context window explicitly. +Empty `min`/`max` raises `value` in both call forms (R1.6b-D2); empty +`sum` remains int zero (C2). `sign_extend` reads exact int64 arguments and +raises `value` if its existing subtraction underflows (R1.6b-D1). + +Counts and indices are ints. `floor`, `ceil` and half-away-from-zero `round` +return an int when the result fits int64. `min`/`max` return the winning +argument with its kind; the first argument wins a tie. `num` requires a +complete numeric-literal string (N10). JSON integer text fitting int64 +decodes exactly (N14). + +The [C embedding API](docs/EMBEDDING.md#value-handles) follows Lua’s numeric +accessors: `eigs_value_new_int` constructs an exact int64, `eigs_value_is_int` +tests the stored kind (`lua_isinteger`), and `eigs_value_as_int` reports +whether a number converts exactly to int64 (`lua_tointegerx`). +`eigs_value_as_num` converts either kind to double (`lua_tonumber`); +`EIGS_TYPE_NUM` covers both. + +LSP hover on a numeric literal reports `int` or `float` from the literal’s +kind: `3.0` remains a float even though its value is integral. + +```eigenscript +print of [type of 3, num_type of 3, num_type of 3.0] +print of [9007199254740993, 3.0, -0.0] +print of [num_type of (len of "abc"), num_type of (floor of -2.5)] +print of [num of "9007199254740993", num_type of (num of "3.0")] +print of [num_type of (min of [3, 3.0]), num_type of (min of [3.0, 3])] +print of (json_path of ["{\"n\":9223372036854775807}", "n"]) +try: + min of [] +catch e: + print of [e.kind, (index_of of [e.message, "min"]) >= 0] +try: + print of (num of "12abc") +catch e: + print of e.kind +``` +```output +["num", "int", "float"] +[9007199254740993, 3.0, -0.0] +["int", "int"] +[9007199254740993, "float"] +["int", "float"] +9223372036854775807 +["value", true] +value +``` + + +`add`, `subtract` and `multiply` preserve exact ints for scalar operands and +for each pair of list elements, with `value` on int64 overflow. `negative` +preserves int scalar and list elements and raises at INT64_MIN. Buffer +arithmetic remains float. A buffer kernel stops at its first raised result, +releases its partial output and does not read later elements (#1417). +An all-int `sum` returns an exact int, while a +float anywhere in a nested input selects float addition for the entire +reduction. A buffer selects float addition too. An empty list's sum is int +zero, in both call forms. `sum` refuses non-finite buffer elements with a +`value` error in every strict mode (N13). In strict mode, it stops at the first NaN with a "not a number" diagnostic and sets `math_flags.invalid`; later elements are not read (#1417). + +```eigenscript +exact is sum of [9007199254740993, 1] +print of [exact, num_type of exact] +mixed is sum of [9223372036854775807, 1, 0.5] +print of [num_type of mixed, mixed == 9223372036854775808.0] +print of [num_type of (sum of []), num_type of (sum of ([]))] +print of (add of [[9007199254740993, 2], [1, 3]]) +try: + negative of (-9223372036854775807 - 1) +catch e: + print of e.kind +``` +```output +[9007199254740994, "int"] +["float", true] +["int", "int"] +[9007199254740994, 5] +value +``` + +Store handles carry integer `_store_id` and `_store_gen` fields. Every store operation requires exact, in-range integer handle fields; integral floats convert, while fractional, non-finite or out-of-range numeric fields raise `value` before store access (R1.6f-D1). `store_count` returns an integer. + +`store_get`, `store_delete`, and `store_update` accept string keys or exact +int64 numeric keys. Integral floats convert; fractional or out-of-range floats +raise `value` before lookup or mutation. Numeric keys retain all int64 digits. +Stored decimal integer text within int64 decodes as an int; a decimal point or +exponent selects a float. Buffer payloads remain floats. Store records preserve +int64 digits and use shortest round-trip float text, including `.0` for +integral floats and the sign of `-0.0`, so `3.0` reads back as a float. + +See the [stored-record example](docs/SPEC.md#stored-records). + +PostgreSQL integer columns (`smallint`, `integer`, `bigint`, `oid`) return +exact int64 values through `db_query_value`; `db_query_json` retains their +exact decimal digits (N14, Q-DB253). A `bigint` beyond 2^53 is an int, +including both int64 boundaries. `numeric` remains text, and finite real +columns returned directly remain floats. Float parameters use N6 shortest round-trip text, preserving `.0` and +signed zero. Store records use the same float writer and exact int64 digits +(N14, #1762). The executable DB examples and boundary assertions +in `tests/test_db.eigs` run on the server-db profile with PostgreSQL. + +TCP handles, ports and sent-byte counts are integers. Numeric socket handles, ports, receive counts and outgoing bytes require exact integers (integral floats convert); bytes must be in 0..255. `net_send` validates the entire payload before sending any byte. Poll timeouts are continuous milliseconds rounded to the C int range; an unrepresentable duration raises `value`. + +`http_early_bind` and `http_serve` require an exact integer port in 0..65535; integral floats convert and port zero requests an ephemeral port. Fractional or out-of-range ports raise `value` before binding. `shared_delete` and `shared_size` return integer counts. + +N6 prints each float with Python's shortest round-trip spelling: integral +floats retain `.0`, negative zero is `-0.0`, and scientific notation starts +below `1e-4` or at `1e16`. Ints keep their exact decimal digits. `print`, +`write`, `str`, f-strings and JSON share the number writer. The executed +examples are `tests/phase2/rows/N6.float-repr.eigs` and +`tests/phase2/rows/N6.str-fstring.eigs`. + +The HTTP shared counter `shared_incr` follows numeric `+`: two int operands +return an exact int and int64 overflow raises `value` without changing the +stored counter (A). A missing counter starts at int zero. A float operand +produces a float. The stored operand is its JSON-decoded value; N6 preserves an +integer-valued float as a float by retaining its `.0`. Executable shared-counter examples in +`tests/test_http.eigs` cover exact values, both overflow directions and the +next operation after a raise. + +`http_post` also preserves every digit of JSON integer header values within +int64 before sending them as text (N14, B6). The executed loopback example +is `tests/phase2/builtins/builtins/http_post.eigs`. + +Builtin `sort` sorts a list in place; `sort_by` returns a new list using numeric keys. +Both are stable on every platform: equal elements or keys keep their input order. + ## Standard Library The machine-checkable EigenCore-0 reference frontend lives in [`spec/lean/`](spec/lean/). @@ -621,6 +865,9 @@ spawn occurrences to match workers. Tape associations carry state grouping; event order does not choose replay identity. Hosts explicitly advance replay sessions between evaluations; unread sibling outcomes prevent advance. See [docs/TRACE.md](docs/TRACE.md) and [docs/EMBEDDING.md](docs/EMBEDDING.md) for the binding and version contract. +Tape format v9 preserves integer/float kinds, chunked module sources, and +length-framed oversized builtin answers; +older formats are refused. Replay also refuses side-effecting boundaries the tape cannot reconstruct: in particular, `mktemp` raises before creating a temporary file. Recorded builtins validate arguments before their tape boundary, so a call @@ -715,3 +962,88 @@ Codespace and a green CI run can't drift. > Launching a Codespace bills to *your* account's free monthly hours — you can > open this org repo from a personal account without org Codespaces billing. + +Tensor sizes and indices follow N5: an integral float is accepted and a +fraction raises `value`. `gather` keeps the selected list cell's int/float +kind; buffer elements and numeric tensor calculations are floats. `shape` +returns integer dimension counts. `mean` and `norm` refuse non-finite buffer +cells with `value` in both strict modes. + +```eigenscript +g is gather of [[[1, 2], [3, 4]], [1.0, 0]] +print of [g, num_type of g[0], num_type of (shape of g)[0]] +try: + gather of [[10, 20], 0.5] +catch e: + print of e.kind +``` +```output +[[2, 3], "int", "int"] +value +``` + +Buffer sizes, indices, offsets and counts follow N5. Byte inputs to +`str_from_bytes`, `f64_from_bytes`, `inflate` and `buf_from_pcm16le` must be +exact integers in 0..255; fractions and out-of-range bytes raise `value`. +`buf_len`, `f64_to_bytes` and `inflate` return integer counts or bytes; +buffer storage and scalar buffer reads are floats. `buf_get`, `buf_peak`, +`buf_dot` and the byte/PCM codecs refuse non-finite input cells with `value`. + +Model token ids and generation limits follow N5 and the C int range; +fractional ids or token counts raise `value`. `eigen_generate` returns int +token ids, while `eigen_eval_loss` returns a float. Temperature, top_p and +training learning rates remain float quantities. Model JSON weights stay +float data; the loader status strings are unchanged. + +Graphics and audio magnitudes round half away from zero and raise `value` +when the result exceeds the C call's range. This refusal happens even before +SDL is loaded: + +```eigenscript +try: + gfx_delay of 1e300 +catch e: + print of e.kind +``` +```output +value +``` + +### Stored buffer data boundary + +Buffer kernels can retain non-finite data. Scalar indexing and iteration +raise `value` when that data is read, in every strict mode (N13). + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +try: + print of p[0] +catch e: + print of e.kind +``` +```output +value +``` + +### Buffer equality + +Buffers compare their stored doubles with IEEE equality: NaN differs from +everything, even itself; infinities compare by sign; signed zeros are equal. +Equality never raises or changes math flags, including under strict mode. + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +print of (p == p) +print of (p == g) +sgd_update of [p, p, 1.0] +print of (p == p) +``` +```output +true +false +false +``` diff --git a/bench/check_regression.sh b/bench/check_regression.sh index 040ce48f3..c53f68869 100755 --- a/bench/check_regression.sh +++ b/bench/check_regression.sh @@ -1,13 +1,14 @@ #!/bin/bash # Deterministic perf regression gate (#398). Wall-clock medians flake on shared # CI runners, so the GATE uses cachegrind instruction counts (Ir) instead: -# same binary + same input => identical Ir, run to run, machine to machine. A -# regression is then a diffable fact, not a statistical argument. Fails if any -# workload's Ir exceeds its baseline: --vs (CI, same job) by more than THRESHOLD_PCT, the checked-in file by more than FILE_THRESHOLD_PCT. +# same binary + same input in one environment gives a diffable instruction +# count; glibc ifunc selection can vary across hosts (#1939). The --vs +# threshold is THRESHOLD_PCT; checked-in files use FILE_THRESHOLD_PCT. # # bench/check_regression.sh # gate: compare vs bench/baseline.txt # bench/check_regression.sh --update # regenerate the baseline # bench/check_regression.sh --selftest # prove a 2x-work pessimization is caught +# bench/check_regression.sh --list-vs # derived --vs workload/tier population set -u DIR="$(cd "$(dirname "$0")" && pwd)" EIGS="${EIGENSCRIPT:-$DIR/../src/eigenscript}" @@ -30,12 +31,18 @@ FILE_THRESHOLD_PERMILLE=$(( FILE_THRESHOLD_PCT * 10 )) # above and below stay exact integer permille comparisons. pct() { awk -v a="$1" -v b="$2" 'BEGIN { printf "%.3f", (a - b) * 100 / b }'; } -ir_of() { # ir_of -> instruction count +# Drop caller EIGS_* settings, then select the requested tier explicitly. +# --vs measures the union of bench/*.eigs (default JIT) and every declared +# workload/tier pair, so its own CI log can re-declare every max_ci (#1939). +ir_of() { # ir_of [jit|off] -> instruction count + local tier="${3:-jit}" tier_env= + case "$tier" in jit) ;; off) tier_env=EIGS_JIT_OFF=1 ;; *) return 2 ;; esac # Capture the producer before parsing it. This is the standard portable # shell pattern when a pipeline must preserve an earlier command's status # (rather than relying on the pipeline's final command or bash pipefail). out=$(mktemp) || return - "$VALGRIND" --tool=cachegrind --cachegrind-out-file=/dev/null "$1" "$2" >"$out" 2>&1 + env $(compgen -e | grep '^EIGS_' | sed 's/^/-u /') $tier_env \ + "$VALGRIND" --tool=cachegrind --cachegrind-out-file=/dev/null "$1" "$2" >"$out" 2>&1 status=$? if [ "$status" -ne 0 ]; then cat "$out" >&2 @@ -110,21 +117,43 @@ case "${1:-}" in reference="$scratch/reference" printf '#!/bin/sh\nexit 0\n' > "$current" cp "$current" "$reference" + # Equal synthetic counts would make real declared costs STALE. Keep the + # full population and give each declared row counts inside its own band. cat > "$profiler" <<'EOF' -#!/bin/sh -while [ "$#" -gt 2 ]; do shift; done -printf '%s %s\n' "$(basename "$1")" "$(basename "$2")" >> "$MEASURE_LOG" -echo 'I refs: 1,000' +#!/usr/bin/env python3 +from decimal import Decimal +import os +from pathlib import Path +import sys + +root = Path(os.environ['BENCH_SELFTEST_ROOT']).resolve() +sys.path.insert(0, str(root / 'tools')) +from phase2_cost_declarations import NOISE, parse_declarations + +binary, file = map(Path, sys.argv[-2:]) +workload = file.resolve().relative_to(root).as_posix() +tier = 'off' if os.environ.get('EIGS_JIT_OFF') == '1' else 'jit' +with open(os.environ['MEASURE_LOG'], 'a') as log: + log.write(f'{binary.name}\t{workload}\t{tier}\n') +declarations = parse_declarations((root / 'tests/phase2/cost_expect.txt').read_text()) +declaration = declarations.get((workload, tier)) +count = 1000000 +if binary.name == 'current' and declaration: + delta = max(Decimal(0), declaration.max_ci - NOISE) + count = int(Decimal(count) * (1 + delta / 100)) +print(f'I refs: {count}') EOF chmod +x "$profiler" "$current" "$reference" - if ! MEASURE_LOG="$measure_log" VALGRIND="$profiler" EIGENSCRIPT="$current" "$0" --vs "$reference" >/dev/null; then + if ! BENCH_SELFTEST_ROOT="$DIR/.." MEASURE_LOG="$measure_log" VALGRIND="$profiler" EIGENSCRIPT="$current" "$0" --vs "$reference" >"$scratch/population.out" 2>&1; then + cat "$scratch/population.out" rm -rf "$scratch" echo "SELFTEST FAILED: valid derived benchmark population was rejected"; exit 1 fi expected="$scratch/expected" - for f in "$DIR"/*.eigs; do - printf 'current %s\nreference %s\n' "$(basename "$f")" "$(basename "$f")" - done | sort > "$expected" + "$0" --list-vs > "$scratch/population" || exit 1 + while IFS=$'\t' read -r workload tier; do + printf 'current\t%s\t%s\nreference\t%s\t%s\n' "$workload" "$tier" "$workload" "$tier" + done < "$scratch/population" | sort > "$expected" if ! sort "$measure_log" | cmp -s - "$expected"; then rm -rf "$scratch" echo "SELFTEST FAILED: a workload/tier was skipped or measured more than once"; exit 1 @@ -132,25 +161,46 @@ EOF rm -rf "$scratch" echo "SELFTEST OK: every derived workload/tier is measured once" exit 0 ;; ---vs) - # Compare THIS binary ($EIGENSCRIPT) against a reference binary ($2) on the - # same workloads. Both built in the same environment => the Ir difference is - # a pure code-change signal (this is what CI runs: PR binary vs origin/main - # binary, so no cross-machine baseline drift). Fails on > THRESHOLD_PCT. - ref="$2"; fail=0 +--vs|--list-vs) + # Base and candidate use this gate's own measurement recipe. Its CI + # counts, including off-tier declarations, are the source of max_ci. + ref="${2:-}"; fail=0; used=0 + if [ "$1" = --vs ] && [ -z "$ref" ]; then echo " FAIL: --vs requires a reference binary"; exit 2; fi + decl=$(bash "$DIR/../tools/phase2_cost.sh" --declared all --method ci) \ + || { echo " FAIL: cannot read the declared costs (tools/phase2_cost.sh --declared all --method ci)"; exit 2; } + ndecl=$(awk 'NF {n++} END {print n+0}' <<< "$decl") + work=$(mktemp -d "${TMPDIR:-/tmp}/eigs-bench-vs.XXXXXX") || exit 2 + trap 'rm -rf "$work"' EXIT for f in "$DIR"/*.eigs; do - name=$(basename "$f" .eigs) - a=$(ir_of "$EIGS" "$f") || exit $? - b=$(ir_of "$ref" "$f") || exit $? - inc=$(pct "$a" "$b") - if [ $(( (a - b) * 1000 )) -gt $(( THRESHOLD_PERMILLE * b )) ]; then - echo " REGRESSION: $name Ir ${b} -> ${a} (+${inc}% > ${THRESHOLD_PCT}%)"; fail=1 - else - echo " ok: $name Ir ${a} (${inc}% vs ref)" - fi + printf 'bench/%s\tjit\n' "$(basename "$f")" >> "$work/population.unsorted" done - [ "$fail" -eq 0 ] && echo "no instruction-count regressions vs reference (threshold ${THRESHOLD_PCT}%)" + awk -F'\t' 'NF {print $1 "\t" $2}' <<< "$decl" >> "$work/population.unsorted" + LC_ALL=C sort -u "$work/population.unsorted" > "$work/population" + if [ "$1" = --list-vs ]; then cat "$work/population"; exit 0; fi + nrows=$(wc -l < "$work/population") + echo " tiers: explicit jit/off (caller EIGS_* unset); column: max_ci; stale_below_max=0.5; declared rows: $ndecl; measured rows: $nrows" + while IFS=$'\t' read -r workload tier; do + f="$DIR/../$workload" + [ -f "$f" ] || { echo " FAIL: missing workload: $workload"; exit 2; } + a=$(ir_of "$EIGS" "$f" "$tier") || exit $? + b=$(ir_of "$ref" "$f" "$tier") || exit $? + [[ "$a" =~ ^[1-9][0-9]*$ && "$b" =~ ^[1-9][0-9]*$ ]] \ + || { echo " FAIL: $workload $tier: no instruction count (cand='$a' ref='$b')"; exit 2; } + row=$(awk -F'\t' -v w="$workload" -v t="$tier" '$1 == w && $2 == t' <<< "$decl") + if [ -n "$row" ]; then used=$((used + 1)); fi + # One exact two-sided predicate; selects max_ci for both tiers. + python3 "$DIR/../tools/phase2_cost_declarations.py" --method ci \ + --row "$workload" "$tier" "$b" "$a" + rc=$? + [ "$rc" -le 1 ] || exit "$rc" + [ "$rc" -eq 0 ] || fail=1 + done < "$work/population" + # Every declaration was measured exactly once, regardless of workload dir + # or tier. The union's population is derived, never hand-maintained. + [ "$used" -eq "$ndecl" ] || { echo " FAIL: declared rows=$ndecl applied=$used"; exit 2; } + [ "$fail" -eq 0 ] && echo "no instruction-count regressions vs reference (threshold ${THRESHOLD_PCT}%; declared rows applied=$used; measured rows=$nrows)" exit $fail ;; + esac # Default (local): compare against the checked-in baseline. NOTE the baseline is diff --git a/changes/added/1758-int64-flip.md b/changes/added/1758-int64-flip.md new file mode 100644 index 000000000..3fc75f60b --- /dev/null +++ b/changes/added/1758-int64-flip.md @@ -0,0 +1,5 @@ +- Integer literals now retain exact int64 values, with exact int/float + comparisons and checked `+`, `-`, `*`, negation and `abs` that raise `value` + on int64 overflow. `num_type` reports `"int"`, `"float"` or `null`; `type` + continues to report `"num"` for either numeric kind. Trace tape format v7 + preserves numeric kinds and refuses older tapes (#1758). diff --git a/changes/added/1758-n9-width-arithmetic.md b/changes/added/1758-n9-width-arithmetic.md new file mode 100644 index 000000000..437d46488 --- /dev/null +++ b/changes/added/1758-n9-width-arithmetic.md @@ -0,0 +1 @@ +- Add explicit wrapping, checked and saturating add/sub/mul for signed and unsigned 8/16/32/64-bit widths, unsigned u64 comparison/division/remainder/decimal formatting, and float32 rounding with `fround` (#1758 N9). Operands use N5 exact conversion and type range checks; width results are ints. diff --git a/changes/added/2120-seed-random-env.md b/changes/added/2120-seed-random-env.md new file mode 100644 index 000000000..44cc2f2e7 --- /dev/null +++ b/changes/added/2120-seed-random-env.md @@ -0,0 +1 @@ +- Add `EIGS_SEED_RANDOM` to make the process-wide random stream reproducible for differential testing and reproduction (#2120). diff --git a/changes/breaking/1758-n8-lua-bitwise.md b/changes/breaking/1758-n8-lua-bitwise.md new file mode 100644 index 000000000..8364e6add --- /dev/null +++ b/changes/breaking/1758-n8-lua-bitwise.md @@ -0,0 +1 @@ +- Bitwise operators and `bit_*` builtins now return exact int64 values. Shifts follow Lua 5.4: right shifts zero-fill, negative counts reverse direction, and counts outside (-64, 64) give zero. Fractional or out-of-range operands still raise `value` (#1740, #1758 N8). diff --git a/changes/changed/1758-number-text.md b/changes/changed/1758-number-text.md new file mode 100644 index 000000000..12a1d034c --- /dev/null +++ b/changes/changed/1758-number-text.md @@ -0,0 +1 @@ +- Float text now uses Python-style shortest round-trip spelling across display, f-strings and JSON (N6), preserving integral float `.0` and signed zero. Integer text remains exact. Store records and SQL parameters use the same writer, fixing dropped digits (#1762). Trace number records use the same spelling without a tape format version change (T1). Constant dedup preserves binary64 bits, including distinct signed zeros. diff --git a/changes/changed/1758-phase2-store-http-net.md b/changes/changed/1758-phase2-store-http-net.md new file mode 100644 index 000000000..376956e6b --- /dev/null +++ b/changes/changed/1758-phase2-store-http-net.md @@ -0,0 +1 @@ +- Store, HTTP and TCP numeric arguments use exact integer readers: fractional identifiers, ports, counts and bytes raise `value`; counts, ports and handles return integers. TCP byte values must be in 0..255 and an invalid payload sends nothing. diff --git a/changes/changed/1758-r1-4-floor-division.md b/changes/changed/1758-r1-4-floor-division.md new file mode 100644 index 000000000..04193efa2 --- /dev/null +++ b/changes/changed/1758-r1-4-floor-division.md @@ -0,0 +1 @@ +- Add floor division `//` and `//=`, returning an exact int for two ints and a float otherwise. `%` now uses floor modulo with the divisor's sign. Integer floor-division overflow raises; `INT64_MIN % -1` is 0. Zero divisors still raise for every numeric kind until N11 (#1758). diff --git a/changes/changed/1758-r16b-numeric-builtins.md b/changes/changed/1758-r16b-numeric-builtins.md new file mode 100644 index 000000000..72e1f80c5 --- /dev/null +++ b/changes/changed/1758-r16b-numeric-builtins.md @@ -0,0 +1,9 @@ +- Numeric builtins preserve exact int64 results and use exact integer arguments. `num` now refuses partial or empty numeric text (N10); JSON, store and database integer text decodes as int64 (N14). Scalar/list tensor arithmetic and shared counters raise on int64 overflow. Empty `min`/`max` now raises `value` in both call forms; `sign_extend` raises on subtraction underflow (R1.6b-D1/D2). +- EigenStore numeric lookup, update, and delete keys use all int64 digits (N5), including keys above 2^53. Fractional or out-of-range floats raise before record changes. Record integer text decodes exactly (N14); float persistence text remains unchanged until R1.7. +- HTTP POST JSON integer headers retain all int64 digits (N14, B6, R1.6b-D3); the pre-R1.6 decoder could round integer values above 2^53. +- CI follow-up: construct ranges with an int64 induction value and a descending element count, avoiding a redundant loop counter while preserving bounds, integer results, limits, and ownership. +- CI follow-up: preserve stored float payload formatting, including canonical NaN, until the scheduled N13 encoder refusal; remove the migrated abs raw-read exception. +- CI follow-up: keep validate.is_alpha recognizing letters after N10 by removing an unused numeric-text conversion; retain its string membership checks. +- CI follow-up: retain literal min/max guard names in the shared reducer so the strict guard census and its probes agree. +- CI follow-up: retain the first strict NaN diagnostic and invalid flag when `sum` reads a buffer (#1417), before reading a later infinity. Checked int64 sum overflow remains a `value` error. +- CI follow-up: stop shared tensor buffer kernels after their first raised result, releasing partial buffers and preserving the original diagnostic and math flags (#1417). diff --git a/changes/changed/1758-r16c-builtins.md b/changes/changed/1758-r16c-builtins.md new file mode 100644 index 000000000..770e1ee16 --- /dev/null +++ b/changes/changed/1758-r16c-builtins.md @@ -0,0 +1,6 @@ +- Numeric builtins now use their final integer or float readers (#1758, R1.6c). + Integer-valued positions, counts, handles, bytecode fields and bytes reject + fractions instead of truncating; continuous durations use checked rounding. + Count, index and status results are ints; buffer data remains float. + Migrated buffer readers refuse non-finite payloads (N13), retaining the + first strict-NaN diagnostic and INVALID flag. diff --git a/changes/changed/1758-r16d-tensor-buffer-model.md b/changes/changed/1758-r16d-tensor-buffer-model.md new file mode 100644 index 000000000..16170e5b6 --- /dev/null +++ b/changes/changed/1758-r16d-tensor-buffer-model.md @@ -0,0 +1 @@ +- Tensor sizes and indices require exact integer values (N5). `gather` preserves selected list cells' numeric kinds, and `shape` returns integer dimensions. Float tensor calculations convert integer inputs to float storage. Buffer windows and byte codecs check exact integer inputs; bytes outside 0..255 raise instead of wrapping. Buffer lengths, `f64_to_bytes` and `inflate` byte lists use integer values. Model token ids and generation counts require exact integer inputs, and generation returns integer token ids. diff --git a/changes/changed/1758-r16e-gfx-numeric-readers.md b/changes/changed/1758-r16e-gfx-numeric-readers.md new file mode 100644 index 000000000..8632c4eec --- /dev/null +++ b/changes/changed/1758-r16e-gfx-numeric-readers.md @@ -0,0 +1 @@ +- Migrate graphics and audio numeric arguments to the Phase 2 exact integer and Q7 readers: clip complete geometry before rounding, clamp bounded quantities, and refuse unrepresentable magnitudes. Buffer-backed audio and framebuffer reads reject stored non-finite samples at the scalar boundary. diff --git a/changes/changed/1758-r16z-bridge.md b/changes/changed/1758-r16z-bridge.md new file mode 100644 index 000000000..e074a8e0d --- /dev/null +++ b/changes/changed/1758-r16z-bridge.md @@ -0,0 +1,2 @@ +- Buffer indexing and iteration refuse stored non-finite elements in every strict mode. Buffer equality compares stored doubles with IEEE equality, without raises or math flags; embedding buffer reads return the exact stored double, including NaN payloads, infinities and signed zero. The transitional numeric bridge and caller-less helpers are removed (#1758, R1.6z). +- Container self-equality follows IEEE buffer equality through nested and cyclic lists/dicts, with a visited scan that keeps shared structures linear (R1.6z-D2). diff --git a/changes/changed/1758-r18-embed-ints.md b/changes/changed/1758-r18-embed-ints.md new file mode 100644 index 000000000..96b36fc94 --- /dev/null +++ b/changes/changed/1758-r18-embed-ints.md @@ -0,0 +1,6 @@ +- The embedding API adds exact int64 construction and conversion +(`eigs_value_new_int`, `eigs_value_as_int`) and an int-kind predicate +(`eigs_value_is_int`), following Lua's numeric C API. Numeric literal hover +distinguishes int and float. The iterator's hidden int position again uses +an in-place immediate update, recovering the S9-K1 fast path. Phase 2 cost +declarations have separate local and CI maxima measured by each gate. diff --git a/changes/fixed/1758-number-text-amalgamation.md b/changes/fixed/1758-number-text-amalgamation.md new file mode 100644 index 000000000..ce32dd20d --- /dev/null +++ b/changes/fixed/1758-number-text-amalgamation.md @@ -0,0 +1 @@ +- Size N6 float-renderer scratch text for the complete unsigned significand and signed exponent format, keeping the two-file amalgamation warning-free at every tested optimization level. diff --git a/changes/fixed/1758-runtime-counts-int.md b/changes/fixed/1758-runtime-counts-int.md new file mode 100644 index 000000000..2732822eb --- /dev/null +++ b/changes/fixed/1758-runtime-counts-int.md @@ -0,0 +1 @@ +- Publish runtime counts and indices as exact ints (S9-K1): loop and assignment counts, historical assignment ordinals, aggregate interrogative lengths, trajectory window sizes, error source lines and scheduler sequence/task metadata. Hidden iterator positions and index-miss sentinels also keep their integer kind. diff --git a/changes/fixed/1758-stable-sort.md b/changes/fixed/1758-stable-sort.md new file mode 100644 index 000000000..c04ba30db --- /dev/null +++ b/changes/fixed/1758-stable-sort.md @@ -0,0 +1 @@ +- `sort` now preserves the input order of equal elements on every platform, including mixed int/float ties. Both `sort` and `sort_by` guarantee stable ordering (the #1758 R1.7 fix-forward sort ruling). diff --git a/changes/fixed/1775-amalgamation-warnings.md b/changes/fixed/1775-amalgamation-warnings.md new file mode 100644 index 000000000..535276718 --- /dev/null +++ b/changes/fixed/1775-amalgamation-warnings.md @@ -0,0 +1 @@ +- The single-file amalgamation (`make amalgamation`, `build/eigenscript_all.c`) compiles without warnings at `-O0` and `-O1` with `-Wall -Wextra`, as it already did at `-O2`. `-O0` reported a missing return at the end of `builtin_tensor_gather`, and `-O1` reported seven possibly uninitialized locals in the bitwise and index paths of the VM; none was reachable, but an embedder building the two-file artifact with warnings on saw them (#1775). diff --git a/changes/fixed/1882-float-floor-division.md b/changes/fixed/1882-float-floor-division.md new file mode 100644 index 000000000..619b68a9f --- /dev/null +++ b/changes/fixed/1882-float-floor-division.md @@ -0,0 +1 @@ +- Float and mixed `//` and `//=` now use Python’s `divmod` quotient correction. Decimal boundaries such as `1.0 // 0.1` return `9.0`, and an underflowed negative ratio such as `1e-300 // -1e300` returns `-1.0`. Integer arithmetic and the current zero-divisor errors are unchanged. (#1882) diff --git a/changes/internal/1758-phase2-cost.md b/changes/internal/1758-phase2-cost.md new file mode 100644 index 000000000..7afaeaff2 --- /dev/null +++ b/changes/internal/1758-phase2-cost.md @@ -0,0 +1,2 @@ +- Keep float iterator exits ahead of int refusals and retain inlined index validation and reference acquisition; measure the CodSpeed workload list in both execution tiers (#1758). +- Apply the machine's graphics-only memory ceiling in the Phase 2 corpus comparison without limiting thread-stack reservations (#1758). diff --git a/changes/internal/1758-phase2-r1-2e.md b/changes/internal/1758-phase2-r1-2e.md new file mode 100644 index 000000000..5d2e17e29 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2e.md @@ -0,0 +1 @@ +- Admit int Values in the tensor bridge. diff --git a/changes/internal/1758-phase2-r1-2f.md b/changes/internal/1758-phase2-r1-2f.md new file mode 100644 index 000000000..86ee604b2 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2f.md @@ -0,0 +1 @@ +- Admit int Values in the buffer bridge. diff --git a/changes/internal/1758-phase2-r1-2g.md b/changes/internal/1758-phase2-r1-2g.md new file mode 100644 index 000000000..6b3fa6ff2 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2g.md @@ -0,0 +1 @@ +- Admit int Values in the host bridge. diff --git a/changes/internal/1758-phase2-r1-2j.md b/changes/internal/1758-phase2-r1-2j.md new file mode 100644 index 000000000..e8b1ff545 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2j.md @@ -0,0 +1 @@ +- Migrate the JIT float guards and standalone emitter stub. diff --git a/changes/internal/1758-phase2-r1-2k.md b/changes/internal/1758-phase2-r1-2k.md new file mode 100644 index 000000000..7347aed5b --- /dev/null +++ b/changes/internal/1758-phase2-r1-2k.md @@ -0,0 +1 @@ +- Give constant deduplication and disassembly exact int arms. diff --git a/changes/internal/1758-phase2-r1-2l.md b/changes/internal/1758-phase2-r1-2l.md new file mode 100644 index 000000000..2df619c1d --- /dev/null +++ b/changes/internal/1758-phase2-r1-2l.md @@ -0,0 +1 @@ +- Migrate float tape tokens and retain the v7 int refusal. diff --git a/changes/internal/1758-phase2-r1-2m.md b/changes/internal/1758-phase2-r1-2m.md new file mode 100644 index 000000000..4f95853f5 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2m.md @@ -0,0 +1 @@ +- Migrate the store encoder and numeric bridge. diff --git a/changes/internal/1758-phase2-r1-2n.md b/changes/internal/1758-phase2-r1-2n.md new file mode 100644 index 000000000..efe359c6f --- /dev/null +++ b/changes/internal/1758-phase2-r1-2n.md @@ -0,0 +1 @@ +- Admit int Values in the embedding number bridge. diff --git a/changes/internal/1758-phase2-r1-2o.md b/changes/internal/1758-phase2-r1-2o.md new file mode 100644 index 000000000..03a4e6dfd --- /dev/null +++ b/changes/internal/1758-phase2-r1-2o.md @@ -0,0 +1 @@ +- Migrate extension numeric guards and exact database int encoding. diff --git a/changes/internal/1758-phase2-r1-2q.md b/changes/internal/1758-phase2-r1-2q.md new file mode 100644 index 000000000..fc7b0b0d0 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2q.md @@ -0,0 +1 @@ +- Finish the VM Value number migration: exact int readers and explicit uncatchable refusals for arithmetic and iterator state whose semantics land in R1.3. diff --git a/changes/internal/1758-phase2-r1-2z.md b/changes/internal/1758-phase2-r1-2z.md new file mode 100644 index 000000000..f1245c940 --- /dev/null +++ b/changes/internal/1758-phase2-r1-2z.md @@ -0,0 +1 @@ +- Remove the transitional Value numeric aliases after migrating all consumers; retain the number bridge until R1.6. diff --git a/changes/internal/1758-phase2-r16a-readers.md b/changes/internal/1758-phase2-r16a-readers.md new file mode 100644 index 000000000..bce9cb324 --- /dev/null +++ b/changes/internal/1758-phase2-r16a-readers.md @@ -0,0 +1,5 @@ +- Add the Phase 2 final numeric readers and an opt-in bridge deprecation meter; builtin callers and behavior are unchanged. + +Declare the bar's probe directory as owned by tools/phase2_builtins.sh in the +corpus exclusion ledger, so the generic differential preserves its standalone +program population while the bar supplies probe fixtures and profiles. diff --git a/changes/internal/1758-phase2-slot-layer.md b/changes/internal/1758-phase2-slot-layer.md new file mode 100644 index 000000000..bacf4edb9 --- /dev/null +++ b/changes/internal/1758-phase2-slot-layer.md @@ -0,0 +1,2 @@ +- #1758 Phase 2 round R1.1, the slot layer: the SMI tag (`0xFFFA`, an int in [-2^47, 2^47)) and its readers exist, and every + slot reader decided what an int does there, but nothing produces an int yet, so no program behaves differently. diff --git a/changes/internal/1758-phase2-value-builtins.md b/changes/internal/1758-phase2-value-builtins.md new file mode 100644 index 000000000..238d39493 --- /dev/null +++ b/changes/internal/1758-phase2-value-builtins.md @@ -0,0 +1 @@ +- Prepare builtin number guards for both Value kinds while retaining the double bridge; preserve exact integer text and numeric sort keys for the future int producer. diff --git a/changes/internal/1758-phase2-value-gfx.md b/changes/internal/1758-phase2-value-gfx.md new file mode 100644 index 000000000..ec1618500 --- /dev/null +++ b/changes/internal/1758-phase2-value-gfx.md @@ -0,0 +1 @@ +- Prepare graphics and audio numeric guards to admit ints through the unchanged bridge. Pin the audio corpus program to SDL's dummy driver so inherited disk audio settings cannot create sdlaudio.raw in the source tree. diff --git a/changes/internal/1758-phase2-value-layer.md b/changes/internal/1758-phase2-value-layer.md new file mode 100644 index 000000000..e7c31a985 --- /dev/null +++ b/changes/internal/1758-phase2-value-layer.md @@ -0,0 +1 @@ +- #1758 Phase 2 round R1.2, the Value layer: the number Value type is renamed (`VAL_FLOAT`), an exact int64 Value type (`VAL_INT`) exists, and every switch and type test over a value's type decides what an int does there; nothing produces an int yet (R1.3), so no program behaves differently. diff --git a/changes/internal/1758-phase2-value-readers.md b/changes/internal/1758-phase2-value-readers.md new file mode 100644 index 000000000..7e88004bc --- /dev/null +++ b/changes/internal/1758-phase2-value-readers.md @@ -0,0 +1 @@ +- Complete the allocation-free int readers: SMI type and Value views preserve VAL_INT and exact digits, and slot_int_value extracts boxed int64 values without converting through double. diff --git a/changes/internal/1758-sync5.md b/changes/internal/1758-sync5.md new file mode 100644 index 000000000..5627a83f6 --- /dev/null +++ b/changes/internal/1758-sync5.md @@ -0,0 +1 @@ +- Merge main through 88a1c911 into Phase2: combine integer-kind and chunked-source tapes as v8, retain side-effect-free replay, remove the rm alias, and register set_exit_code for R1.6c (R1.6-S1/S2). diff --git a/changes/internal/1848-stdlib-real-valued-literals.md b/changes/internal/1848-stdlib-real-valued-literals.md new file mode 100644 index 000000000..7c5242f96 --- /dev/null +++ b/changes/internal/1848-stdlib-real-valued-literals.md @@ -0,0 +1,2 @@ +- No entry: `rest_energy`, `poisson_pmf`/`poisson_cdf` and `fmt_num`/`fmt_percent` keep computing in floats + under #1758's unreleased int literals, so they answer as the last release does (#1848). diff --git a/changes/internal/1863-container-get-ref-comments.md b/changes/internal/1863-container-get-ref-comments.md new file mode 100644 index 000000000..909f18dbf --- /dev/null +++ b/changes/internal/1863-container-get-ref-comments.md @@ -0,0 +1 @@ +- Comments only: `src/container.h` states that `list_get_ref` and `dict_value_get_ref` return NULL for a double or an SMI, and a boxed int's `VAL_INT` Value otherwise, and that container storage holds boxed ints outside the SMI range (#1863). No behaviour changes. diff --git a/changes/internal/2120-seeded-arms.md b/changes/internal/2120-seeded-arms.md new file mode 100644 index 000000000..c331f46a3 --- /dev/null +++ b/changes/internal/2120-seeded-arms.md @@ -0,0 +1 @@ +- `jit_diff` runs every arm with one `EIGS_SEED_RANDOM`, so a program that draws a random value is compared like any other instead of reading as a divergence about 1 run in 8; this replaces the temporary two-file waiver, and `test_chunk_verify_stack.eigs`, which a replay-first rule would have masked, pins the OBS row (#2120). diff --git a/changes/internal/2122-gate-safe-jit-diff.md b/changes/internal/2122-gate-safe-jit-diff.md new file mode 100644 index 000000000..5fc157976 --- /dev/null +++ b/changes/internal/2122-gate-safe-jit-diff.md @@ -0,0 +1 @@ +- `jit_diff` now ledgers unstable comparisons that trace/replay cannot safely adjudicate without changing the observer gate, while allowing such a pair to agree on a later run (#2122). diff --git a/codspeed.yml b/codspeed.yml index b98c34d67..3691509b2 100644 --- a/codspeed.yml +++ b/codspeed.yml @@ -28,13 +28,13 @@ # make && codspeed run -m simulation --cycle-estimation=false # every target # (CLI 5.3.1 has no per-target selector; the ids exist for the report only) # -# Workloads: bench/*.eigs are the five Ir-gate workloads; the three tests/ -# benches are the JIT's own shapes (CLAUDE.md "Benchmarks"), dmg_shape being -# the consumer stand-in for the DMG emulator's hot loop. +# In-repo workloads: every bench/*.eigs and tests/bench_*.eigs file. +# tools/phase2_cost.sh reads this same set and measures each in both tiers. +# dmg_shape is the consumer stand-in for the DMG emulator's hot loop. $schema: https://raw.githubusercontent.com/CodSpeedHQ/codspeed/refs/heads/main/schemas/codspeed.schema.json benchmarks: - # --- the five Ir-gate workloads (bench/) --- + # --- the Ir-gate workloads (bench/) --- - name: "scalar_loop" id: scalar-loop exec: ./src/eigenscript bench/scalar_loop.eigs @@ -54,7 +54,17 @@ benchmarks: id: unobserved-armed-loop exec: ./src/eigenscript bench/unobserved_armed_loop.eigs - # --- the JIT's own shapes (tests/) --- + - name: "alloc_heavy" + id: alloc-heavy + exec: ./src/eigenscript bench/alloc_heavy.eigs + - name: "conservative_observed_loop" + id: conservative-observed-loop + exec: ./src/eigenscript bench/conservative_observed_loop.eigs + - name: "conservative_unobserved_loop" + id: conservative-unobserved-loop + exec: ./src/eigenscript bench/conservative_unobserved_loop.eigs + + # --- runtime and JIT workloads (tests/) --- - name: "bench_perf" id: bench-perf exec: ./src/eigenscript tests/bench_perf.eigs @@ -65,6 +75,16 @@ benchmarks: id: idxset exec: ./src/eigenscript tests/bench_idxset.eigs + - name: "bench_buf_mix" + id: buf-mix + exec: ./src/eigenscript tests/bench_buf_mix.eigs + - name: "bench_buf_resample" + id: buf-resample + exec: ./src/eigenscript tests/bench_buf_resample.eigs + - name: "bench_pcm_codec" + id: pcm-codec + exec: ./src/eigenscript tests/bench_pcm_codec.eigs + # --- a REAL consumer: the DMG Game Boy emulator running Blargg's cpu_instrs --- # The in-repo dmg_shape bench flatters the JIT ~10x against the emulator it # names (measured 2026-09-21 under cachegrind: JIT removes 48% of the diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 226a2e8d9..f28a4509a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -218,9 +218,10 @@ EigenScript uses reference counting plus numeric and environment freelists. (`__ATOMIC_RELAXED` increment, `__ATOMIC_ACQ_REL` decrement). When the refcount reaches zero, `free_value` tears down the value and its children. -**Numeric freelist.** Freed `VAL_NUM` values are placed in a per-thread -freelist (up to 4096 entries) and reused by `make_num`, avoiding -malloc/free churn in arithmetic-heavy loops. +**Numeric freelist.** Freed `VAL_FLOAT` values are placed in a per-thread +freelist (up to 4096 entries) and reused by `make_float`, avoiding +malloc/free churn in arithmetic-heavy loops. `VAL_INT` boxes are freed +directly; immediate floats and SMIs need no heap reference count. **Environment freelist.** Freed `Env` structs are cached per-thread (up to 1024 entries) and reused by `env_new`, avoiding allocation in tight diff --git a/docs/BUILTINS.md b/docs/BUILTINS.md index a83d4d3c8..1cf631b18 100644 --- a/docs/BUILTINS.md +++ b/docs/BUILTINS.md @@ -45,13 +45,21 @@ audio (`audio_open`, `audio_close`, `audio_pause`, `audio_play`, `audio_capture_close`, `audio_stream_open`, `audio_stream_push`, `audio_stream_queued`, `audio_stream_clear`, `audio_stream_close`), and +N6 prints each float with Python's shortest round-trip spelling: integral +floats retain `.0`, negative zero is `-0.0`, and scientific notation starts +below `1e-4` or at `1e16`. Ints keep their exact decimal digits. `print`, +`write`, `str`, f-strings and JSON share the number writer. The executed +examples are `tests/phase2/rows/N6.float-repr.eigs` and +`tests/phase2/rows/N6.str-fstring.eigs`. + + ## Runtime status and control | Function | Usage | Description | |---|---|---| | `math_flags` | `math_flags of null` | Return sticky `overflow`, `invalid`, and `underflow` numeric-status booleans. | | `must_not_yield` | `must_not_yield of fn` | Run a callback while rejecting cooperative suspension. | -| `inflate` | `inflate of bytes` | Decode raw DEFLATE bytes when zlib support is enabled. | +| `inflate` | `inflate of bytes` | Input bytes follow N5, then 0..255; output byte values are ints. Decode raw DEFLATE bytes when zlib support is enabled. | ## Core Language @@ -60,43 +68,54 @@ audio (`audio_open`, `audio_close`, `audio_pause`, `audio_play`, | Name | Signature | Description | |------|-----------|-------------| | `print` | `print of value` | Output value to stdout with newline | -| `len` | `len of value` | Length of string or list count | +| `len` | `len of value` | Exact int length of string, list, dict, buffer or text builder | | `str` | `str of value` | Convert to string representation | -| `num` | `num of value` | Convert to number (parse string or coerce). `num of "inf"` saturates to `1e308`; `num of "nan"` raises a `value` error naming `num` by default, while under `EIGS_STRICT=0` it is `0` and sets `math_flags.invalid` (#971). | +| `num` | `num of value` | Parse complete numeric-literal text (N10): decimal integer text fitting int64 and wrapped hex integers give ints; a point/exponent or decimal overflow gives a float. Optional sign and surrounding ASCII whitespace are accepted. Partial/empty text, separators, 0b/0o, hex floats, inf and nan raise `value` in every strict mode. Numbers retain their kind; existing non-text coercions remain. | | `type` | `type of value` | Return type name: "num", "str", "list", "dict", "buffer", "text_builder", "fn", "builtin", "none" (the null value — SPEC.md is normative and its gated example prints `none`; the string `"null"` is never produced) | +| `num_type` | `num_type of value` | Return "int" for an int64, "float" for a binary64, or `null` for any other value. Both numeric kinds still have `type of` "num" (#1758 N7). | | `assert` | `assert of [cond, msg]` | Raise catchable error `"ASSERT FAIL: "` if condition is false | -| `exit` | `exit of N` | Terminate the program with exit code `N` (default 0). **Uncatchable** — a `try`/`catch` does not intercept it — and unwinds through normal teardown, so it is leak-clean even with live closures. Code after it does not run. The request is scoped to the evaluating thread and cleared at each host eval entry, so under the embedding API a script that calls `exit` does not disable `try`/`catch` for the host's *next* eval (#739). | -| `set_exit_code` | `set_exit_code of N` | Set the status (integer 0..255) used when the process ends gracefully, without ending it. The last call wins. Explicit `exit` and runtime failures retain their own status. | +| `exit` | `exit of N` | Terminate the program with exit code `N` (default 0). **Uncatchable** — a `try`/`catch` does not intercept it — and unwinds through normal teardown, so it is leak-clean even with live closures. Code after it does not run. The request is scoped to the evaluating thread and cleared at each host eval entry, so under the embedding API a script that calls `exit` does not disable `try`/`catch` for the host's *next* eval (#739). A supplied numeric code follows N5, then the C int range; invalid codes raise catchably before requesting exit. | +| `set_exit_code` | `set_exit_code of N` | Set the status (integer 0..255) used when the process ends gracefully, without ending it. The last call wins. Explicit `exit` and runtime failures retain their own status. Integral floats are accepted; fractions raise `value` (N5). | | `coalesce` | `coalesce of [value, default]` | Return value unless empty/null, else default | | `eval` | `eval of code_string` | Execute EigenScript code, return result | | `throw` | `throw of message` | Raise catchable error | Numeric values are finite by construction. `NaN` collapses to `0`, and -overflow or infinity saturates at `+/-1e308`. This applies to numeric -literals, `num` conversion, scalar arithmetic, tensor arithmetic, and the +overflow or infinity saturates at `+/-1e308`. This applies to float-producing numeric +literals, scalar arithmetic, tensor arithmetic, and the numeric fast paths used by reassignment and `unobserved` blocks. +Counts, byte codes and indices (`len`, `ord`, `index_of`, `list_index_of`), +`range` elements, channel/thread handle id and generation fields, `spawn`'s +numeric `done` flag, and task ids (`task_spawn`, `task_self`) are ints. +`task_sched_seed` accepts an exact int64 seed by N5; no double conversion +loses its low bits. `scan_ints` returns exact int64 tokens, skips non-integer +tokens, and raises `value` naming any integer token outside int64. +`abs` preserves its input kind and raises at INT64_MIN. Numeric sorting and +`sort_by` retain exact int64 ordering. + ### Lists | Name | Signature | Description | |------|-----------|-------------| | `append` | `append of [list, item]` | Append item to list (mutates list) | | `concat` | `concat of [a, b]` | Concatenate two lists into new list | -| `range` | `range of n` or `range of [start, end]` | Generate integer list [0..n) or [start..end) | -| `set_at` | `set_at of [list, index, value]` | Set element at index (mutates list); negative indices count from the end, like `[]`. Also takes a **buffer** in the first position — `set_at of [buf, i, v]`, or `set_at of [shaped_buf, row, col, v]` — where a non-number value is refused (`cannot store str in a buffer`) | -| `get_at` | `get_at of [list, index]` | Get element at index; negative indices count from the end, like `[]`. Also takes a **buffer**: `get_at of [buf, i]`, or `get_at of [shaped_buf, row, col]` | -| `copy_into` | `copy_into of [dest, offset, src]` | Copy src elements into dest starting at offset — a list into a list, or a buffer / list of numbers into a buffer. Returns dest. Wrong arity, a non-number offset (the doc used to list `[dest, src, offset]`, the code has always read `[dest, offset, src]`), a bad type, or a non-number element bound for a buffer RAISES (#1069; it silently returned null) | -| `list_slice` | `list_slice of [list, start, end]` | New list with the elements of [start, end) — dual of `copy_into`. Negative indices count from the end, like `[]`; bounds then clamp to [0, len]. `start >= end` gives `[]`. Never raises on bounds | -| `num_copy` | `num_copy of value` | Create independent copy of numeric value | -| `hex` | `hex of n` or `hex of [n, nibbles]` | Uppercase hex string of a non-negative integer, zero-padded to `nibbles` (never truncated). Raises on negatives, fractions, non-numbers | +| `range` | `range of n` or `range of [start, end]` | Generate int elements for [0..n), [start..end), or [start..end) by step. Every bound/step follows N5: integral floats convert, fractions raise `value`, then C int bounds and the existing element cap apply. | +| `set_at` | `set_at of [list, index, value]` | Set element at index (mutates list); negative indices count from the end, like `[]`. Also takes a **buffer** in the first position — `set_at of [buf, i, v]`, or `set_at of [shaped_buf, row, col, v]` — where a non-number value is refused (`cannot store str in a buffer`). Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `get_at` | `get_at of [list, index]` | Get element at index; negative indices count from the end, like `[]`. Also takes a **buffer**: `get_at of [buf, i]`, or `get_at of [shaped_buf, row, col]`. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). Buffer elements must be finite; non-finite cells raise `value` in every strict mode (N13). | +| `copy_into` | `copy_into of [dest, offset, src]` | Copy src elements into dest starting at offset — a list into a list, or a buffer / list of numbers into a buffer. Returns dest. Wrong arity, a non-number offset (the doc used to list `[dest, src, offset]`, the code has always read `[dest, offset, src]`), a bad type, or a non-number element bound for a buffer RAISES (#1069; it silently returned null). Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `list_slice` | `list_slice of [list, start, end]` | New list with the elements of [start, end) — dual of `copy_into`. Negative indices count from the end, like `[]`; bounds then clamp to [0, len]. `start >= end` gives `[]`. Clamped integer bounds remain valid. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `num_copy` | `num_copy of value` | Create an independent numeric copy, preserving int/float kind and every int64 bit | +| `hex` | `hex of n` or `hex of [n, nibbles]` | Exact uppercase hex digits for any non-negative int64. Value and optional nibble count follow N5; fractions raise, integral floats convert. Width clamps to 0..16, never truncating digits. | | `sort` | `sort of list` | Stably sort an all-number or all-string list in-place (numeric / lexicographic); equal elements keep their input order on every platform. Mixed or non-scalar elements raise — use `sort_by` for records. Returns the list. A non-list argument raises by default; under `EIGS_STRICT=0` it is handed back unchanged (#971). | -| `list_truncate` | `list_truncate of [list, new_len]` | Shrink list in-place to new_len items. No-op if new_len >= length. Returns the list | -| `list_remove_at` | `list_remove_at of [list, index]` | Remove element at index, shift tail down (mutates). No-op if out of bounds. Returns the list | -| `list_insert_at` | `list_insert_at of [list, index, value]` | Insert value at index, shift tail up (mutates) — dual of `list_remove_at`. `index == len` appends; any other out-of-bounds index is a no-op. Returns the list | -| `list_index_of` | `list_index_of of [list, value]` | Index of the first element structurally equal to `value` (the same comparison `==` uses — nested lists and dicts match by structure), -1 if none | +| `list_truncate` | `list_truncate of [list, new_len]` | Shrink list in-place to new_len items. No-op if new_len >= length. Returns the list. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `list_remove_at` | `list_remove_at of [list, index]` | Remove element at index, shift tail down (mutates). No-op if out of bounds. Returns the list. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `list_insert_at` | `list_insert_at of [list, index, value]` | Insert value at index, shift tail up (mutates) — dual of `list_remove_at`. `index == len` appends; any other out-of-bounds index is a no-op. Returns the list. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | +| `list_index_of` | `list_index_of of [list, value]` | First structural match index as an int, or int -1 when absent; exact numeric equality (N2). | | `list_contains` | `list_contains of [list, value]` | `true` if any element structurally equals `value`, else `false` — the list counterpart of the string-only `contains` | | `sort_by` | `sort_by of [list, key_fn]` | Stably sort list by numeric keys from key_fn, evaluated once per element; equal keys keep their input order on every platform. Returns a new sorted list | | `dispatch` | `dispatch of [table, key, arg]` | Index list `table` by numeric `key` and call the resulting function with `arg` — a jump table (mirrors the `OP_DISPATCH` fast path). `key` must be a number. An ordinary builtin, not a special form: a user binding of the name wins (#459 — the fast path steps aside for any unit that rebinds `dispatch`, references `eval`, or compiles against an env where it is already rebound), and the parenthesized `dispatch of ([t, k, a])` form is one argument per #355/#405 | +| `fill` | `fill of [count, value]` | Construct a list of repeated values. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | ### Strings @@ -104,14 +123,14 @@ numeric fast paths used by reassignment and `unobserved` blocks. |------|-----------|-------------| | `str_lower` | `str_lower of s` | Convert to lowercase | | `str_upper` | `str_upper of s` | Convert to uppercase | -| `char_at` | `char_at of [s, index]` | Single character at index as string ("" if out of range); negative indices count from the end, like `[]` | +| `char_at` | `char_at of [s, index]` | Single character at index as string ("" if out of range); negative indices count from the end, like `[]`. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | | `contains` | `contains of [haystack, needle]` | `true` if haystack contains needle, else `false` (non-string operands are `false`, never a spurious match) | | `starts_with` | `starts_with of [s, prefix]` | `true` if s starts with prefix, else `false` | | `ends_with` | `ends_with of [s, suffix]` | `true` if s ends with suffix, else `false` | -| `index_of` | `index_of of [haystack, needle]` | First index of needle in haystack, or -1 (non-string operands are -1) | -| `substr` | `substr of [s, start, length]` | Extract substring | +| `index_of` | `index_of of [haystack, needle]` | First byte index as an int, or int -1 when absent (legacy non-string fallback is -1) | +| `substr` | `substr of [s, start, length]` | Extract substring. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | | `split` | `split of [s, delim]` | Split string by delimiter into list. A non-string `s` or `delim` raises by default; under `EIGS_STRICT=0`, `s` splits as `""` (so answers `[""]`) and `delim` falls back to `" "` (#971). | -| `scan_int_tokens` | `scan_int_tokens of s` or `scan_int_tokens of [s, comment_marker]` | Token rows `[text, line, col, start, end, is_int, value]` | +| `scan_int_tokens` | `scan_int_tokens of s` or `scan_int_tokens of [s, comment_marker]` | Token rows `[text, line, col, start, end, is_int, value]`; all numeric fields are ints. Integer text beyond int64 raises `value` naming the token (C3). | | `trim` | `trim of s` | Strip leading/trailing whitespace | | `str_replace` | `str_replace of [s, old, new]` | Replace all occurrences of old with new | | `chr` | `chr of byte` | One-byte string from a byte value 1–255 (the writing inverse of `ord`). Raises outside 1–255 — including 0, since strings are NUL-terminated — and on fractions; for a Unicode *codepoint* use `utf8_encode` (lib/utf8.eigs). | @@ -154,7 +173,103 @@ builtin-call forms below are retained for backward compatibility. | `bit_not` | `bit_not of x` | Bitwise NOT (prefer `~x`) | | `bit_shl` | `bit_shl of [a, b]` | Left shift (prefer `a << b`) | | `bit_shr` | `bit_shr of [a, b]` | Unsigned right shift (prefer `a >> b`) | -| `sign_extend` | `sign_extend of [val, bits]` | Sign-extend val from given bit width. E.g. `sign_extend of [0xFF, 8]` returns -1 | +| `sign_extend` | `sign_extend of [val, bits]` | Exact int64 result from the existing 1..32-bit sign-extension operation; value and width follow N5 (integral floats convert, fractions raise). Its subtraction is checked: int64 underflow raises `value`, naming `sign_extend` (R1.6b-D1). Bits above the width retain the existing behavior (#1896 is separate). | + +### Width arithmetic (N9) + +Binary width operations take exactly `[a, b]`. Integral floats convert by N5; +fractions, floats outside int64 and operands outside the named type raise +`value`. Wrong shapes and non-number operands raise `type_mismatch` in both +strict modes. There is no operand masking. `i64` and `u64` accept every int64; +a `u64` uses its int64 bit pattern (`-1` is unsigned max). Wrapping reduces +modulo the width; checked overflow raises `value` naming the builtin; +saturating clamps to the type range. Width results are ints. Unsigned division +and remainder by zero raise `value` with `division by zero`. + +`u64_to_string` and `fround` are unary and accept the usual one-element list +form. `fround` converts an int to double first, then through float32 and back +to double. Until R2.2, float32 overflow follows the current float-result guard +(clamp at ±1e308 and set `math_flags.overflow`). Rust std is the numeric oracle. + +| Function | Usage | Description | +|---|---|---| +| `i8_wrapping_add` | `i8_wrapping_add of [a, b]` | N9: i8 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `i8_wrapping_sub` | `i8_wrapping_sub of [a, b]` | N9: i8 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `i8_wrapping_mul` | `i8_wrapping_mul of [a, b]` | N9: i8 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `i8_checked_add` | `i8_checked_add of [a, b]` | N9: i8 checked add; N5 then type range without masking; exact int result (Rust std). | +| `i8_checked_sub` | `i8_checked_sub of [a, b]` | N9: i8 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `i8_checked_mul` | `i8_checked_mul of [a, b]` | N9: i8 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `i8_saturating_add` | `i8_saturating_add of [a, b]` | N9: i8 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `i8_saturating_sub` | `i8_saturating_sub of [a, b]` | N9: i8 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `i8_saturating_mul` | `i8_saturating_mul of [a, b]` | N9: i8 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `i16_wrapping_add` | `i16_wrapping_add of [a, b]` | N9: i16 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `i16_wrapping_sub` | `i16_wrapping_sub of [a, b]` | N9: i16 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `i16_wrapping_mul` | `i16_wrapping_mul of [a, b]` | N9: i16 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `i16_checked_add` | `i16_checked_add of [a, b]` | N9: i16 checked add; N5 then type range without masking; exact int result (Rust std). | +| `i16_checked_sub` | `i16_checked_sub of [a, b]` | N9: i16 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `i16_checked_mul` | `i16_checked_mul of [a, b]` | N9: i16 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `i16_saturating_add` | `i16_saturating_add of [a, b]` | N9: i16 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `i16_saturating_sub` | `i16_saturating_sub of [a, b]` | N9: i16 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `i16_saturating_mul` | `i16_saturating_mul of [a, b]` | N9: i16 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `i32_wrapping_add` | `i32_wrapping_add of [a, b]` | N9: i32 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `i32_wrapping_sub` | `i32_wrapping_sub of [a, b]` | N9: i32 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `i32_wrapping_mul` | `i32_wrapping_mul of [a, b]` | N9: i32 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `i32_checked_add` | `i32_checked_add of [a, b]` | N9: i32 checked add; N5 then type range without masking; exact int result (Rust std). | +| `i32_checked_sub` | `i32_checked_sub of [a, b]` | N9: i32 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `i32_checked_mul` | `i32_checked_mul of [a, b]` | N9: i32 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `i32_saturating_add` | `i32_saturating_add of [a, b]` | N9: i32 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `i32_saturating_sub` | `i32_saturating_sub of [a, b]` | N9: i32 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `i32_saturating_mul` | `i32_saturating_mul of [a, b]` | N9: i32 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `i64_wrapping_add` | `i64_wrapping_add of [a, b]` | N9: i64 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `i64_wrapping_sub` | `i64_wrapping_sub of [a, b]` | N9: i64 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `i64_wrapping_mul` | `i64_wrapping_mul of [a, b]` | N9: i64 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `i64_checked_add` | `i64_checked_add of [a, b]` | N9: i64 checked add; N5 then type range without masking; exact int result (Rust std). | +| `i64_checked_sub` | `i64_checked_sub of [a, b]` | N9: i64 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `i64_checked_mul` | `i64_checked_mul of [a, b]` | N9: i64 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `i64_saturating_add` | `i64_saturating_add of [a, b]` | N9: i64 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `i64_saturating_sub` | `i64_saturating_sub of [a, b]` | N9: i64 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `i64_saturating_mul` | `i64_saturating_mul of [a, b]` | N9: i64 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `u8_wrapping_add` | `u8_wrapping_add of [a, b]` | N9: u8 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `u8_wrapping_sub` | `u8_wrapping_sub of [a, b]` | N9: u8 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `u8_wrapping_mul` | `u8_wrapping_mul of [a, b]` | N9: u8 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `u8_checked_add` | `u8_checked_add of [a, b]` | N9: u8 checked add; N5 then type range without masking; exact int result (Rust std). | +| `u8_checked_sub` | `u8_checked_sub of [a, b]` | N9: u8 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `u8_checked_mul` | `u8_checked_mul of [a, b]` | N9: u8 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `u8_saturating_add` | `u8_saturating_add of [a, b]` | N9: u8 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `u8_saturating_sub` | `u8_saturating_sub of [a, b]` | N9: u8 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `u8_saturating_mul` | `u8_saturating_mul of [a, b]` | N9: u8 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `u16_wrapping_add` | `u16_wrapping_add of [a, b]` | N9: u16 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `u16_wrapping_sub` | `u16_wrapping_sub of [a, b]` | N9: u16 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `u16_wrapping_mul` | `u16_wrapping_mul of [a, b]` | N9: u16 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `u16_checked_add` | `u16_checked_add of [a, b]` | N9: u16 checked add; N5 then type range without masking; exact int result (Rust std). | +| `u16_checked_sub` | `u16_checked_sub of [a, b]` | N9: u16 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `u16_checked_mul` | `u16_checked_mul of [a, b]` | N9: u16 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `u16_saturating_add` | `u16_saturating_add of [a, b]` | N9: u16 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `u16_saturating_sub` | `u16_saturating_sub of [a, b]` | N9: u16 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `u16_saturating_mul` | `u16_saturating_mul of [a, b]` | N9: u16 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `u32_wrapping_add` | `u32_wrapping_add of [a, b]` | N9: u32 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `u32_wrapping_sub` | `u32_wrapping_sub of [a, b]` | N9: u32 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `u32_wrapping_mul` | `u32_wrapping_mul of [a, b]` | N9: u32 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `u32_checked_add` | `u32_checked_add of [a, b]` | N9: u32 checked add; N5 then type range without masking; exact int result (Rust std). | +| `u32_checked_sub` | `u32_checked_sub of [a, b]` | N9: u32 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `u32_checked_mul` | `u32_checked_mul of [a, b]` | N9: u32 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `u32_saturating_add` | `u32_saturating_add of [a, b]` | N9: u32 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `u32_saturating_sub` | `u32_saturating_sub of [a, b]` | N9: u32 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `u32_saturating_mul` | `u32_saturating_mul of [a, b]` | N9: u32 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `u64_wrapping_add` | `u64_wrapping_add of [a, b]` | N9: u64 wrapping add; N5 then type range without masking; exact int result (Rust std). | +| `u64_wrapping_sub` | `u64_wrapping_sub of [a, b]` | N9: u64 wrapping sub; N5 then type range without masking; exact int result (Rust std). | +| `u64_wrapping_mul` | `u64_wrapping_mul of [a, b]` | N9: u64 wrapping mul; N5 then type range without masking; exact int result (Rust std). | +| `u64_checked_add` | `u64_checked_add of [a, b]` | N9: u64 checked add; N5 then type range without masking; exact int result (Rust std). | +| `u64_checked_sub` | `u64_checked_sub of [a, b]` | N9: u64 checked sub; N5 then type range without masking; exact int result (Rust std). | +| `u64_checked_mul` | `u64_checked_mul of [a, b]` | N9: u64 checked mul; N5 then type range without masking; exact int result (Rust std). | +| `u64_saturating_add` | `u64_saturating_add of [a, b]` | N9: u64 saturating add; N5 then type range without masking; exact int result (Rust std). | +| `u64_saturating_sub` | `u64_saturating_sub of [a, b]` | N9: u64 saturating sub; N5 then type range without masking; exact int result (Rust std). | +| `u64_saturating_mul` | `u64_saturating_mul of [a, b]` | N9: u64 saturating mul; N5 then type range without masking; exact int result (Rust std). | +| `u64_compare` | `u64_compare of [a, b]` | N9: N5 int64 bit patterns; unsigned order gives int -1, 0 or 1 (Rust std). | +| `u64_div` | `u64_div of [a, b]` | N9: N5 int64 bit patterns; unsigned quotient as int64 bits; zero raises value (Rust std). | +| `u64_rem` | `u64_rem of [a, b]` | N9: N5 int64 bit patterns; unsigned remainder as int64 bits; zero raises value (Rust std). | +| `u64_to_string` | `u64_to_string of x` | N9: N5 int64 bit pattern to unsigned decimal string (Rust std). | +| `fround` | `fround of x` | N9: int converts to f64 first; f64 to f32 to f64, float result; current result guard until R2.2. | ### Buffers @@ -163,33 +278,34 @@ Compact typed arrays of doubles with O(1) indexed access. Iterable with | Name | Signature | Description | |------|-----------|-------------| -| `buffer` | `buffer of count` | Create zero-filled buffer of given size (or `buffer of [rows, cols]` for a shaped one). A non-numeric size raises by default; under `EIGS_STRICT=0` it makes an empty buffer (#971). | -| `buf_get` | `buf_get of [buf, index]` | Read element; out-of-range raises `index_range` (#502 — folding to 0 was indistinguishable from a real stored 0), matching the `buf[i]` operator | -| `buf_set` | `buf_set of [buf, index, value]` | Write element | -| `buf_len` | `buf_len of buf` | Return buffer element count | -| `buf_from_list` | `buf_from_list of list` | Convert numeric list to buffer | -| `buf_copy` | `buf_copy of [src, src_off, dst, dst_off, count]` | Bulk copy between buffers (`memmove`, overlap-safe). Out-of-range windows / negative count raise (`index_range`/`value`); count 0 is a no-op | -| `buf_mix` | `buf_mix of [dst, src, dst_off, src_off, count, gain]` | In-place mix: `dst[dst_off+i] += src[src_off+i] * gain` over the window — the audio mix-down kernel (#597). VM-identical arithmetic (byte-equal to the interpreted loop); same-buffer overlap runs forward in index order. Bad windows raise | -| `buf_scale_range` | `buf_scale_range of [b, off, count, gain]` | In-place multiply over a window: `b[off+i] *= gain` (fades, normalize). Bad windows raise | -| `buf_fill` | `buf_fill of [b, off, count, value]` | Bulk store over a window: `b[off+i] = value`. Bad windows raise | -| `buf_peak` | `buf_peak of [b, off, count]` | Max absolute value over a window (normalize/meter scans); 0 for an empty window. Bad windows raise | -| `buf_dot` | `buf_dot of [a, b, a_off, b_off, count]` | Windowed dot product: sum of `a[a_off+i] * b[b_off+i]` (YIN autocorrelation). Like `dot`, summation order/association is **unspecified** (backends may reassociate); no-NaN/Inf preserved. Bad windows raise | -| `buf_from_pcm16le` | `buf_from_pcm16le of [bytes, byte_off, count]` | Decode `count` little-endian signed 16-bit PCM samples starting at `byte_off` into a NEW float buffer: `v = b0 + 256*b1`, two's-complement fold at 32768, `v / 32767` — bit-identical to the interpreted per-sample loop it replaces (#602, the WAV-import kernel). Bad windows raise | -| `buf_to_pcm16le` | `buf_to_pcm16le of [floats, off, count]` | Encode `count` samples into a NEW byte buffer (2 bytes/sample, LE): clamp to [-1, 1], `round(x*32767)`, two's complement via +65536 — bit-identical to the interpreted write loop (#602). Bad windows raise | -| `buf_deinterleave` | `buf_deinterleave of [src, channel, nch, count?]` | Every `nch`-th sample starting at index `channel` into a NEW buffer (interleaved channel split). `count` defaults to the full available tail; bad `channel`/`nch` raise `value`, count overrun raises `index_range` (#602) | -| `buf_resample_linear` | `buf_resample_linear of [src, dst_len]` | Endpoint-inclusive **linear** resample into a NEW buffer: `pos = i*(n-1)/(dst_len-1)`, lerp between the floor/ceil neighbors — bit-identical to DeslanStudio's interpreted `ab_resample_linear` (#603). The kernel is linear interpolation, **not** Fourier/sinc resampling (the consumer-documented divergence from `scipy.signal.resample`; no anti-aliasing). `dst_len` 0 → empty buffer; empty `src` with `dst_len > 0` raises `value` | -| `read_bytes_buf` | `read_bytes_buf of path` / `read_bytes_buf of [path, max_bytes]` | Read binary file as a buffer. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. An over-cap `io` error names the size, active cap, and `[path, max_bytes]` escape. (A buffer stores one double per byte, so the ceiling bounds worst-case amplification.) Missing, unreadable, and directory paths raise `io` with the path and OS reason; an empty file returns an empty buffer | -| `write_bytes` | `write_bytes of [path, {, append}]` | Write raw bytes to a file. Binary-clean (NUL written verbatim, unlike `write_text`). `append` (default 0): 0 truncates, nonzero appends. Returns bytes written; I/O failure raises `io` with the path and OS reason. Trace-recorded result. | +| `buffer` | `buffer of count` | Dimensions and size follow N5, then the C int range. Create zero-filled buffer of given size (or `buffer of [rows, cols]` for a shaped one). A non-numeric size raises by default; under `EIGS_STRICT=0` it makes an empty buffer (#971). | +| `buf_get` | `buf_get of [buf, index]` | The index follows N5; the returned element is float. Read element; out-of-range raises `index_range` (#502 — folding to 0 was indistinguishable from a real stored 0), matching the `buf[i]` operator | +| `buf_set` | `buf_set of [buf, index, value]` | The index follows N5; stored values convert to float (Q2). Write element | +| `buf_len` | `buf_len of buf` | Returns an integer count. Return buffer element count | +| `buf_from_list` | `buf_from_list of list` | Integer cells convert to float storage (Q2). Convert numeric list to buffer | +| `buf_copy` | `buf_copy of [src, src_off, dst, dst_off, count]` | Offsets/counts follow N5; fractions raise `value`. Bulk copy between buffers (`memmove`, overlap-safe). Out-of-range windows / negative count raise (`index_range`/`value`); count 0 is a no-op | +| `buf_mix` | `buf_mix of [dst, src, dst_off, src_off, count, gain]` | Offsets/counts follow N5; fractions raise `value`. In-place mix: `dst[dst_off+i] += src[src_off+i] * gain` over the window — the audio mix-down kernel (#597). VM-identical arithmetic (byte-equal to the interpreted loop); same-buffer overlap runs forward in index order. Bad windows raise | +| `buf_scale_range` | `buf_scale_range of [b, off, count, gain]` | Offsets/counts follow N5; fractions raise `value`. In-place multiply over a window: `b[off+i] *= gain` (fades, normalize). Bad windows raise | +| `buf_fill` | `buf_fill of [b, off, count, value]` | Offsets/counts follow N5; fractions raise `value`. Bulk store over a window: `b[off+i] = value`. Bad windows raise | +| `buf_peak` | `buf_peak of [b, off, count]` | Offsets/counts follow N5; fractions raise `value`. Max absolute value over a window (normalize/meter scans); 0 for an empty window. Bad windows raise | +| `buf_dot` | `buf_dot of [a, b, a_off, b_off, count]` | Offsets/counts follow N5; fractions raise `value`. Windowed dot product: sum of `a[a_off+i] * b[b_off+i]` (YIN autocorrelation). Like `dot`, summation order/association is **unspecified** (backends may reassociate); no-NaN/Inf preserved. Bad windows raise | +| `buf_from_pcm16le` | `buf_from_pcm16le of [bytes, byte_off, count]` | Input bytes follow N5, then 0..255. Offsets/counts follow N5. Decode `count` little-endian signed 16-bit PCM samples starting at `byte_off` into a NEW float buffer: `v = b0 + 256*b1`, two's-complement fold at 32768, `v / 32767` — bit-identical to the interpreted per-sample loop it replaces (#602, the WAV-import kernel). Bad windows raise | +| `buf_to_pcm16le` | `buf_to_pcm16le of [floats, off, count]` | Offsets/counts follow N5. Encode `count` samples into a NEW byte buffer (2 bytes/sample, LE): clamp to [-1, 1], `round(x*32767)`, two's complement via +65536 — bit-identical to the interpreted write loop (#602). Bad windows raise | +| `buf_deinterleave` | `buf_deinterleave of [src, channel, nch, count?]` | Channel/count arguments follow N5. Every `nch`-th sample starting at index `channel` into a NEW buffer (interleaved channel split). `count` defaults to the full available tail; bad `channel`/`nch` raise `value`, count overrun raises `index_range` (#602) | +| `buf_resample_linear` | `buf_resample_linear of [src, dst_len]` | Destination length follows N5. Endpoint-inclusive **linear** resample into a NEW buffer: `pos = i*(n-1)/(dst_len-1)`, lerp between the floor/ceil neighbors — bit-identical to DeslanStudio's interpreted `ab_resample_linear` (#603). The kernel is linear interpolation, **not** Fourier/sinc resampling (the consumer-documented divergence from `scipy.signal.resample`; no anti-aliasing). `dst_len` 0 → empty buffer; empty `src` with `dst_len > 0` raises `value` | +| `read_bytes_buf` | `read_bytes_buf of path` / `read_bytes_buf of [path, max_bytes]` | Read binary file as a buffer. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. An over-cap `io` error names the size, active cap, and `[path, max_bytes]` escape. (A buffer stores one double per byte, so the ceiling bounds worst-case amplification.) Missing, unreadable, and directory paths raise `io` with the path and OS reason; an empty file returns an empty buffer. The cap follows N5: integral floats convert; fractions raise `value`. Byte data remains float buffer storage (Q2). | +| `write_bytes` | `write_bytes of [path, {, append}]` | Write raw bytes to a file. Binary-clean (NUL written verbatim, unlike `write_text`). `append` (default 0): 0 truncates, nonzero appends. Returns bytes written; I/O failure raises `io` with the path and OS reason. Trace-recorded result. Every byte follows N5 then 0..255; fractions and out-of-range bytes raise `value` before I/O or replay. The written count is an int. | | `rename` | `rename of [old, new]` | Rename/replace a file. Atomic on POSIX (`rename(2)`) — a crash leaves either the old or the new file whole, never a mix; basis for crash-safe swaps. Returns `true`; I/O failure raises `io` with both paths and the OS reason. Trace-recorded result. | | `remove_file` | `remove_file of path` | Delete a file. Returns `true`; I/O failure raises `io` with the path and OS reason. Trace-recorded result. | +| `dot` | `dot of [a, b]` | Dot product of float buffers, returning a float; summation association is unspecified. Non-finite input cells raise `value` in every strict mode (N13). | ### Self-hosting | Name | Signature | Description | |------|-----------|-------------| -| `vm_run_bytecode` | `vm_run_bytecode of ` | Assemble a chunk from a descriptor and run it on the C VM, returning the result. Descriptor: `[abi, code, constants, functions?, param_count?, name?, local_names?]` — `abi` is the **bytecode ABI revision** the producer was built against (currently `1`; see below); `code` is a list of byte ints (opcodes + little-endian operands, 16-bit except `OP_LINE`'s, which is 32-bit since #630); `constants` is the pool; `functions` is a list of nested descriptors referenced by `OP_CLOSURE` (nested descriptors carry **no** `abi` element); `local_names` (slot order) sizes the call frame and names parameters. The minimal `[abi, code, constants]` form is a flat module chunk. The bridge for an EigenScript-written compiler: emit bytecode as data, execute it on the same VM (and JIT) the C compiler's output uses. Caller supplies a well-formed chunk ending in `OP_RETURN`. **The chunk must also be stack-balanced**. `vm_run_bytecode` rejects an invalid chunk by raising a catchable `value` error whose diagnostic names the malformed field or stack-verification failure; `sandbox_run` returns its structured rejection `{ok: false, error: {kind: "value", message: "invalid chunk descriptor", line: 0}}` instead. No instruction may be reached with fewer operands on the stack than it consumes, and the stack depth must be the same on every path into a given instruction — the JVM/Wasm rule, which keeps verification linear. So an `if`/`else` whose two arms leave different depths is rejected, as is a `CALL` that cannot see its own callee; the depth an instruction runs at is measured from the chunk's own frame base, and consuming below it would reach the caller's operands. This is the shape the C compiler already emits — a conditional's arms each push their value before the join — so a producer that mirrors compiler output needs no change. **A descriptor whose `abi` is missing or does not match the runtime raises (kind `value`) rather than executing** — #704: opcode numbers *and* operand widths are the bytecode ABI, and before the stamp a producer built against an older revision ran misaligned garbage at exit 0 with no error. Producers must hardcode the revision as a literal; a producer that reads the runtime's current value back would always agree and the check would be decoration. A non-list descriptor also raises (it silently returned `null` before #704). | -| `sandbox_run` | `sandbox_run of [descriptor_or_source, max_iterations?, max_bytes?, max_work?]` | Run an ABI-stamped descriptor, or compile a source string with the C compiler and run it, in the same sealed-root allowlist. Returns `{ok:true,result:...}` or `{ok:false,error:{kind,message,line,file}}`; source front-end failures have kind `parse`, while policy/budget failures have kind `sandbox`. Budgets are deterministic and cumulative for the whole call: loop back edges default to **1,000,000**, allocation/output bytes default to **256 MiB**, and executed VM instructions (including function-call entry/dispatch and callback re-entry) default to **10,000,000**. Source preparation additionally has a deterministic compile-phase sub-budget under `max_bytes`: **1 MiB + 128 bytes per source byte** is reserved as the worst-case lexer/parser/compiler/chunk working set, and a source whose reservation exceeds `max_bytes` is refused before tokenization. This is deliberately not a wall-clock deadline; native blocking calls are excluded, preserving replay determinism. | -| `record_history` | `record_history of flag` | Enable (nonzero) / disable (0) per-assignment history recording that `prev of x` and ` is x at ` temporal queries read (sets both value- and observer-state history). The C compiler auto-enables it when compiling a temporal query; a self-hosted compiler calls this. The flag must be a number — a non-numeric flag raises (it is not silently treated as disable). Returns the previous setting. | +| `vm_run_bytecode` | `vm_run_bytecode of ` | Assemble a chunk from a descriptor and run it on the C VM, returning the result. Descriptor: `[abi, code, constants, functions?, param_count?, name?, local_names?]` — `abi` is the **bytecode ABI revision** the producer was built against (currently `1`; see below); `code` is a list of byte ints (opcodes + little-endian operands, 16-bit except `OP_LINE`'s, which is 32-bit since #630); `constants` is the pool; `functions` is a list of nested descriptors referenced by `OP_CLOSURE` (nested descriptors carry **no** `abi` element); `local_names` (slot order) sizes the call frame and names parameters. The minimal `[abi, code, constants]` form is a flat module chunk. The bridge for an EigenScript-written compiler: emit bytecode as data, execute it on the same VM (and JIT) the C compiler's output uses. Caller supplies a well-formed chunk ending in `OP_RETURN`. **The chunk must also be stack-balanced**. `vm_run_bytecode` rejects an invalid chunk by raising a catchable `value` error whose diagnostic names the malformed field or stack-verification failure; `sandbox_run` returns its structured rejection `{ok: false, error: {kind: "value", message: "invalid chunk descriptor", line: 0}}` instead. No instruction may be reached with fewer operands on the stack than it consumes, and the stack depth must be the same on every path into a given instruction — the JVM/Wasm rule, which keeps verification linear. So an `if`/`else` whose two arms leave different depths is rejected, as is a `CALL` that cannot see its own callee; the depth an instruction runs at is measured from the chunk's own frame base, and consuming below it would reach the caller's operands. This is the shape the C compiler already emits — a conditional's arms each push their value before the join — so a producer that mirrors compiler output needs no change. **A descriptor whose `abi` is missing or does not match the runtime raises (kind `value`) rather than executing** — #704: opcode numbers *and* operand widths are the bytecode ABI, and before the stamp a producer built against an older revision ran misaligned garbage at exit 0 with no error. Producers must hardcode the revision as a literal; a producer that reads the runtime's current value back would always agree and the check would be decoration. A non-list descriptor also raises (it silently returned `null` before #704). ABI and parameter counts follow N5 and the C int range; code bytes follow N5 and 0..255, without truncation or wrapping. | +| `record_history` | `record_history of flag` | Enable (nonzero) / disable (0) per-assignment history recording that `prev of x` and ` is x at ` temporal queries read (sets both value- and observer-state history). The C compiler auto-enables it when compiling a temporal query; a self-hosted compiler calls this. The flag must be a number — a non-numeric flag raises (it is not silently treated as disable). Returns the previous setting as an int; fractional numeric flags retain nonzero truthiness. | +| `sandbox_run` | `sandbox_run of [descriptor_or_source, max_iterations?, max_bytes?, max_work?]` | Run an ABI-stamped descriptor, or compile a source string with the C compiler and run it, in the same sealed-root allowlist. Returns `{ok:true,result:...}` or `{ok:false,error:{kind,message,line,file}}`; source front-end failures have kind `parse`, while policy/budget failures have kind `sandbox`. Budgets are deterministic and cumulative for the whole call: loop back edges default to **1,000,000**, allocation/output bytes default to **256 MiB**, and executed VM instructions (including function-call entry/dispatch and callback re-entry) default to **10,000,000**. Source preparation additionally has a deterministic compile-phase sub-budget under `max_bytes`: **1 MiB + 128 bytes per source byte** is reserved as the worst-case lexer/parser/compiler/chunk working set, and a source whose reservation exceeds `max_bytes` is refused before tokenization. This is deliberately not a wall-clock deadline; native blocking calls are excluded, preserving replay determinism. Iteration, byte and work limits require exact integers (N5); fractions raise `value`. Error line numbers are ints. | ### Bytes ↔ values @@ -198,9 +314,9 @@ For serialization: reconstruct strings/floats from raw bytes (the inverse of an | Name | Signature | Description | |------|-----------|-------------| -| `str_from_bytes` | `str_from_bytes of ` | Build a string from raw byte values (0–255) — the list form of `chr` (`chr of n` == `str_from_bytes of [n]` for 1–255), inverting an `ord`-over-bytes loop. Nonnumeric list elements raise `type_mismatch` by default and convert to numeric zero under `EIGS_STRICT=0`. Strings are NUL-terminated: a numeric byte that converts to `0` ends conversion, and later elements are not inspected — keep NUL-bearing binary in a buffer. | -| `f64_to_bytes` | `f64_to_bytes of x` | List of 8 ints: the big-endian IEEE-754 encoding of double `x` (network byte order, portable across host endianness). A non-number raises by default; under `EIGS_STRICT=0` it encodes as `0.0` (#971). | -| `f64_from_bytes` | `f64_from_bytes of ` | Decode a double from the first 8 big-endian IEEE-754 bytes; the input must contain at least 8 bytes. Inverse of `f64_to_bytes`. A `NaN` bit pattern raises a `value` error by default; under `EIGS_STRICT=0` it collapses to `0` and sets `math_flags.invalid` (#971). | +| `str_from_bytes` | `str_from_bytes of ` | Input bytes follow N5, then 0..255; fractions/out-of-range bytes raise `value`. Build a string from raw byte values (0–255) — the list form of `chr` (`chr of n` == `str_from_bytes of [n]` for 1–255), inverting an `ord`-over-bytes loop. Nonnumeric list elements raise `type_mismatch` by default and convert to numeric zero under `EIGS_STRICT=0`. Strings are NUL-terminated: a numeric byte that converts to `0` ends conversion, and later elements are not inspected — keep NUL-bearing binary in a buffer. | +| `f64_to_bytes` | `f64_to_bytes of x` | An integer input first converts to nearest f64 (Q2). List of 8 ints: the big-endian IEEE-754 encoding of double `x` (network byte order, portable across host endianness). A non-number raises by default; under `EIGS_STRICT=0` it encodes as `0.0` (#971). | +| `f64_from_bytes` | `f64_from_bytes of ` | Input bytes follow N5, then 0..255; fractions/out-of-range bytes raise `value`. Decode a double from the first 8 big-endian IEEE-754 bytes; the input must contain at least 8 bytes. Inverse of `f64_to_bytes`. A `NaN` bit pattern raises a `value` error by default; under `EIGS_STRICT=0` it collapses to `0` and sets `math_flags.invalid` (#971). | Buffers also support direct indexing (`buf[i]`, `buf[i] is val`) and compound assignment (`buf[i] += val`). @@ -228,9 +344,9 @@ Requires the `zlib` build (`make zlib`, `-DEIGENSCRIPT_EXT_ZLIB=1 | Name | Signature | Description | |------|-----------|-------------| | `json_encode` | `json_encode of value` | Serialize value to JSON string. Raises on a value nested deeper than 200 levels — which includes any **cyclic** value (`dict_set of [d, "self", d]`, `append of [a, a]`), since a cycle has no depth. Catchable. | -| `json_decode` | `json_decode of s` | Parse JSON string to value. Raises past the same 200-level limit, so a document that decodes always re-encodes. `\uXXXX` surrogate pairs are combined into one code point; unpaired surrogates, an escaped NUL, and malformed `\u` escapes raise (strict decode — lenient callers such as `json_path` receive the complete document with U+FFFD in place of the bad scalar). | +| `json_decode` | `json_decode of s` | Parse JSON string to value. Integer text fitting int64 decodes exactly as int; decimal overflow, a point or exponent produces float (N14). Raises past the same 200-level limit, so a document that decodes always re-encodes. `\uXXXX` surrogate pairs are combined into one code point; unpaired surrogates, an escaped NUL, and malformed `\u` escapes raise (strict decode — lenient callers such as `json_path` receive the complete document with U+FFFD in place of the bad scalar). | | `json_build` | `json_build of [k1, v1, k2, v2, ...]` | Build JSON object from key-value pairs. `json_build of null` is `{}`; any other non-list raises by default and answers `{}` under `EIGS_STRICT=0` (#971). | -| `json_path` | `json_path of [json_str, "dot.path"]` | Extract nested value by dot-notation path; `""` when there is no value at that path (absent key, index out of range, JSON `null`). The document is parsed leniently: a malformed document is walked as far as it parsed, so a parse failure also answers `""` or a partial value. By default, a document that `json_decode` would reject (structural error, a repaired `\u` scalar, trailing garbage) raises a catchable `value` error `json_path: invalid JSON at position N` instead (#971 Phase C); JSON `false`/`null`/absent keys stay answers in both modes. | +| `json_path` | `json_path of [json_str, "dot.path"]` | Extract nested value as text by dot-notation path, with exact int64 digits (N14); `""` when there is no value at that path (absent key, index out of range, JSON `null`). The document is parsed leniently: a malformed document is walked as far as it parsed, so a parse failure also answers `""` or a partial value. By default, a document that `json_decode` would reject (structural error, a repaired `\u` scalar, trailing garbage) raises a catchable `value` error `json_path: invalid JSON at position N` instead (#971 Phase C); JSON `false`/`null`/absent keys stay answers in both modes. | ## Dictionaries @@ -268,14 +384,14 @@ Query a binding's assignment history. Always on for top-level bindings; |------|--------|---------| | `prev` | `prev of x` | Value of `x` just before its most recent assignment | | `at` | `what is x at L` | State at or before line `L` — works with all six interrogatives and `prev` | -| `state_at` | `state_at of line` | Dict of every tracked binding's value at or before `line` | +| `state_at` | `state_at of line` | Dict of every tracked binding's value at or before `line`. Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | ## Observer | Name | Signature | Description | |------|-----------|-------------| | `observe` | `observe of value` | Return [status, entropy, dH, prev_dH] snapshot | -| `get_observer_thresholds` | `get_observer_thresholds of null` | `[dh_zero, dh_small, h_low]` | +| `get_observer_thresholds` | `get_observer_thresholds of null` | `[dh_zero, dh_small, h_low]`. Numeric inputs convert to float; numeric results are floats. | | `classify` | `classify of t` or `classify of [t, "entropy"]` | Classify a trajectory snapshot (from `trajectory of x`, #421): value-channel label by default, entropy-channel with `"entropy"`. Raises `type_mismatch` on a non-snapshot — a bare value never silently classifies | **`report` and `report_value` are reserved** (#1102). They cannot be bound or @@ -326,13 +442,13 @@ Boolean keywords that check the most recently observed value: | `file_exists` | `file_exists of "path"` | `true` if the path exists (any kind: file, directory, device, fifo), `false` otherwise. A `stat` probe — never blocks (#1070: the old `fopen` probe hung on a reader-less fifo). Trace-recorded, so replay is deterministic (#585) | | `is_dir` | `is_dir of "path"` | `true` if the path names a directory, `false` for a plain file / missing path (#576 — replaces the `file_exists of "path/."` probe). Trace-recorded, so replay is deterministic | | `is_file` | `is_file of "path"` | `true` iff the path names a REGULAR file (`S_ISREG`); `false` for a directory, a device/fifo/socket, a missing path, or a non-string. `read_file_util` admits only regular files, so this is the probe a driver uses to match that contract (#1058). Trace-recorded, so replay is deterministic | -| `read_text` | `read_text of path` / `read_text of [path, max_bytes]` | Read file or stream contents as a string. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. Unseekable streams (including `/dev/stdin`, pipes, and FIFOs) are read in chunks to EOF. Missing, unreadable, directory, and over-cap paths raise `io`; over-cap errors name the size, active cap, and `[path, max_bytes]` escape. An empty input returns `""` | +| `read_text` | `read_text of path` / `read_text of [path, max_bytes]` | Read file or stream contents as a string. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. Unseekable streams (including `/dev/stdin`, pipes, and FIFOs) are read in chunks to EOF. Missing, unreadable, directory, and over-cap paths raise `io`; over-cap errors name the size, active cap, and `[path, max_bytes]` escape. An empty input returns `""`. The cap follows N5: integral floats convert; fractions raise `value`. | | `read_line` | `read_line of null` | Blocking byte-exact line read from **stdin**: the returned string includes its line terminator (`\n`, with a preceding `\r` preserved), or `null` at EOF. Thus a terminated final line differs from an unterminated one, and an empty line is `"\n"`. Pipes and redirected regular files have identical semantics. Trace-recorded, so replay is deterministic | -| `read_bytes` | `read_bytes of path` / `read_bytes of [path, max_bytes]` | Read a file's or stream's raw bytes as a list of integers 0–255. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. Unseekable streams are read in chunks to EOF. Missing, unreadable, directory, and over-cap paths raise `io`; over-cap errors name the size, active cap, and `[path, max_bytes]` escape. An empty input returns `[]`. Trace-recorded, so replay is deterministic | +| `read_bytes` | `read_bytes of path` / `read_bytes of [path, max_bytes]` | Read a file's or stream's raw bytes as a list of integers 0–255. The default cap is 10 MiB; `max_bytes` raises it up to the shared 512 MiB hard ceiling. Unseekable streams are read in chunks to EOF. Missing, unreadable, directory, and over-cap paths raise `io`; over-cap errors name the size, active cap, and `[path, max_bytes]` escape. An empty input returns `[]`. Trace-recorded, so replay is deterministic. The cap follows N5: integral floats convert; fractions raise `value`. Returned byte values are ints (N1/Q1). | | `write_text` | `write_text of ["path", text]` | Write string to file (`true` on success); I/O failure raises `io` with the path and OS reason. Trace-recorded result | -| `exec_capture` | `exec_capture of ["cmd", "arg1", ...]` | Run subprocess, return [exit_code, stdout_text]. No shell (direct exec). Child stdin is /dev/null. Returns [-1, ""] on failure, [-2, partial] on timeout. 10 MB output cap. Timeout form: `exec_capture of [["cmd", ...], seconds]` | +| `exec_capture` | `exec_capture of ["cmd", "arg1", ...]` | Run subprocess, return [exit_code, stdout_text]. No shell (direct exec). Child stdin is /dev/null. Returns [-1, ""] on failure, [-2, partial] on timeout. 10 MB output cap. Timeout form: `exec_capture of [["cmd", ...], seconds]`. Exit statuses are ints. Seconds convert to milliseconds rounded half away from zero; an interval outside the poll API C int range raises `value` before starting the child (Q7 magnitude). | | `env_get` | `env_get of "VAR_NAME"` | Get environment variable (empty string if unset) | -| `random_hex` | `random_hex of n` | Generate n random hex characters from /dev/urandom (`""` for `n <= 0` or `n > 256`). A non-number `n` raises by default; under `EIGS_STRICT=0` it answers `""` (#971). | +| `random_hex` | `random_hex of n` | Generate n random hex characters from /dev/urandom (`""` for `n <= 0` or `n > 256`). A non-number `n` raises by default; under `EIGS_STRICT=0` it answers `""` (#971). The count follows N5, then the C int range: integral floats convert; fractions raise `value` before replay or randomness. | | `try_parse` | `try_parse of code_string` | `true` if string is valid EigenScript syntax, `false` otherwise | | `mkdir` | `mkdir of "path"` | Create directory (and parents). `true` on success; I/O failure raises `io` with the path and OS reason. Trace-recorded: replay serves the recorded result and does not re-create the directory (#585) | | `ls` | `ls of "path"` | List non-hidden directory entries as bytewise-sorted strings (the order of `LC_ALL=C ls -1`). I/O failure raises `io` with the path and OS reason. Trace-recorded, so replay is deterministic (#585) | @@ -350,8 +466,8 @@ has the shared 10,000,000-element limit. | Name | Signature | Description | |------|-----------|-------------| -| `stream_open` | `stream_open of ["path", count]` | Open file, write header for an integral `count` from 1 through 10,000,000 float64 values. Counts outside that range or fractional counts raise a catchable `limit` error naming the path and cap, including under `EIGS_STRICT=0`. Returns `true` on success, `false` on an I/O failure. One stream per **thread**: opening a second closes the first, and an unclosed stream is flushed and closed when the thread ends (#739) | -| `stream_write` | `stream_write of value` | Append one float64 to the open stream. `true` on success, `false` on failure | +| `stream_open` | `stream_open of ["path", count]` | Open file, write header for an integral `count` from 1 through 10,000,000 float64 values. Fractional counts raise `value` (N5); integral floats convert. Integral counts outside the cap raise a catchable `limit` error naming the path and cap, including under `EIGS_STRICT=0`. Returns `true` on success, `false` on an I/O failure. One stream per **thread**: opening a second closes the first, and an unclosed stream is flushed and closed when the thread ends (#739) | +| `stream_write` | `stream_write of value` | Append one float64 to the open stream. `true` on success, `false` on failure. Numeric inputs convert to float storage (Q2). | | `stream_close` | `stream_close of null` | Close the stream. `true` on success, `false` on failure | `load_file` and `import` resolve an absolute path as-is; otherwise they try the @@ -380,18 +496,18 @@ stdlib roots. See [Modules](SPEC.md#modules) for import collision handling. | Name | Signature | Description | |------|-----------|-------------| -| `random` | `random of null` | Random float in [0, 1) | -| `random_int` | `random_int of [lo, hi]` | Random integer in [lo, hi] inclusive; raises on non-finite or out-of-int64 bounds and on a span over 2^31. A malformed argument (not `[lo, hi]`, or non-numeric bounds) raises by default; under `EIGS_STRICT=0` it answers `0` (#971). | -| `seed_random` | `seed_random of n` | Seed the RNG for deterministic sequences | +| `random` | `random of null` | Random float in [0, 1). Numeric inputs convert to float; numeric results are floats. | +| `random_int` | `random_int of [lo, hi]` | Random int in [lo, hi] inclusive. Bounds follow N5: integral floats convert; fractions and out-of-int64 values raise `value`. The existing span limit is 2^31. Malformed arguments raise by default and return int 0 under `EIGS_STRICT=0`. | +| `seed_random` | `seed_random of n` | Seed the RNG from an exact int64 (N5); fractional floats raise. The existing srand48 generator uses the low 32 seed bits. `random` is not cryptographic. The `eigenscript` CLI reads `EIGS_SEED_RANDOM=` at startup and seeds the shared stream with it before the program runs, which makes it predictable for the whole process (for differential testing and reproduction); an unset or empty value does nothing. A nonempty value is accepted when `strtol` parses the entire value as a representable decimal `long`; leading whitespace and a sign (such as `+7`) are accepted, but trailing characters and out-of-range values are startup errors (exit 2). This environment variable is CLI-only: embedders call `seed_random`. Under `EIGS_TRACE` seeded draws are recorded; under `EIGS_REPLAY` recorded values are served and this knob is irrelevant. | ## Time | Name | Signature | Description | |------|-----------|-------------| -| `monotonic_ns` | `monotonic_ns of null` | Nanoseconds from `CLOCK_MONOTONIC` (jump-free) | -| `monotonic_ms` | `monotonic_ms of null` | Milliseconds from `CLOCK_MONOTONIC` | -| `clock_unix` | `clock_unix of null` | Seconds since the Unix epoch (float, wall clock; tape-captured for replay) | -| `usleep` | `usleep of microseconds` | Pause execution | +| `monotonic_ns` | `monotonic_ns of null` | Exact int64 nanoseconds from `CLOCK_MONOTONIC` (jump-free); raises `value` if the nanosecond count overflows int64. | +| `monotonic_ms` | `monotonic_ms of null` | Milliseconds from `CLOCK_MONOTONIC`. Numeric inputs convert to float; numeric results are floats. | +| `clock_unix` | `clock_unix of null` | Seconds since the Unix epoch (float, wall clock; tape-captured for replay). Numeric inputs convert to float; numeric results are floats. | +| `usleep` | `usleep of microseconds` | Pause execution. The continuous duration rounds half away from zero; a result outside the C int range raises `value` (Q7 magnitude). Nonpositive durations return immediately. | ## Trace & Replay @@ -420,20 +536,20 @@ automatically at exit. | Name | Signature | Description | |------|-----------|-------------| -| `abs` | `abs of x` | Absolute value | -| `min` | `min of [n1, n2, ...]` | Smallest of a list of numbers (any length >= 1) | -| `max` | `max of [n1, n2, ...]` | Largest of a list of numbers (any length >= 1) | -| `floor` | `floor of x` | Round down to integer | -| `ceil` | `ceil of x` | Round up to integer | -| `round` | `round of x` | Round to nearest integer | -| `sin` | `sin of x` | Sine (radians) | -| `cos` | `cos of x` | Cosine (radians) | -| `tan` | `tan of x` | Tangent (radians) | -| `asin` | `asin of x` | Inverse sine; input is clamped to [-1, 1] | -| `acos` | `acos of x` | Inverse cosine; input is clamped to [-1, 1] | -| `atan` | `atan of x` | Inverse tangent | -| `atan2` | `atan2 of [y, x]` | Two-argument inverse tangent | -| `pi` | `pi of null` | The constant π (3.14159265...) | +| `abs` | `abs of x` | Absolute value, preserving int or float kind. `abs` of INT64_MIN raises `value` because its positive magnitude does not fit in int64 (#1758 N4b). | +| `min` | `min of [n1, n2, ...]` | Winning numeric argument, retaining its int/float kind; compare int64 exactly, including mixed-kind comparisons. First argument wins ties. Empty input raises `value`, naming the builtin, in both call forms and both strict modes (R1.6b-D2). | +| `max` | `max of [n1, n2, ...]` | Winning numeric argument, retaining its int/float kind; compare int64 exactly, including mixed-kind comparisons. First argument wins ties. Empty input raises `value`, naming the builtin, in both call forms and both strict modes (R1.6b-D2). | +| `floor` | `floor of x` | Round down; int inputs remain exact. A float result fitting int64 becomes an int; otherwise it remains float. | +| `ceil` | `ceil of x` | Round up; int inputs remain exact. A float result fitting int64 becomes an int; otherwise it remains float. | +| `round` | `round of x` | Round half away from zero; int inputs remain exact. A float result fitting int64 becomes an int; otherwise it remains float. | +| `sin` | `sin of x` | Sine (radians). Numeric inputs convert to float; numeric results are floats. | +| `cos` | `cos of x` | Cosine (radians). Numeric inputs convert to float; numeric results are floats. | +| `tan` | `tan of x` | Tangent (radians). Numeric inputs convert to float; numeric results are floats. | +| `asin` | `asin of x` | Inverse sine; input is clamped to [-1, 1]. Numeric inputs convert to float; numeric results are floats. | +| `acos` | `acos of x` | Inverse cosine; input is clamped to [-1, 1]. Numeric inputs convert to float; numeric results are floats. | +| `atan` | `atan of x` | Inverse tangent. Numeric inputs convert to float; numeric results are floats. | +| `atan2` | `atan2 of [y, x]` | Two-argument inverse tangent. Numeric inputs convert to float; numeric results are floats. | +| `pi` | `pi of null` | The constant π (3.14159265...). Numeric inputs convert to float; numeric results are floats. | ## Tensor Math @@ -444,7 +560,8 @@ boxed `Value` per number, and the container the JIT and the AOT compile against. Every builtin in this section that accepts a flat numeric list accepts a buffer in the same position; a 1-D buffer reads as a 1-D tensor and a shaped buffer (`buffer of [r, c]`, `reshape of [buf, r, c]`) as its `r x c` 2-D -tensor. The numbers are identical either way. +tensor. Buffers store floats; integer list elements stay exact through +`add`, `subtract`, `multiply`, `negative` and all-int `sum`. **Container of the result**: a builtin that returns a tensor returns a *buffer* when **every** tensor operand was a buffer, and a *list* otherwise @@ -460,12 +577,12 @@ heterogeneous or nested data. | Name | Signature | Description | |------|-----------|-------------| -| `add` | `add of [a, b]` | Element-wise addition | -| `subtract` | `subtract of [a, b]` | Element-wise subtraction | -| `multiply` | `multiply of [a, b]` | Element-wise multiplication | +| `add` | `add of [a, b]` | Element-wise addition; two int scalar or list elements produce an exact int, raising `value` on int64 overflow. Buffer arithmetic stays float. | +| `subtract` | `subtract of [a, b]` | Element-wise subtraction; two int scalar or list elements produce an exact int, raising `value` on int64 overflow. Buffer arithmetic stays float. | +| `multiply` | `multiply of [a, b]` | Element-wise multiplication; two int scalar or list elements produce an exact int, raising `value` on int64 overflow. Buffer arithmetic stays float. | | `divide` | `divide of [a, b]` | Element-wise division; a zero denominator raises `divide: division by zero` by default (as the `/` operator does), while under `EIGS_STRICT=0` it returns 0; overflow saturates (#971). | | `pow` | `pow of [base, exp]` | Element-wise exponentiation; overflow saturates. A negative base with a fractional exponent raises a `value` error naming `pow` by default; under `EIGS_STRICT=0` its `NaN` collapses to `0` and sets `math_flags.invalid` (#971). | -| `negative` | `negative of t` | Element-wise negation | +| `negative` | `negative of t` | Element-wise negation; scalar and list ints remain exact ints, raising `value` at INT64_MIN. Float and buffer elements remain floats. | The arithmetic builtins listed above take shaped **buffers** wherever they take a flat numeric list (#1093/#973), through **one** implementation — the same @@ -482,7 +599,8 @@ have (the one place the two containers still differ: buffers truncate flat, so `tests/test_autograd.eigs`); a non-numeric partner (a string, a dict) raises by default and answers `0` under `EIGS_STRICT=0`. Before #1093 only equal-count `add` had a buffer path and every other buffer case answered a silent `0`. Same `num_guard` kernels -either way, so the numbers are byte-identical. +for float inputs either way. Integer list arithmetic is exact; storing an int +in a buffer first converts it to float and can round it. ### Functions @@ -500,18 +618,20 @@ either way, so the numbers are byte-identical. | Name | Signature | Description | |------|-----------|-------------| -| `matmul` | `matmul of [a, b]` | Matrix multiplication. Two shaped buffers multiply on the flat data and give a buffer; a 1-D left operand gives a 1-D result. Mixed list/buffer operands give a list. An accumulation that reaches `inf - inf` is `NaN`; by default that raises a catchable `value` error naming `matmul` (#971). Kernels may retain raw non-finite values in buffer storage, but every scalar read applies the finite-number guard (#1417): infinity saturates at ±`1e308`, and with strict mode off a `NaN` becomes `0` and sets `math_flags.invalid`, matching the list result. | +| `matmul` | `matmul of [a, b]` | Matrix multiplication. Two shaped buffers multiply on the flat data and give a buffer; a 1-D left operand gives a 1-D result. Mixed list/buffer operands give a list. An accumulation that reaches `inf - inf` is `NaN`; by default that raises a catchable `value` error naming `matmul` (#971). Kernels may retain raw non-finite values in buffer storage. Indexing and iteration, like `get_at`, refuse stored NaN and infinity with `value` in every strict mode (N13). Strict NaN keeps the first-NaN diagnostic and INVALID flag; the interpreter, JIT and leaf fallback agree on the error source line. | | `matmul_at` | `matmul_at of [a, b]` | `aᵀ·b` without materialising the transpose: `a` is `(m × k)`, `b` is `(m × n)`, result `(k × n)` — the weight gradient `dW = Xᵀ·dY` of a linear layer. Buffers and nested lists; byte-identical to `matmul` of the explicitly transposed operand (same tiled kernel order). Two 1-D operands give their `(k × n)` outer product. Shape/type/size errors raise like `matmul` (#973) | | `matmul_bt` | `matmul_bt of [a, b]` | `a·bᵀ`: `a` is `(m × k)`, `b` is `(n × k)`, result `(m × n)` — the input gradient `dX = dY·Wᵀ`. A 1-D left operand is a row vector and yields a 1-D result, as for `matmul` (#973) | -| `gather` | `gather of [matrix, indices, dim]` | Gather one element per row: `out[i] = matrix[i][indices[i]]`. `matrix` may be a shaped buffer and `indices` a list or a buffer; a shaped-buffer `matrix` gives a buffer. `gather of [vec, i]` on a 1-D tensor returns element `i`. An **out-of-range index raises `index_range`** — in every form, list or buffer (#973/#1093, settled at integration: there is no element there, and `scatter_add` raises on the same index). A row that is not a row (a 1-D tensor in the per-row form) still answers `0.0` for that row | -| `scatter_add` | `scatter_add of [dst, indices, values]` | The gradient of `gather`, accumulated **in place** into the buffer `dst` (returned). A shaped `[rows × cols]` dst does `dst[i][indices[i]] += values[i]` per row; a 1-D dst does the flat `dst[indices[j]] += values[j]`. `values` is a buffer, a list of numbers, or one number broadcast to every index; repeats accumulate. Lengths must line up exactly — a non-scalar `values` shorter or longer than `indices`, or a per-row `dst` whose row count differs from the index count, raises `value` rather than truncating (a dropped gradient entry is a silent wrong number). Every index is validated **before** anything is written, so a raise (`index_range`, `value`, or `type_mismatch` for a non-buffer dst / non-numeric index or value) leaves `dst` untouched (#973) | +| `gather` | `gather of [matrix, indices, dim]` | N5 exact indices (integral floats accepted; fractions raise `value`). Selected list cells retain their numeric kind; buffer elements are floats. Gather one element per row: `out[i] = matrix[i][indices[i]]`. `matrix` may be a shaped buffer and `indices` a list or a buffer; a shaped-buffer `matrix` gives a buffer. `gather of [vec, i]` on a 1-D tensor returns element `i`. An **out-of-range index raises `index_range`** — in every form, list or buffer (#973/#1093, settled at integration: there is no element there, and `scatter_add` raises on the same index). A row that is not a row (a 1-D tensor in the per-row form) still answers `0.0` for that row | +| `scatter_add` | `scatter_add of [dst, indices, values]` | N5 exact indices (fractions raise `value`). The gradient of `gather`, accumulated **in place** into the buffer `dst` (returned). A shaped `[rows × cols]` dst does `dst[i][indices[i]] += values[i]` per row; a 1-D dst does the flat `dst[indices[j]] += values[j]`. `values` is a buffer, a list of numbers, or one number broadcast to every index; repeats accumulate. Lengths must line up exactly — a non-scalar `values` shorter or longer than `indices`, or a per-row `dst` whose row count differs from the index count, raises `value` rather than truncating (a dropped gradient entry is a silent wrong number). Every index is validated **before** anything is written, so a raise (`index_range`, `value`, or `type_mismatch` for a non-buffer dst / non-numeric index or value) leaves `dst` untouched (#973) | + +Buffer arithmetic stops at its first raised kernel result and releases its partial output; later elements are not read (#1417). ### Reductions | Name | Signature | Description | |------|-----------|-------------| -| `mean` | `mean of t` | Average of all elements (list, nested list or buffer; an empty buffer or list is `0.0`) | -| `sum` | `sum of t` | Sum of all elements (list, nested list or buffer) | +| `mean` | `mean of t` | Non-finite buffer cells raise `value` in both strict modes (N13). Average of all elements (list, nested list or buffer; an empty buffer or list is `0.0`) | +| `sum` | `sum of t` | Sum of all elements (list, nested list or buffer). An all-int input returns an exact int and raises `value` on int64 overflow. A float anywhere in a nested input makes the reduction float from the start; buffers produce floats and refuse non-finite elements with `value` in every strict mode (N13). In strict mode the first NaN sets `math_flags.invalid` and raises "not a number" before reading later elements (#1417). An empty list returns int `0`. | ### Construction @@ -519,9 +639,9 @@ either way, so the numbers are byte-identical. |------|-----------|-------------| | `zeros` | `zeros of n` or `zeros of [rows, cols]` | `zeros of n` returns a **buffer** of `n` zeros (`type of` is `buffer`, `print of` shows ``); `zeros of [rows, cols]` returns the nested-list 2-D tensor. Breaking change in #1093 — `zeros of n` used to return a list; write `[0 for i in range of n]` if you need that. Both spellings cap at 10,000,000 elements and charge the sandbox budget | | `zeros_like` | `zeros_like of t` | Zero tensor matching `t`'s shape **and container**: a buffer gives a buffer (shape preserved), a list gives a list, a number gives `0.0` | -| `random_normal` | `random_normal of [rows, cols, scale]` | Gaussian random tensor | -| `shape` | `shape of t` | Return dimensions as list | -| `reshape` | `reshape of [buffer, rows, cols]` | New numeric buffer with the same data reinterpreted as `rows`×`cols` (requires `rows*cols == count`; `null` otherwise) | +| `random_normal` | `random_normal of [rows, cols, scale]` | Gaussian random float tensor; dimensions follow N5 (integral floats accepted; fractions raise `value`). Dimension refusal precedes the trace-tape boundary | +| `shape` | `shape of t` | Return dimensions as a list of exact integers | +| `reshape` | `reshape of [buffer, rows, cols]` | Rows/columns follow N5, then the C int range. New numeric buffer with the same data reinterpreted as `rows`×`cols` (requires `rows*cols == count`; `null` otherwise) | ### Persistence @@ -545,13 +665,13 @@ either way, so the numbers are byte-identical. | Name | Signature | Description | |------|-----------|-------------| -| `token_name` | `token_name of id` | Return token type name by ID (`"?"` for an unknown id). A non-number raises by default; under `EIGS_STRICT=0` it answers `"?"` too (#971). | +| `token_name` | `token_name of id` | Return token type name by ID (`"?"` for an unknown id). A non-number raises by default; under `EIGS_STRICT=0` it answers `"?"` too (#971). Integer positions, lengths and counts accept integral floats; fractions or values outside the C int range raise `value` (N5). | ## Corpus Preparation | Name | Signature | Description | |------|-----------|-------------| -| `build_corpus` | `build_corpus of [files, top_n, stream_path, vocab_path]` | Three-pass C-backed corpus builder: tokenise `files`, emit top-`n` vocabulary and stream-format token IDs. The token stream, including file separators, is capped at 10,000,000 elements; exceeding it raises a catchable `limit` error naming the stream path and cap, including under `EIGS_STRICT=0`, before opening the stream file | +| `build_corpus` | `build_corpus of [files, top_n, stream_path, vocab_path]` | Three-pass C-backed corpus builder: tokenise `files`, emit top-`n` vocabulary and stream-format token IDs. The token stream, including file separators, is capped at 10,000,000 elements; exceeding it raises a catchable `limit` error naming the stream path and cap, including under `EIGS_STRICT=0`, before opening the stream file. Vocabulary, slot and integer-literal counts follow N5 and the C int range; fractions raise `value`. Returned stream/identifier/file counts are ints. | ## Optional: Network Extension (TCP sockets) @@ -583,6 +703,10 @@ backlog before `net_accept` runs (see `examples/net_echo.eigs`). Sockets left open at exit are closed by the runtime's handle-table drain. UDP is not yet exposed. +TCP handles, ports and sent-byte counts are integers. Numeric socket handles, ports, receive counts and outgoing bytes require exact integers (integral floats convert); bytes must be in 0..255. `net_send` validates the entire payload before sending any byte. Poll timeouts are continuous milliseconds rounded to the C int range; an unrepresentable duration raises `value`. + +`http_early_bind` and `http_serve` require an exact integer port in 0..65535; integral floats convert and port zero requests an ephemeral port. Fractional or out-of-range ports raise `value` before binding. `shared_delete` and `shared_size` return integer counts. + ## Optional: HTTP Extension Requires full build. Provides an embedded HTTP server. @@ -621,10 +745,10 @@ axis. Three controls bound it: | `http_static` | `http_static of [prefix, directory]` | Serve static files (realpath-confined to `directory`). Register before `http_serve`: once serving it raises, including from a `code` route (#1140) | | `http_early_bind` | `http_early_bind of port`, `http_early_bind of null`, or `http_early_bind of [port, "/livez"]` | Bind/listen before initialization. `null` defaults to 5000; a positive `PORT` environment override still wins. During init every request receives **503 Service Unavailable** with `Retry-After: 1`; never a fake 200 for arbitrary paths. Only an explicitly supplied liveness path answers GET/HEAD with 200, `text/plain`, body `OK` (HEAD has no body), by exact request-target match, including any query string. The path must start with `/` and contain no whitespace, CR, LF or NUL; invalid paths raise. The two scalar spellings configure no liveness path. Init-window capacity is the same number as the serving connection cap (`HTTP_MAX_CONCURRENT_CONNS`, 256): an idle or slow client cannot delay any other in-flight answer, a newcomer past that cap is accepted and answered a bodyless 503 (Content-Length 0, identical on the wire for every method including HEAD) immediately, and a client that never sends a complete request line is answered 503 at a 1 s per-client deadline. Init-window replies (liveness 200, init 503, capacity shed, handoff/teardown drain) are best-effort to a peer that is not reading: the responder never waits on a client. Once `http_serve` takes over, normal routing applies even to that path (404 unless registered) | | `http_response_header` | `http_response_header of [name, value]` | Register before `http_serve`, including before or after `http_early_bind`; returns `"response header registered"`. Both arguments must be strings. Name: RFC 7230 token, **1–64 bytes**. Value: **0–1024 bytes**, visible ASCII, space or tab only; **no CR/LF/NUL**. Runtime-owned and runtime-emitted names (`Content-Length`, `Content-Type`, `Transfer-Encoding`, `Connection`, `Cache-Control`, `Retry-After`, `Allow`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`) are refused case-insensitively. An existing name is **replaced case-insensitively**, including at capacity; at most **16** distinct headers. Every violation raises a loud error naming the builtin and rule; a rejected registration prevents `http_serve` from starting even if caught. Registered headers accompany **every** response: routes (string/file/code/authed), static files, HEAD, OPTIONS, errors, load shedding, init 503s and optional liveness 200s. Content-Type, Content-Length, status and body bytes are unchanged. Registration is deterministic configuration and adds no tape records | -| `http_serve` | `http_serve of port` | Start blocking HTTP server | +| `http_serve` | `http_serve of port` | Start blocking HTTP server on an exact integer port in 0..65535 (integral floats convert). | | `http_request_body` | `http_request_body of null` | Get current request body | | `http_session_id` | `http_session_id of null` | Get current session ID | -| `http_post` | `http_post of [url, headers, body]` | HTTP POST via curl (no shell). `headers` is a JSON **string**, either an object (`"{\"X-Key\": \"v\"}"`) or a flat alternating array (`"[\"X-Key\", \"v\"]"`); `""` means no headers. Any other parsed shape raises `type_mismatch` rather than silently sending none (#755). Max 32 headers; CR/LF is stripped from both halves | +| `http_post` | `http_post of [url, headers, body]` | HTTP POST via curl (no shell). `headers` is a JSON **string**, either an object (`"{\"X-Key\": \"v\"}"`) or a flat alternating array (`"[\"X-Key\", \"v\"]"`); `""` means no headers. Any other parsed shape raises `type_mismatch` rather than silently sending none (#755). JSON integer header values within int64 retain every digit when sent as text (N14, B6). Max 32 headers; CR/LF is stripped from both halves | | `http_request_headers` | `http_request_headers of null` | Get current request headers | ### Per-worker code routes @@ -660,11 +784,11 @@ server down with it. Total bytes are bounded by | `shared_set` | `shared_set of [key, value]` | Store `value` (JSON-encoded). Returns `null` if over byte cap. | | `shared_get` | `shared_get of key` | Return stored value (re-parsed) or `null` if absent. | | `shared_has` | `shared_has of key` | Return `true` if key present, else `false`. | -| `shared_delete` | `shared_delete of key` | Remove key; return `1` if removed, `0` if absent. | +| `shared_delete` | `shared_delete of key` | Remove key; return integer `1` if removed, `0` if absent. | | `shared_keys` | `shared_keys of null` | Return list of keys. | -| `shared_size` | `shared_size of null` | Return current key count. | +| `shared_size` | `shared_size of null` | Return current integer key count. | | `shared_clear` | `shared_clear of null` | Drop all entries. | -| `shared_incr` | `shared_incr of [key, delta]` | Atomic single-lock read-modify-write. Missing key treated as `0`. Returns new value, or `null` if existing value is non-numeric. | +| `shared_incr` | `shared_incr of [key, delta]` | Atomic single-lock read-modify-write. Missing key treated as int `0`. Two ints produce an exact int; a float operand produces a float. Int64 overflow raises `value` without changing the counter. Returns new value, or `null` if existing value is non-numeric. | Individual op atomicity is guaranteed by the mutex. For read-modify-write atomicity use `shared_incr`; `shared_get`+`shared_set` @@ -693,35 +817,45 @@ first use, so no SDL2 headers or link-time SDL dependency are needed. | Name | Signature | Description | |------|-----------|-------------| -| `gfx_open` | `gfx_open of [width, height, title]` | Open window and renderer. Returns `true` on success or `false` when libSDL2 is unavailable. A non-numeric size raises by default; under `EIGS_STRICT=0` it returns `false` (#1007 — it used to be read without a type check, so a string opened a `0x0` window and still answered `true`). | +| `gfx_open` | `gfx_open of [width, height, title]` | Open window and renderer. Continuous sizes round half away from zero; a rounded size outside the C int range raises `value`. Returns `true` on success or `false` when libSDL2 is unavailable. A non-numeric size raises by default; under `EIGS_STRICT=0` it returns `false` (#1007 — it used to be read without a type check, so a string opened a `0x0` window and still answered `true`). | | `gfx_close` | `gfx_close of null` | Destroy window and quit SDL | -| `gfx_clear` | `gfx_clear of [r, g, b]` / `gfx_clear of null` | Clear backbuffer to color; `null` clears to black | -| `gfx_rect` | `gfx_rect of [x, y, w, h, r, g, b]` or `[..., a]` | Filled rectangle | -| `gfx_line` | `gfx_line of [x1, y1, x2, y2, r, g, b]` | Line segment | -| `gfx_point` | `gfx_point of [x, y, r, g, b]` | Single pixel | -| `gfx_circle` | `gfx_circle of [cx, cy, radius, r, g, b]` | Filled circle (midpoint) | -| `gfx_rrect` | `gfx_rrect of [x, y, w, h, radius, r, g, b]` or `[..., a]` | Filled rounded rectangle (scanline corner fill); radius clamps to half the smaller dimension, `radius 0` = plain rect | -| `gfx_clip` | `gfx_clip of [x, y, w, h]` / `gfx_clip of null` | Set / clear the render clip rectangle | -| `gfx_read` | `gfx_read of [x, y]` | Read back one rendered pixel as `[r, g, b]` — the render-decode oracle primitive (#823). Reads the current back buffer: call after drawing, **before** `gfx_present`. Null with no window or a failed read. Nondeterministic input (font raster, driver), so it records/replays on the trace tape | +| `gfx_clear` | `gfx_clear of [r, g, b]` / `gfx_clear of null` | Clear backbuffer to color; channels clamp to 0..255, then round half away from zero. `null` clears to black | +| `gfx_rect` | `gfx_rect of [x, y, w, h, r, g, b]` or `[..., a]` | Filled rectangle; intersect the whole rectangle with the drawable area in float space, then round | +| `gfx_line` | `gfx_line of [x1, y1, x2, y2, r, g, b]` | Line segment; clip with Liang-Barsky in float space, then round | +| `gfx_point` | `gfx_point of [x, y, r, g, b]` | Single pixel; an outside point draws nothing, and a surviving coordinate rounds half away from zero | +| `gfx_circle` | `gfx_circle of [cx, cy, radius, r, g, b]` | Filled circle; clip continuous spans to visible rows and columns before rounding, so a huge radius does work bounded by the drawable area | +| `gfx_rrect` | `gfx_rrect of [x, y, w, h, radius, r, g, b]` or `[..., a]` | Filled rounded rectangle; clip continuous spans to the drawable area, then round. Radius clamps to half the smaller dimension; `radius 0` = plain rect | +| `gfx_clip` | `gfx_clip of [x, y, w, h]` / `gfx_clip of null` | Intersect the continuous clip rectangle with the drawable area, then round; `null` clears it | +| `gfx_read` | `gfx_read of [x, y]` | Read back one rendered pixel as integer channels `[r, g, b]`; coordinates round half away from zero and an outside point returns null — the render-decode oracle primitive (#823). Reads the current back buffer: call after drawing, **before** `gfx_present`. Null with no window or a failed read. Nondeterministic input (font raster, driver), so it records/replays on the trace tape | | `gfx_text` | `gfx_text of [x, y, text, r, g, b]` or `[..., scale]` | Text. Proportional antialiased TTF when libSDL2_ttf + a font are available (#593); the 5x7 bitmap font otherwise — see the font note below the table | -| `gfx_text_width` | `gfx_text_width of [text, scale?]` or `of "text"` | Pixel width of `text` under the active text renderer: TTF metrics when active, `len * 6 * scale` in bitmap mode. Works before `gfx_open` | -| `gfx_text_height` | `gfx_text_height of scale?` | Pixel line height under the active text renderer: TTF font height when active, `7 * scale` in bitmap mode | +| `gfx_text_width` | `gfx_text_width of [text, scale?]` or `of "text"` | Integer pixel width under the active text renderer: TTF metrics when active, `len * 6 * scale` in bitmap mode. Scale rounds half away from zero; a rounded scale outside the C int range raises `value`. Works before `gfx_open`. | +| `gfx_text_height` | `gfx_text_height of scale?` | Integer pixel line height under the active text renderer: TTF font height when active, `7 * scale` in bitmap mode. Scale rounds half away from zero; a rounded scale outside the C int range raises `value`. | | `gfx_present` | `gfx_present of null` | Flip backbuffer to screen | -| `gfx_poll` | `gfx_poll of null` | Return next event as dict (`quit`, `keydown`, `keyup`, `mousemove`, `mousedown`, `mouseup`, `wheel`, `resize`), or null. Key, mouse, and wheel events carry `shift`/`ctrl`/`alt` (bools); wheel `x`/`y` are scroll deltas | -| `gfx_ticks` | `gfx_ticks of null` | Milliseconds since `SDL_Init` | -| `gfx_delay` | `gfx_delay of ms` | Sleep for ms (SDL-coordinated) | +| `gfx_poll` | `gfx_poll of null` | Return next event as dict (`quit`, `keydown`, `keyup`, `mousemove`, `mousedown`, `mouseup`, `wheel`, `resize`), or null. Key, mouse, and wheel events carry `shift`/`ctrl`/`alt` (bools); event coordinates, dimensions, scancodes and button codes are ints. Wheel `x`/`y` are scroll deltas | +| `gfx_ticks` | `gfx_ticks of null` | Milliseconds since `SDL_Init`, as an int; int `0` before SDL is loaded | +| `gfx_delay` | `gfx_delay of ms` | Sleep for continuous milliseconds, rounded half away from zero. A rounded delay outside 0..4294967295 raises `value`. | | `gfx_title` | `gfx_title of "text"` | Update window title | -| `gfx_fb` | `gfx_fb of [buf, w, h, x, y, scale]` | Blit buffer (palette indices 0-3) as scaled texture | -| `ppu_render_frame` | `ppu_render_frame of [mem_buf, fb_buf]` | Full Game Boy PPU render (BG/window/sprites) into framebuffer | +| `gfx_fb` | `gfx_fb of [buf, w, h, x, y, scale]` | Blit a palette buffer; `w` and `h` require exact integers in the C `int` range. The continuous destination is clipped before rounding and retains the original source-pixel mapping | +| `ppu_render_frame` | `ppu_render_frame of [mem_buf, fb_buf]` | Full Game Boy PPU render (BG/window/sprites) into framebuffer; each memory byte must be an exact integer in 0..255 | + +**Continuous geometry and colour (Q7).** Drawing coordinates, dimensions, +scale and colour channels accept ints or floats. Shapes are clipped as a whole +against the drawable area in float space before half-away-from-zero rounding; +an empty intersection draws nothing. Colour channels clamp to 0..255 before +rounding. Nonfinite numeric inputs raise `value` in both strict modes. Bitmap +text clips each glyph cell, including scales too large for the TTF integer size +API. Fractional drawing scales also use this continuous bitmap-cell path. **Wrong-typed and wrong-arity arguments (#1007).** Every builtin in this extension that takes an argument at all — the drawing calls, the text calls, the framebuffer blit, the PPU renderer, and the whole audio surface below — type-checks its arguments *before* reading them, and by default a wrong type, a short argument list, a wrong-shaped argument *container* (a -number or a string where a list belonged) or an out-of-domain value raises a -catchable `type` error naming the builtin and the shape it wanted. Under `EIGS_STRICT=0`, the -answer is byte-identical to before: the drawing calls still +number or a string where a list belonged) raises a catchable `type_mismatch` +error naming the builtin and the shape it wanted. This paragraph describes the +legacy type/shape guards. Numeric N5, Q1 and Q7 validation instead raises `value` +in both strict modes, as specified above and in each row. Under `EIGS_STRICT=0`, +the type/shape guards retain their legacy stand-ins: the drawing calls still answer `null`, the generators still answer an empty list, the device calls still answer `0` or the device id they already answered. @@ -746,8 +880,9 @@ draw a **black** rectangle where red was asked for — silently, in both modes. That read is gone in both modes; an unchecked union pun is not behaviour anything can depend on. -So be precise about what "byte-identical" covers: the **returned value** is -unchanged in every case, and so is anything the call *reports*. What a +For these legacy wrong-type probes, "byte-identical" covers the returned +stand-in value and anything the call reports; numeric result kinds follow +the individual contracts above. What a rejected call **draws** is not, and cannot be — a 48-bit pointer read as a double is a subnormal that truncates to 0, so the parent painted a shape at coordinate 0, in colour 0, or at scale 1, and this build paints nothing at @@ -784,7 +919,7 @@ raises under `EIGS_STRICT`, and a pinned documented answer does not. **Text rendering and fonts (#593).** `gfx_text` lazily loads `libSDL2_ttf-2.0.so.0` on first use and renders proportional antialiased text (`TTF_RenderUTF8_Blended`) when both the library and a font file are -present. Font selection: the `EIGS_GFX_FONT` environment variable +present and the drawing scale is an integer supported by its size API. Font selection: the `EIGS_GFX_FONT` environment variable (absolute path to a `.ttf`) wins; when it is set but unreadable the runtime warns once and stays on the bitmap font (a nonexistent path is the deterministic off-switch). Otherwise a short list of common system @@ -796,6 +931,37 @@ should measure through `gfx_text_width`/`gfx_text_height` (as `lib/ui.eigs` does) rather than assuming the `6 * scale` monospace advance. Text rendering is output-only: no trace-tape records in either mode. +## EigenStore + +EigenStore stores record dicts in named collections. A nonempty string `_id` +field supplies an explicit record key; otherwise `store_put` generates one. + +| Name | Signature | Description | +|------|-----------|-------------| +| `store_open` | `store_open of path` | Open/create a store; return a handle dict with integer id/generation fields. | +| `store_close` | `store_close of handle` | Flush and close the exact handle; return `null`. | +| `store_collections` | `store_collections of handle` | Return collection names for the exact handle. | +| `store_count` | `store_count of [handle, collection]` | Return the integer live-record count (zero for an unknown collection). | +| `store_drop` | `store_drop of [handle, collection]` | Drop a collection; return whether it existed. | +| `store_put` | `store_put of [handle, collection, record]` | Write a record dict, add its `_id` field, and return the string key. Int64 fields are stored exactly. | +| `store_get` | `store_get of [handle, collection, key]` | Return the record dict, or `null` for an absent key. Numeric keys convert exactly to int64 and use their full decimal digits. | +| `store_delete` | `store_delete of [handle, collection, key]` | Delete the record and return `true`, or `false` when absent. Numeric keys follow the same rule as `store_get`. | +| `store_update` | `store_update of [handle, collection, key, record]` | Replace the record and return `true`, or `false` when absent. Numeric keys follow the same rule as `store_get`. An oversized or non-dict replacement leaves the old record intact. | +| `store_query` | `store_query of [handle, collection]` | Return the collection's records as a list of dicts. | + +Store handles carry integer `_store_id` and `_store_gen` fields. Every store operation requires exact, in-range integer handle fields; integral floats convert, while fractional, non-finite or out-of-range numeric fields raise `value` before store access (R1.6f-D1). `store_count` returns an integer. + +`store_get`, `store_delete`, and `store_update` accept string keys or exact +int64 numeric keys. Integral floats convert; fractional or out-of-range floats +raise `value` before lookup or mutation. Numeric keys retain all int64 digits. +Stored decimal integer text within int64 decodes as an int; a decimal point or +exponent selects a float. Buffer payloads remain floats. Float persistence keeps +its existing text format until R1.7, so a whole float such as `3.0` may read back +as an int during this transition. + +An invalid, closed, or stale store handle raises `value`. For a complete +example, see [stored records](SPEC.md#stored-records). + ## Optional: Database Extension Requires full build with libpq. PostgreSQL client. @@ -803,9 +969,9 @@ Requires full build with libpq. PostgreSQL client. | Name | Signature | Description | |------|-----------|-------------| | `db_connect` | `db_connect of null` | Connect via DATABASE_URL env var; returns a status JSON, never raises | -| `db_query_value` | `db_query_value of sql` or `db_query_value of [sql, p1, p2]` | Execute query, return row 0 col 0 typed by its SQL type; `null` for SQL NULL, `""` for no rows | +| `db_query_value` | `db_query_value of sql` or `db_query_value of [sql, p1, p2]` | Execute query, return row 0 col 0 typed by its SQL type: exact int64 for integer columns, float for finite real columns; `null` for SQL NULL, `""` for no rows | | `db_execute` | `db_execute of sql` or `db_execute of [sql, p1, p2]` | Execute command with optional params; returns `"ok"` | -| `db_query_json` | `db_query_json of sql` or `db_query_json of [sql, p1, p2]` | Execute query, return all rows as a JSON array of objects, each value typed by its SQL type | +| `db_query_json` | `db_query_json of sql` or `db_query_json of [sql, p1, p2]` | Execute query, return all rows as a JSON array of objects, each value typed by its SQL type; integer columns retain exact int64 digits | ### Failures raise (#888) @@ -848,7 +1014,7 @@ Values carry their column's SQL type rather than arriving as strings: |---|---|---| | NULL (any column type) | `null` | Distinct from `""` — checked before the type | | `boolean` | `true` / `false` → `true` / `false` (bool) | `if row.is_admin:` means what it reads as | -| `smallint`, `integer`, `bigint`, `oid` | number | `bigint` past 2^53 **raises** — see below | +| `smallint`, `integer`, `bigint`, `oid` | int | Exact across the full SQL type's range, including `bigint` beyond 2^53 | | `real`, `double precision` | number | `NaN`/`Infinity` arrive as strings; JSON has no literal for them | | `numeric` | **string** | Deliberate — see below | | everything else | string | text, date, uuid, json, … unchanged | @@ -858,20 +1024,18 @@ row's value: a column that decoded as a number for row 1 and a string for row 100 would break `row.n + 1` on data rather than on schema. **`numeric` stays a string.** It is PostgreSQL's arbitrary-precision decimal -— the money type — and an EigenScript number is a binary double, which -cannot hold `numeric(38,10)` or even `0.1` exactly. Preserving the digits +— the money type — and neither int64 nor binary float can hold every +`numeric(38,10)` value exactly. Preserving the digits is the safe default; `SELECT amount::float8` is the one-token opt-in to a number when approximate is fine. Note `avg()` and `sum(numeric)` return `numeric`, so those want the cast; `count(*)` and `sum(integer)` return -`bigint` and are already numbers. +`bigint` and arrive as ints. -**A `bigint` past 2^53 raises** instead of silently rounding, naming the -column and the fix: - -``` -Error query.eigs:3: db: column 'id' value 9007199254740993 exceeds the exact-integer -range of a number (2^53); select it as text (id::text) to keep the digits -``` +**Integer columns preserve every int64 digit.** `db_query_value` returns an +int directly, and `db_query_json` writes the exact decimal digits so that +`json_decode` returns an int. This includes both int64 boundaries and values +such as `9007199254740993`; no cast to text is needed to preserve an integer +key. Numeric parameters also bind ints as exact digits. Before this, every value was a string: SQL `false` arrived as `"f"`, which is a non-empty string and therefore **truthy**, so `if row.is_admin:` passed @@ -887,9 +1051,9 @@ Requires full build. Transformer model inference and training. | `eigen_model_load` | `eigen_model_load of "path.json"` | Load model weights from JSON | | `eigen_model_loaded` | `eigen_model_loaded of null` | `true` if model loaded, `false` otherwise | | `eigen_model_info` | `eigen_model_info of null` | JSON with model config and stats | -| `eigen_generate` | `eigen_generate of [prompt, temp, max_tokens]` | Generate text from prompt. Every prompt token id must be in `[0, vocab_size)` or it raises `index_range`. Raises when the prompt exceeds the model's `max_seq_len`. | -| `eigen_eval_loss` | `eigen_eval_loss of [prompt, target]` | Return the target token's cross-entropy loss. Every prompt and target token id must be in `[0, vocab_size)` or it raises `index_range`. Raises when the prompt exceeds the model's `max_seq_len`. | -| `native_train_step_builtin` | `native_train_step_builtin of [input, output, lr]` | Single training step. Every input and output token id must be in `[0, vocab_size)` or it raises `index_range` before training. Raises when the combined input and output length exceeds the model's `max_seq_len`. | +| `eigen_generate` | `eigen_generate of [prompt, temp, max_tokens]` | Token ids and max_tokens follow N5 then C int range; output token ids are ints. Generate text from prompt. Every prompt token id must be in `[0, vocab_size)` or it raises `index_range`. Raises when the prompt exceeds the model's `max_seq_len`. | +| `eigen_eval_loss` | `eigen_eval_loss of [prompt, target]` | Token ids follow N5 then C int range; loss is float. Return the target token's cross-entropy loss. Every prompt and target token id must be in `[0, vocab_size)` or it raises `index_range`. Raises when the prompt exceeds the model's `max_seq_len`. | +| `native_train_step_builtin` | `native_train_step_builtin of [input, output, lr]` | Token ids follow N5 then C int range; learning rate remains float. Single training step. Every input and output token id must be in `[0, vocab_size)` or it raises `index_range` before training. Raises when the combined input and output length exceeds the model's `max_seq_len`. | | `model_save_weights` | `model_save_weights of "path.json"` | Save model weights to JSON | | `model_load_weights` | `model_load_weights of "path.json"` | Load model weights (alias) | @@ -898,23 +1062,23 @@ Requires full build. Transformer model inference and training. | Name | Signature | Description | |------|-----------|-------------| | `spawn` | `spawn of fn` or `spawn of [fn, arg1, ...]` | Spawn a thread running `fn`. Bare-fn form passes no args; list form passes `arg1...` positionally. Missing trailing params bind to `null`. Extra args follow the direct-call rule (docs/SPEC.md, Functions): a callee of 2+ parameters raises `call passes 3 arguments but the callee takes 2` at the spawn site (`spawn of [two, 1, 2, 99]`), and a 1-parameter callee receives the whole list (`spawn of [one, 5, 6]` binds `[5, 6]`). Args are shared by reference (unlike channel sends, which copy) — see thread-safety note below. Returns a thread handle dict. | -| `thread_join` | `thread_join of handle` | Block until the thread completes. Returns the thread function's copied return value, or re-raises a worker's uncaught error with the same kind and message. Catching that raise observes the worker failure, matching `task_join`. | +| `thread_join` | `thread_join of handle` | Block until the thread completes. Returns the thread function's copied return value, or re-raises a worker's uncaught error with the same kind and message. Catching that raise observes the worker failure, matching `task_join`. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | | `channel` | `channel of null` | Create a bounded FIFO channel (capacity 64). Returns a channel handle dict. | -| `send` | `send of [channel, value]` | Send a value to the channel. Blocks if full. Sending to a **closed** channel raises a catchable `value` error (rather than silently dropping the value); `recv` on a closed empty channel returns `null` (EOF-like). | -| `recv` | `recv of channel` | Receive a value from the channel. **Blocks** until a value is available or the channel is closed. | -| `try_recv` | `try_recv of channel` | Non-blocking receive. Returns the value if available, `null` if the channel is empty. | -| `recv_timeout` | `recv_timeout of [channel, ms]` | Bounded-wait receive. Returns the value if one arrives before `ms` milliseconds elapse, else `null`. A close while waiting also returns `null`. Fractional `ms` is honored (ns precision on Linux); negative `ms` degenerates to a `try_recv`. | -| `close_channel` | `close_channel of channel` | Close the channel. Wakes all blocked senders/receivers. | -| `channel_closed` | `channel_closed of channel` | Returns `true` if closed, `false` otherwise. An unknown or reclaimed channel is closed (`true`). A value that is not a channel handle raises by default; under `EIGS_STRICT=0` it answers `true` (#971). | +| `send` | `send of [channel, value]` | Send a value to the channel. Blocks if full. Sending to a **closed** channel raises a catchable `value` error (rather than silently dropping the value); `recv` on a closed empty channel returns `null` (EOF-like). Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `recv` | `recv of channel` | Receive a value from the channel. **Blocks** until a value is available or the channel is closed. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `try_recv` | `try_recv of channel` | Non-blocking receive. Returns the value if available, `null` if the channel is empty. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `recv_timeout` | `recv_timeout of [channel, ms]` | Bounded-wait receive. Returns the value if one arrives before `ms` milliseconds elapse, else `null`. A close while waiting also returns `null`. negative `ms` degenerates to a `try_recv`. The timeout rounds half away from zero to milliseconds (Q7 magnitude); values outside the C long range raise `value`. The previous one-year cap is removed. | +| `close_channel` | `close_channel of channel` | Close the channel. Wakes all blocked senders/receivers. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `channel_closed` | `channel_closed of channel` | Returns `true` if closed, `false` otherwise. An unknown or reclaimed channel is closed (`true`). A value that is not a channel handle raises by default; under `EIGS_STRICT=0` it answers `true` (#971). Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | | `task_spawn` | `task_spawn of fn` or `task_spawn of [fn, arg1, ...]` | Create a cooperative task (#408) running `fn` on the single OS thread — deterministic by construction, unlike `spawn`'s OS thread. Args are deep-COPIED (share-nothing, like channel sends), not shared by reference. Returns a numeric task id. A ready task can run when the current task explicitly yields, suspends in a blocking scheduler operation, or finishes. | -| `task_alive` | `task_alive of id` | Returns `true` while the task is runnable or suspended, `false` once it has finished (or for an unknown id). | +| `task_alive` | `task_alive of id` | Returns `true` while the task is runnable or suspended, `false` once it has finished (or for an unknown id). Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | | `task_self` | `task_self of null` | The **running task's own id** (a number, in the same integer space `task_spawn` returns; the main task is 0, including before any task has been spawned). Lets a worker hand out its own id as a reply address — the message-link pattern a mailbox otherwise cannot express (#526). Deterministic — reads scheduler state, records no nondeterminism. | | `task_yield` | `task_yield of null` | Cooperatively hand control to the next ready task; this task resumes round-robin. A no-op when no task has been spawned. Forbidden inside a nested evaluation (raises `value`). | -| `task_join` | `task_join of id` | Block until task `id` finishes, then return its deep-copied result — or re-raise its uncaught error (as the same `{kind, message, line}` it died with). Joining an already-finished task returns immediately; an unknown id (or self) returns null. All tasks blocked with none runnable is a `deadlock` error, not a hang — catchable by a `try`/`catch` around the join on the main task (`e.kind == "deadlock"`); terminal only if unhandled. | -| `task_send` | `task_send of [id, value]` | Append a deep-copied message to task `id`'s unbounded FIFO mailbox, waking it if it waits in `task_recv`. Returns `true` if delivered, `false` if `id` is finished/unknown (a silent drop — send-to-dead is never an error). Never blocks. | +| `task_join` | `task_join of id` | Block until task `id` finishes, then return its deep-copied result — or re-raise its uncaught error (as the same `{kind, message, line}` it died with). Joining an already-finished task returns immediately; an unknown id (or self) returns null. All tasks blocked with none runnable is a `deadlock` error, not a hang — catchable by a `try`/`catch` around the join on the main task (`e.kind == "deadlock"`); terminal only if unhandled. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `task_send` | `task_send of [id, value]` | Append a deep-copied message to task `id`'s unbounded FIFO mailbox, waking it if it waits in `task_recv`. Returns `true` if delivered, `false` if `id` is finished/unknown (a silent drop — send-to-dead is never an error). Never blocks. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | | `task_recv` | `task_recv of null` | Return the next message from this task's mailbox, or block cooperatively until one arrives. Forbidden inside a nested evaluation (raises `value`). | -| `task_kill` | `task_kill of id` | Tear down task `id`: drop its mailbox, mark it dead, wake any joiner with an `interrupt` error. Returns `true` if killed, `false` for a finished/unknown/self target. | -| `task_detach` | `task_detach of id` | Mark task `id` **fire-and-forget** (the pthread-detach precedent, #530): it is reaped the moment it finishes — or immediately if already finished — releasing its handle slot for reuse, so task-per-message workloads are bounded by *concurrent* tasks, not lifetime spawns. No join can ever see a detached task's failure, so its uncaught death prints its trace and **stops the program** (exit 1) at main's next scheduling point; detaching a task that already died of an uncaught error nothing joined stops the program at this call instead of discarding the error. Neither stop is catchable (#1736, [CONCURRENCY.md](CONCURRENCY.md#when-a-task-dies-of-an-uncaught-error-1736)). A reaped id reads as unknown afterwards (`task_join` null, `task_alive` `false`). A task may detach itself: `task_detach of (task_self of null)`. Returns 1, or 0 for main/unknown. | +| `task_kill` | `task_kill of id` | Tear down task `id`: drop its mailbox, mark it dead, wake any joiner with an `interrupt` error. Returns `true` if killed, `false` for a finished/unknown/self target. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | +| `task_detach` | `task_detach of id` | Mark task `id` **fire-and-forget** (the pthread-detach precedent, #530): it is reaped the moment it finishes — or immediately if already finished — releasing its handle slot for reuse, so task-per-message workloads are bounded by *concurrent* tasks, not lifetime spawns. No join can ever see a detached task's failure, so its uncaught death prints its trace and **stops the program** (exit 1) at main's next scheduling point; detaching a task that already died of an uncaught error nothing joined stops the program at this call instead of discarding the error. Neither stop is catchable (#1736, [CONCURRENCY.md](CONCURRENCY.md#when-a-task-dies-of-an-uncaught-error-1736)). A reaped id reads as unknown afterwards (`task_join` null, `task_alive` `false`). A task may detach itself: `task_detach of (task_self of null)`. Returns 1, or 0 for main/unknown. Handle ids and generations require exact integers (integral floats are accepted); fractions raise `value` before effects (N5). | | `task_sleep` | `task_sleep of ticks` | Suspend this task until the **virtual clock** advances by `ticks`. The clock is logical (discrete-event): it only jumps forward — to the earliest sleeper — when nothing else is runnable, so sleeping stays deterministic, not wall-clock. A negative sleep is treated as 0. A no-op when no task has been spawned. | | `task_sched_seed` | `task_sched_seed of n` | Install a scheduling **seed**: the scheduler switches from FIFO round-robin to picking the next ready task from a seeded, platform-independent PRNG. Same seed ⇒ same interleaving (byte-identical run + replay, zero tape nondeterminism); a different seed explores a different ordering — the lever a deterministic simulation tester uses to search interleavings. No seed ⇒ unchanged FIFO. Typically called once at program start. Returns null. | @@ -931,8 +1095,8 @@ receiver. | Name | Signature | Description | |------|-----------|-------------| -| `nearest_in_range` | `nearest_in_range of [entities, x, y, range, world_w, world_h]` | Position query: find the nearest active entity to `(x, y)` within `range` using torus (wrapping) distance. Because the query has no entity identity, no candidate is implicitly excluded. Returns `{"index", "dist", "dx", "dy"}` or `null`. Optional extra args: custom key names `[..., px_key, py_key, active_key]`. | -| `nearest_in_range_all` | `nearest_in_range_all of [entities, range, world_w, world_h]` | For every entity `i`, return its nearest **other** active entity within `range` using torus distance, or `null`; entity `i` is never its own candidate. The result list has one entry per input entity. Optional trailing custom key names `[..., px_key, py_key, active_key]`. | +| `nearest_in_range` | `nearest_in_range of [entities, x, y, range, world_w, world_h]` | Position query: find the nearest active entity to `(x, y)` within `range` using torus (wrapping) distance. Because the query has no entity identity, no candidate is implicitly excluded. Returns `{"index", "dist", "dx", "dy"}` or `null`. Optional extra args: custom key names `[..., px_key, py_key, active_key]`. The index field is an int; distance and displacements are floats. | +| `nearest_in_range_all` | `nearest_in_range_all of [entities, range, world_w, world_h]` | For every entity `i`, return its nearest **other** active entity within `range` using torus distance, or `null`; entity `i` is never its own candidate. The result list has one entry per input entity. Optional trailing custom key names `[..., px_key, py_key, active_key]`. The index field is an int; distance and displacements are floats. | The per-entity result and self-exclusion are executable here: @@ -949,26 +1113,106 @@ print of [neighbors[0].index, neighbors[1].index, neighbors[2]] | Name | Signature | Description | |------|-----------|-------------| -| `audio_open` | `audio_open of [freq, channels]` or `of null` | Open the mixer playback device. Defaults `[44100, 1]`. Returns the device id (`>= 2`), or `0` when SDL/audio is unavailable. Rates above **71,582,788 Hz** raise `value`: at every accepted rate, a generator's 30-second maximum has an `int`-sized sample count (#1821). Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007 — they used to be read without a type check, so a string opened the device against a garbage spec and still answered a real id, taking the device with it). A **short or non-list** argument also raises by default; with `EIGS_STRICT=0`, it opens at the defaults and returns the device id or `0` if unavailable (#1007). `of null` is still the defaults. | -| `audio_sine` | `audio_sine of [freq, duration, amplitude]` | Generate a sine-wave sample list. `duration` is limited to 30 seconds; a longer duration raises `value`. | -| `audio_square` | `audio_square of [freq, duration, amplitude]` | Generate a square-wave sample list. `duration` is limited to 30 seconds; a longer duration raises `value`. | -| `audio_saw` | `audio_saw of [freq, duration, amplitude]` | Generate a sawtooth-wave sample list. `duration` is limited to 30 seconds; a longer duration raises `value`. | -| `audio_noise` | `audio_noise of [duration, amplitude]` | Generate a white-noise sample list. The samples come from the stream behind `random`, so `seed_random` pins them, and they are trace-recorded: under `EIGS_REPLAY` the recorded samples are served (#1803). `duration` is limited to 30 seconds; a longer duration raises `value` and records nothing. | -| `audio_sweep` | `audio_sweep of [freq_start, freq_end, duration, amplitude, waveform]` | Generate a frequency sweep with continuous phase. `waveform`: 0=sine, 1=sawtooth. Returns a sample list. `duration` is limited to 30 seconds; a longer duration raises `value`. | -| `audio_play` | `audio_play of samples` | Play a clip once on a free mixer channel (oldest finite channel recycled when all 16 are busy). Returns the channel id, or `0` on bad args / closed device. A non-numeric element in `samples` raises a `type_mismatch` error (#1007 — it used to be coerced to 0, so a wrong-typed list played silence on a real channel id), and so does a `samples` that is not a list or buffer at all (#1007 — `audio_play of 42` answered the documented "nothing to play" `0`, indistinguishable from an empty clip). `of null` still plays nothing. | -| `audio_play_loop` | `audio_play_loop of [samples, loops]` | Play `samples` `loops` times on one mixer channel; `loops == -1` loops forever (the mixer rewinds — no memory multiplication). Returns the channel id, or `0` on bad args / closed device. `loops` must be a number equal to `-1` or in `1..10000`; anything else raises by default and answers `0` under `EIGS_STRICT=0` (#1007), and so does a `samples` slot that is not a list or buffer. | -| `audio_volume` | `audio_volume of [channel, vol]` | Live per-channel volume, `0.0`–`4.0`. Returns `1`, or `0` on a bad/inactive channel. A non-numeric `channel` or `vol` raises by default and answers `0` under `EIGS_STRICT=0` (#1007); an out-of-range channel is simply inactive and stays quiet. | -| `audio_stop` | `audio_stop of channel` | Stop one mixer channel. Returns `1`, or `0` on a bad/inactive channel. A non-numeric `channel` raises by default and answers `0` under `EIGS_STRICT=0` (#1007). | -| `audio_capture_open` | `audio_capture_open of [freq, channels]` | Open the recording (microphone) device and start capturing (#579). Defaults `[44100, 1]`; SDL converts to exactly the requested format. Returns the device id, or `0` when SDL/capture is unavailable. Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007), and so does a **short or non-list** argument, which used to open at the defaults and answer a real device id. `of null` is still the defaults. Re-opening closes the previous capture device. Trace-recorded — under `EIGS_REPLAY` no real device is opened. | +| `audio_open` | `audio_open of [freq, channels]` or `of null` | Open the mixer playback device. Defaults `[44100, 1]`. `freq` follows N5 and the C int range. Rates above **71,582,788 Hz** raise `value`: at every accepted rate, a generator's 30-second maximum has an `int`-sized sample count (#1821). `channels` follows N5 and the domain `1..255` (R1.6e-D1). Integral floats are accepted; fractions and out-of-domain values raise `value` before device access or trace recording. Device ids and the unavailable result are ints. Returns the device id (`>= 2`), or `0` when SDL/audio is unavailable. Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007 — they used to be read without a type check, so a string opened the device against a garbage spec and still answered a real id, taking the device with it). A **short or non-list** argument also raises by default; with `EIGS_STRICT=0`, it opens at the defaults and returns the device id or `0` if unavailable (#1007). `of null` is still the defaults. | +| `audio_pause` | `audio_pause of flag` or `of null` | Pause (`1`) or unpause (`0`) the mixer device. The flag follows N5 and the C int range; an integral float is accepted, while a fraction or unrepresentable value raises `value` even when no device is open. `null` defaults to pause. Returns null. | +| `audio_queue_size` | `audio_queue_size of null` | Return the exact int count of unplayed bytes across finite mixer channels; channels looping forever are excluded. Returns int `0` when no device is open. | +| `audio_music_play` | `audio_music_play of [path, loops]` | Play a music file through SDL_mixer. The optional loop count follows N5 and the C int range; `-1` loops forever, `0` plays once, and `N` plays `N+1` times. A fraction or unrepresentable loop count raises `value` before loading the file. Returns a bool indicating success. | +| `audio_mix` | `audio_mix of [samples_a, samples_b]` | Add two lists of finite numeric samples, padding the shorter list with zeros, and clamp to `[-1, 1]`. Returns float samples; int inputs convert to float. | +| `audio_gain` | `audio_gain of [samples, volume]` | Multiply a list of finite numeric samples by a finite numeric gain and clamp to `[-1, 1]`. Returns float samples; int inputs convert to float. | +| `audio_sine` | `audio_sine of [freq, duration, amplitude]` | Generate a sine-wave list of float samples. Duration is limited to 30 seconds; a longer duration raises `value`. | +| `audio_square` | `audio_square of [freq, duration, amplitude]` | Generate a square-wave list of float samples. Duration is limited to 30 seconds; a longer duration raises `value`. | +| `audio_saw` | `audio_saw of [freq, duration, amplitude]` | Generate a sawtooth-wave list of float samples. Duration is limited to 30 seconds; a longer duration raises `value`. | +| `audio_noise` | `audio_noise of [duration, amplitude]` | Generate a white-noise list of float samples. `seed_random` pins the samples; trace replay serves the recorded samples (#1803). Duration is limited to 30 seconds; a longer duration raises `value` and records nothing. | +| `audio_sweep` | `audio_sweep of [freq_start, freq_end, duration, amplitude, waveform]` | Generate a frequency sweep as float samples with continuous phase. The waveform selector follows N5: zero selects sine; any other exact integer selects sawtooth. Duration is limited to 30 seconds; a longer duration raises `value`. | +| `audio_envelope` | `audio_envelope of [samples, attack, decay, sustain_level, release]` | Return float samples with an ADSR envelope. Attack, decay and release times are continuous seconds: their sample counts round half away from zero and raise `value` outside the C int range. | +| `audio_play` | `audio_play of samples` | Play a clip once on a free mixer channel (oldest finite channel recycled when all 16 are busy). Returns the channel id, or `0` on bad args / closed device. A non-numeric element in `samples` raises a `type_mismatch` error (#1007 — it used to be coerced to 0, so a wrong-typed list played silence on a real channel id), and so does a `samples` that is not a list or buffer at all (#1007 — `audio_play of 42` answered the documented "nothing to play" `0`, indistinguishable from an empty clip). `of null` still plays nothing. Numeric channel answers are ints. Samples cross the finite data boundary, clamp to [-1,1], then round half away from zero to PCM16; stored lenient NaN and every infinity raise `value` (N13). | +| `audio_play_loop` | `audio_play_loop of [samples, loops]` | Play `samples` `loops` times on one mixer channel; `loops == -1` loops forever (the mixer rewinds — no memory multiplication). Returns the channel id, or `0` on bad args / closed device. `loops` must be a number equal to `-1` or in `1..10000`; anything else raises by default and answers `0` under `EIGS_STRICT=0` (#1007), and so does a `samples` slot that is not a list or buffer. Loop counts follow N5, then the C int range and the existing -1 or 1..10000 playback policy. Channel answers are ints; samples use the same finite/Q7 PCM conversion as `audio_play`. | +| `audio_volume` | `audio_volume of [channel, vol]` | Live per-channel volume, `0.0`–`4.0`. Returns `1`, or `0` on a bad/inactive channel. A non-numeric `channel` or `vol` raises by default and answers `0` under `EIGS_STRICT=0` (#1007); an out-of-range channel is simply inactive and stays quiet. Channel numbers follow N5 and the C int range. Continuous gain clamps to [0,4], then rounds half away from zero; numeric status answers are ints. | +| `audio_stop` | `audio_stop of channel` | Stop one mixer channel. Returns int `1`, or int `0` on an inactive channel. `channel` follows N5 and the C int range: integral floats are accepted; a fraction or unrepresentable value raises `value` before the device is consulted. A non-numeric `channel` raises by default and answers int `0` under `EIGS_STRICT=0` (#1007). | +| `audio_capture_open` | `audio_capture_open of [freq, channels]` | Open the recording (microphone) device and start capturing (#579). Defaults `[44100, 1]`; SDL converts to exactly the requested format. `freq` follows N5 and the C int range; `channels` follows N5 and the domain `1..255` (R1.6e-D1). Integral floats are accepted; fractions and out-of-domain values raise `value` before device access or trace recording. Device ids and the unavailable result are ints. Returns the device id, or `0` when SDL/capture is unavailable. Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007). A **short or non-list** argument raises by default; with `EIGS_STRICT=0`, it opens at the defaults and returns the device id or `0` if unavailable (#1007). `of null` is still the defaults. Re-opening closes the previous capture device. Trace-recorded — under `EIGS_REPLAY` no real device is opened. | | `audio_capture_read` | `audio_capture_read of null` | Drain samples accumulated since the last read as a **buffer** of floats in `[-1, 1]` (interleaved when `channels > 1`). At most 2048 samples per call — loop until the returned buffer is empty to drain fully (keeps each trace record replayable). Empty buffer = nothing new yet; `null` = no capture device open. Trace-recorded — replay serves the recorded samples, never a live microphone. | | `audio_capture_close` | `audio_capture_close of null` | Stop and close the recording device, dropping undrained samples. Safe to call twice or with no device open. | -| `audio_stream_open` | `audio_stream_open of [freq, channels]` | Open the live streaming playback device (queue mode, F-DS-17 — for on-the-fly synthesis like musical typing). Coexists with the `audio_open` mixer device. Defaults `[44100, 1]`. Returns the device id (`>= 2`), or `0` when SDL/audio is unavailable. Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007 — they used to skip the override, open at 44100/1 and answer a real id, so a caller that asked for 48000 was told it got it). A **short or non-list** argument raises for the same reason: `audio_stream_open of [48000]` answered device id 2 opened at 44100/1. `of null` is still the defaults. Re-opening closes the previous stream device. | -| `audio_stream_push` | `audio_stream_push of samples` | Queue a block of float samples `[-1, 1]` (**list** or **buffer**) onto the live stream. Same size cap / clamp as `audio_play`. Pure output sink (not trace-recorded). Returns `true` on success, `false` on a closed device or bad shape; a `samples` that is not a list or buffer raises by default and answers `false` under `EIGS_STRICT=0` (#1007). | -| `audio_stream_queued` | `audio_stream_queued of null` | Samples still buffered (not yet played) on the live stream — the refill pump pushes another block only while this stays under its latency target. Returns `0` when no stream is open. Trace-recorded (a live, timing-dependent value) — replay serves the recorded depth, keeping the session deterministic. | +| `audio_stream_open` | `audio_stream_open of [freq, channels]` | Open the live streaming playback device (queue mode, F-DS-17 — for on-the-fly synthesis like musical typing). Coexists with the `audio_open` mixer device. Defaults `[44100, 1]`. `freq` follows N5 and the C int range; `channels` follows N5 and the domain `1..255` (R1.6e-D1). Integral floats are accepted; fractions and out-of-domain values raise `value` before device access or trace recording. Device ids and the unavailable result are ints. Returns the device id (`>= 2`), or `0` when SDL/audio is unavailable. Non-numeric `freq`/`channels` raise by default and answer `0` under `EIGS_STRICT=0` (#1007 — they used to skip the override, open at 44100/1 and answer a real id, so a caller that asked for 48000 was told it got it). A **short or non-list** argument raises for the same reason: `audio_stream_open of [48000]` answered device id 2 opened at 44100/1. `of null` is still the defaults. Re-opening closes the previous stream device. | +| `audio_stream_push` | `audio_stream_push of samples` | Queue a block of float samples `[-1, 1]` (**list** or **buffer**) onto the live stream. Same size cap / clamp as `audio_play`. Pure output sink (not trace-recorded). Returns `true` on success, `false` on a closed device or bad shape; a `samples` that is not a list or buffer raises by default and answers `false` under `EIGS_STRICT=0` (#1007). Buffer samples follow the finite data boundary (N13); PCM clamps to [-1,1] and rounds half away from zero. | +| `audio_stream_queued` | `audio_stream_queued of null` | Int sample count still buffered (not yet played) on the live stream — the refill pump pushes another block only while this stays under its latency target. Returns int `0` when no stream is open. Trace-recorded (a live, timing-dependent value) — replay serves the recorded depth, keeping the session deterministic. | | `audio_stream_clear` | `audio_stream_clear of null` | Drop any buffered audio on the live stream (flush for a panic / all-notes-off). Safe with no device. | | `audio_stream_close` | `audio_stream_close of null` | Stop and close the live stream device, dropping buffered audio. Safe to call twice or with no device open. | +The sine, saw, square, noise and sweep generators reject non-finite durations +and rounded sample counts outside the C int range. Their validated counts +still truncate: at 44100 Hz, 0.00101 seconds produces 44 samples. The sample +count rounding change is staged for R2.6 (RULINGS B5). + ## Internal (sanitizer builds only) | Name | Signature | Description | |------|-----------|-------------| + +Buffer sizes, indices, offsets and counts follow N5. Byte inputs to +`str_from_bytes`, `f64_from_bytes`, `inflate` and `buf_from_pcm16le` must be +exact integers in 0..255; fractions and out-of-range bytes raise `value`. +`buf_len`, `f64_to_bytes` and `inflate` return integer counts or bytes; +buffer storage and scalar buffer reads are floats. + +Buffer construction and `reshape` check dimensions before narrowing to C integers. +`buf_copy`, `buf_fill`, `buf_mix`, `buf_scale_range`, `buf_peak`, `buf_dot`, +`buf_deinterleave` and `buf_resample_linear` use exact N5 windows/counts. +`buf_set`, `buf_fill` and `buf_from_list` convert stored integers to float (Q2). +`buf_to_pcm16le` retains its clamp and half-away-from-zero rounding. + +Model token ids and generation limits follow N5 and the C int range; +fractional ids or token counts raise `value`. `eigen_generate` returns int +token ids, while `eigen_eval_loss` returns a float. Temperature, top_p and +training learning rates remain float quantities. Model JSON weights stay +float data; the loader status strings are unchanged. + +`mean`, `norm`, `buf_get`, `buf_peak`, `buf_dot` and byte/PCM codec input +readers refuse non-finite buffer cells with `value` in both strict modes (N13). + +`gfx_fb` palette values and `ppu_render_frame` memory bytes use the Q1 byte +reader: exact integers in 0..255. Fractions and out-of-range values raise +`value`; a stored NaN or infinity raises at the data boundary in both strict +and lenient modes, without normalising the stored payload again. + +### Stored buffer data boundary + +Buffer kernels can retain non-finite data. Scalar indexing and iteration +raise `value` when that data is read, in every strict mode (N13). + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +try: + print of p[0] +catch e: + print of e.kind +``` +```output +value +``` + +### Buffer equality + +Buffers compare their stored doubles with IEEE equality: NaN differs from +everything, even itself; infinities compare by sign; signed zeros are equal. +Equality never raises or changes math flags, including under strict mode. + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +print of (p == p) +print of (p == g) +sgd_update of [p, p, 1.0] +print of (p == p) +``` +```output +true +false +false +``` + +N14 record and SQL writers use the same N6 float text and exact int64 digits. +The executed cases are `tests/phase2/rows/N14.store-exact-digits.sh` and +`tests/test_db_params.c` (#1762). diff --git a/docs/CI.md b/docs/CI.md index d72b092ca..80ab840c2 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -747,6 +747,7 @@ file: | `Scorecard analysis` (`scorecard.yml`) | advisory | runs on push, schedule and `branch_protection_rule`, never on a PR; a posture score | | `Analyze (python)`, `Analyze (javascript-typescript)` | advisory | CodeQL *default setup* (a GitHub app, not a workflow file) over the repo's non-C code; the C analysis that blocks is `Analyze C` | | `issue-triage / …` (`issue-triage.yml`), `release.yml` jobs | advisory | not triggered by PRs or pushes to `main` | +| `phase2 (MAP rows + lib differential)` (`phase2.yml`) | advisory | #1758's integration branch only: a push to `phase2-numbers` or `workflow_dispatch`, never a PR or the queue; `tools/phase2_ci.sh` checks the MAP rows of every landed round and the stdlib int differential | ## Coverage on pull requests @@ -769,8 +770,10 @@ result pins; changing it or the driver selects every row. The formatter suite uses `eigenscript --dump-tokens FILE` as its safe-mode oracle. The command performs no execution and writes one tab-separated row -per lexer token: numeric token type, value (strings/identifiers as hexadecimal -bytes and numbers as hexadecimal floating point), and one-based source line. +per lexer token: numeric token type, value, and one-based source line. +Strings and identifiers use hexadecimal bytes. Numeric values carry their +kind: `i` followed by exact signed decimal digits for an int, or `f` followed +by C hexadecimal floating-point text for a float. Columns are deliberately absent, while `INDENT` and `DEDENT` remain tokens; this makes horizontal whitespace and comment-column changes immaterial but detects changes to literal bytes, physical lines, or block structure. Lexer @@ -804,7 +807,9 @@ need no suite run — plus changed-gate self-tests, exit 1 on any failure; the d joins when a binary exists. Its gate list is the only copy: the `gate-selftests` CI job runs the same script. Among them, `tools/enrolment_check.sh` (also suite `[99ab]`) fails when a `tests/*.sh` or -`tests/*.py` is invoked by no suite section, workflow step or enrolled script; -exceptions live in `tests/enrolment_exemptions.txt` with a reason each. +`tests/*.py` is invoked by no suite section, workflow step or enrolled script, +and when a `tools/` entry point (a top-level `tools/*.sh` or `tools/*.py`, or a +`tools/

/` package) is reached from no workflow step that way; exceptions +live in `tests/enrolment_exemptions.txt` with a reason each. Counts that grow with the suite (child-script sites, doc-claim populations) are floors: adding a test needs no count edit, a drop is red. diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md index 5fd50bc79..aa861f137 100644 --- a/docs/COMPARISON.md +++ b/docs/COMPARISON.md @@ -1,5 +1,21 @@ # EigenScript compared to Python, JavaScript, Rust, and Lisp + +Width arithmetic uses explicit wrapping, checked and saturating builtins. +N5 converts integral floats, then checks the named type range without masking. +Width results are ints; checked overflow raises `value`. `u64` uses int64 bit +patterns. `fround` returns a float after double-to-float32-to-double conversion +(ints convert to double first). See [the width builtins](BUILTINS.md#width-arithmetic-n9). + +```eigenscript +print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]] +print of (fround of 0.1) +``` +```output +[-9223372036854775808, 0] +0.10000000149011612 +``` + A translation guide: each section shows the same program in a language you already know, then in EigenScript. Every EigenScript block followed by an `output` block is executed by the test suite @@ -15,6 +31,44 @@ fundamental way: **every assignment is observed**, and the language has first-class constructs (`converged`, `what is x`, `prev of x`) for asking the running program about its own state and history. + +Bitwise operators and `bit_*` builtins return ints, including when an +operand is an integral float. They use Lua 5.4's 64-bit shift rules: `>>` +zero-fills; a negative count shifts in the opposite direction; a count of +64 or more, or -64 or less, gives zero. Each operand must be an exact int64; +a fractional or out-of-range float raises `value`. + +```eigenscript +print of [1 << 64, 1 << -1, -1 >> 1, 8 >> -1, 1 << 63] +print of [bit_shr of [-1, 1], bit_shl of [8, -1], num_type of (3.0 & 1)] +``` +```output +[0, 0, 9223372036854775807, 16, -9223372036854775808] +[9223372036854775807, 4, "int"] +``` + +`//` floors the quotient; `%` has the divisor's sign. `//=` applies the same +operation to its assignment target, like `/=`. Zero divisors raise `value` +for int, float and mixed operands. Two ints produce an int for +`//` and `%`; a float operand produces a float. `/` always produces a float. +Float and mixed `//` use Python’s `divmod` quotient correction: `1.0 // 0.1` +is `9.0`, and `1e-300 // -1e300` is `-1.0`. `//=` uses the same rule. + +```eigenscript +x is -7 +x //= 2 +print of [x, 7 // -2, -7 % 3, 7 % -3, -7.5 % 2] +print of [num_type of (7 // 2), num_type of (7.0 // 2)] +y is 1.0 +y //= 0.1 +print of [1.0 // 0.1, 1 // 0.1, 1e-300 // -1e300, y, num_type of y] +``` +```output +[-4, -4, 2, -2, 0.5] +["int", "float"] +[9.0, 9.0, -1.0, 9.0, "float"] +``` + Like indexed nearest-neighbour APIs in Python's scientific ecosystem, EigenScript's batched `nearest_in_range_all` does not count each query entity as its own neighbour. Its scalar `nearest_in_range` takes an explicit position, @@ -25,7 +79,7 @@ so it has no query-entity identity to exclude. | | Python | JavaScript | Rust | Lisp | EigenScript | |---|---|---|---|---|---| | typing | dynamic | dynamic | static | dynamic | dynamic | -| numbers | int + float | float | many | many | one: 64-bit float | +| numbers | int + float | float | many | many | int64 + float64 | | assignment | `x = 1` | `let x = 1` | `let x = 1;` | `(define x 1)` | `x is 1` | | call | `f(x)` | `f(x)` | `f(x)` | `(f x)` | `f of x` | | blocks | indentation | braces | braces | parens | indentation | @@ -50,6 +104,54 @@ missing, unreadable, and directory paths. Empty files return empty values. `vm_run_bytecode` raises a catchable `value` error naming a rejected chunk descriptor; a valid program may still return `null`. `sandbox_run` reports descriptor rejection in its structured `{ok: false, error: ...}` result. Its optional fourth limit, `max_work`, bounds cumulative bytecode instructions across function calls and callback re-entry (default 10,000,000), independently of loop and allocation limits. This meters VM work, not elapsed time: a blocking native callback must return before the sandbox can stop. +The positions and counts of `char_at`, `substr`, `get_at`, `set_at`, +`copy_into`, `fill` and the `list_*` mutation/slice builtins use exact conversion +(N5). So do handle fields, bytecode ABI/counts/bytes, sandbox limits and numeric +exit codes. Integral floats are accepted; fractions and values outside the +receiving C type raise `value` before indexing or effects. +`recv_timeout` rounds milliseconds half away from zero and raises beyond the +C long range, with no one-year cap. `usleep` rounds microseconds and raises +beyond the C int range. `record_history` returns its previous flag as an int; +nonzero numeric flags still enable history. `task_detach` and sandbox error lines return ints. +`read_bytes` produces int bytes; `write_bytes` requires exact bytes in 0..255 +and returns an int count. File caps and corpus/stream counts follow N5; an +integral stream count outside its cap still raises `limit`. Subprocess exit +statuses are ints, and timeout seconds round to representable poll milliseconds. + +```eigenscript +print of (substr of ["hello", 1.0, 2.0]) +try: + print of (substr of ["hello", 1.5, 2]) +catch e: + print of e.kind +print of (num_type of (record_history of 0.5)) +print of (usleep of 1.5) +try: + usleep of 1e300 +catch e: + print of e.kind +path is mktemp of null +written is write_bytes of [path, [65.0, 66.0]] +bytes is read_bytes of path +print of [num_type of written, num_type of bytes[0], str_from_bytes of bytes] +try: + write_bytes of [path, [65.5]] +catch e: + print of e.kind +remove_file of path +``` +```output +el +value +int +null +value +["int", "int", "AB"] +value +``` + +Runtime-published counts and indices are exact ints (S9-K1): `__loop_iterations__`, live and historical `when` assignment counts, aggregate `what` lengths, trajectory `window`, error `line`, and scheduler trace `seq` and task ids. Scheduler `tick` remains a continuous virtual time. + Sandbox execution does not update shared temporal history: assignment values, names, counts and observer snapshots stay outside that history even when recording is armed. Ordinary tape assignment records still emit. Host and trusted descriptor history recording resumes normally outside the sandbox; sandbox temporal reads remain refused. `lib/eigen.eigs` snapshots the host values used by its tokenizer, parser, evaluator, import helpers, and fresh meta environments when it loads (#1386). Later host builtin rebinding does not change those dependencies, including entropy's `log`/`divide` calls. For `load_file`, the snapshots use values visible during initialization; loaded code still shares the host scope. String conversion still uses the pristine reserved f-string bridge. Explicit custom environments, debug hooks, and rebinding the interpreter's own helper names remain caller-controlled; captured caller-defined functions retain their own binding behavior. @@ -75,14 +177,20 @@ print(7 / 2) # 3.5 print(7 // 2) # 3 (floor division operator) ``` -EigenScript — one number type, true division; floor explicitly: +EigenScript — signed int64 and finite float64 share the `num` type; +`num_type of` distinguishes their kind. Decimal integer literals fitting +int64 are exact; a point, exponent or out-of-range decimal literal gives +a float. Hex integer literals wrap modulo 2^64. Int `+ - *` raise `value` +on overflow; unary minus and `abs` raise at INT64_MIN. Mixed arithmetic +converts ints to floats, while mixed-kind comparison is exact, including +beyond 2^53. `/` returns a float; `//` floors the quotient: ```eigenscript x is 42 x += 1 print of x print of (7 / 2) -print of (floor of (7 / 2)) +print of (7 // 2) ``` ```output 43 @@ -90,6 +198,117 @@ print of (floor of (7 / 2)) 3 ``` + +Empty `min`/`max` raises `value` in both call forms (R1.6b-D2); empty +`sum` remains int zero (C2). `sign_extend` reads exact int64 arguments and +raises `value` if its existing subtraction underflows (R1.6b-D1). + +Counts and indices are ints. `floor`, `ceil` and half-away-from-zero `round` +return an int when the result fits int64. `min`/`max` return the winning +argument with its kind; the first argument wins a tie. `num` requires a +complete numeric-literal string (N10). JSON integer text fitting int64 +decodes exactly (N14). + +The [C embedding API](EMBEDDING.md#value-handles) follows Lua’s numeric +accessors: `eigs_value_new_int` constructs an exact int64, `eigs_value_is_int` +tests the stored kind (`lua_isinteger`), and `eigs_value_as_int` reports +whether a number converts exactly to int64 (`lua_tointegerx`). +`eigs_value_as_num` converts either kind to double (`lua_tonumber`); +`EIGS_TYPE_NUM` covers both. + +LSP hover on a numeric literal reports `int` or `float` from the literal’s +kind: `3.0` remains a float even though its value is integral. + +```eigenscript +print of [type of 3, num_type of 3, num_type of 3.0] +print of [9007199254740993, 3.0, -0.0] +print of [num_type of (len of "abc"), num_type of (floor of -2.5)] +print of [num of "9007199254740993", num_type of (num of "3.0")] +print of [num_type of (min of [3, 3.0]), num_type of (min of [3.0, 3])] +print of (json_path of ["{\"n\":9223372036854775807}", "n"]) +try: + min of [] +catch e: + print of [e.kind, (index_of of [e.message, "min"]) >= 0] +try: + print of (num of "12abc") +catch e: + print of e.kind +``` +```output +["num", "int", "float"] +[9007199254740993, 3.0, -0.0] +["int", "int"] +[9007199254740993, "float"] +["int", "float"] +9223372036854775807 +["value", true] +value +``` + +`add`, `subtract` and `multiply` preserve exact ints for scalar operands and +for each pair of list elements, with `value` on int64 overflow. `negative` +preserves int scalar and list elements and raises at INT64_MIN. Buffer +arithmetic remains float. A buffer kernel stops at its first raised result, +releases its partial output and does not read later elements (#1417). +An all-int `sum` returns an exact int, while a +float anywhere in a nested input selects float addition for the entire +reduction. A buffer selects float addition too. An empty list's sum is int +zero, in both call forms. `sum` refuses non-finite buffer elements with a +`value` error in every strict mode (N13). In strict mode, it stops at the first NaN with a "not a number" diagnostic and sets `math_flags.invalid`; later elements are not read (#1417). + +```eigenscript +exact is sum of [9007199254740993, 1] +print of [exact, num_type of exact] +mixed is sum of [9223372036854775807, 1, 0.5] +print of [num_type of mixed, mixed == 9223372036854775808.0] +print of [num_type of (sum of []), num_type of (sum of ([]))] +print of (add of [[9007199254740993, 2], [1, 3]]) +try: + negative of (-9223372036854775807 - 1) +catch e: + print of e.kind +``` +```output +[9007199254740994, "int"] +["float", true] +["int", "int"] +[9007199254740994, 5] +value +``` + +PostgreSQL integer columns (`smallint`, `integer`, `bigint`, `oid`) return +exact int64 values through `db_query_value`; `db_query_json` retains their +exact decimal digits (N14, Q-DB253). A `bigint` beyond 2^53 is an int, +including both int64 boundaries. `numeric` remains text, and finite real +columns returned directly remain floats. Float parameters use N6 shortest round-trip text, preserving `.0` and +signed zero. Store records use the same float writer and exact int64 digits +(N14, #1762). The executable DB examples and boundary assertions +in `tests/test_db.eigs` run on the server-db profile with PostgreSQL. + +TCP handles, ports and sent-byte counts are integers. Numeric socket handles, ports, receive counts and outgoing bytes require exact integers (integral floats convert); bytes must be in 0..255. `net_send` validates the entire payload before sending any byte. Poll timeouts are continuous milliseconds rounded to the C int range; an unrepresentable duration raises `value`. + +`http_early_bind` and `http_serve` require an exact integer port in 0..65535; integral floats convert and port zero requests an ephemeral port. Fractional or out-of-range ports raise `value` before binding. `shared_delete` and `shared_size` return integer counts. + +N6 prints each float with Python's shortest round-trip spelling: integral +floats retain `.0`, negative zero is `-0.0`, and scientific notation starts +below `1e-4` or at `1e16`. Ints keep their exact decimal digits. `print`, +`write`, `str`, f-strings and JSON share the number writer. The executed +examples are `tests/phase2/rows/N6.float-repr.eigs` and +`tests/phase2/rows/N6.str-fstring.eigs`. + +The HTTP shared counter `shared_incr` follows numeric `+`: two int operands +return an exact int and int64 overflow raises `value` without changing the +stored counter (A). A missing counter starts at int zero. A float operand +produces a float. The stored operand is its JSON-decoded value; N6 preserves an +integer-valued float as a float by retaining its `.0`. Executable shared-counter examples in +`tests/test_http.eigs` cover exact values, both overflow directions and the +next operation after a raise. + +`http_post` also preserves every digit of JSON integer header values within +int64 before sending them as text (N14, B6). The executed loopback example +is `tests/phase2/builtins/builtins/http_post.eigs`. + ## Functions Python: @@ -171,6 +390,9 @@ print of (c of null) 2 ``` +Builtin `sort` sorts a list in place; `sort_by` returns a new list using numeric keys. +Both are stable on every platform: equal elements or keys keep their input order. + ## Lists: map / filter / comprehension Python: @@ -253,13 +475,18 @@ the rest accept either, and hand back a buffer when every operand was one. before adding it to the accumulator, in ascending inner-index order, on both containers. They do not fuse the multiplication and addition. Raw non-finite results may remain in a buffer's internal work area, but every -scalar read applies EigenScript's numeric guard: infinity saturates at -±`1e308`, and a NaN raises in strict mode (or becomes `0` and sets the invalid -math flag under `EIGS_STRICT=0`). Structural buffer equality and scalar -reductions normalize each input before comparison or arithmetic; the reduction +ordinary indexed read and iteration step refuse stored NaN or infinity with +`value` in every strict mode, matching `get_at` (N13). Strict NaN retains +its first-NaN diagnostic and invalid flag. All execution tiers report the +same error kind, message and source line. +Structural buffer equality compares stored doubles with IEEE equality, without +raises or math flags; NaN differs from everything, including itself, infinities +compare by sign, and signed zeros are equal. The remaining scalar reductions +retain their declared data boundaries; the reduction association contracts remain unchanged. Mixed buffer/list tensor operations and numeric byte/sample/device conversions also normalize reads and stop on the -first error. Byte conversion truncates and wraps modulo 256 after normalization. +first error. `write_bytes` requires exact bytes in 0..255 (N5/Q1); other byte +converters retain truncation and wrapping at this stage. Raw copies, typed serialization and buffer-only work areas preserve their data. `type_mismatch` error by default; `EIGS_STRICT=0` retains numeric-zero substitution. `str_from_bytes` stops at numeric NUL and does not inspect later @@ -298,6 +525,19 @@ Ada 3 ``` +Store handles carry integer `_store_id` and `_store_gen` fields. Every store operation requires exact, in-range integer handle fields; integral floats convert, while fractional, non-finite or out-of-range numeric fields raise `value` before store access (R1.6f-D1). `store_count` returns an integer. + +`store_get`, `store_delete`, and `store_update` accept string keys or exact +int64 numeric keys. Integral floats convert; fractional or out-of-range floats +raise `value` before lookup or mutation. Numeric keys retain all int64 digits. +Stored decimal integer text within int64 decodes as an int; a decimal point or +exponent selects a float. Buffer payloads remain floats. Store records preserve +int64 digits and use shortest round-trip float text, including `.0` for +integral floats and the sign of `-0.0`, so `3.0` reads back as a float. + +See the [stored-record example](SPEC.md#stored-records) for an exact key above +2^53 and a fractional-key refusal. + ## Pattern matching Rust: @@ -513,6 +753,17 @@ deviations are in issue #1990. ## What has no equivalent elsewhere: the observer +For non-numeric bindings, `converged` and ordinarily `stable` compare +bounded, detached assignment snapshots; finite in-place mutations are visible +on reassignment. A refused or oversized snapshot leaves both predicates false. +An accepted snapshot containing a NaN buffer is unequal to itself under raw +IEEE comparison, so it cannot converge; its `stable` predicate instead uses +the entropy-window rule after a full bounded comparison window (S10-D1). +Refusal clears that eligibility even if the entropy window is already complete. +The matmul scenario's equality / stability / +convergence remains `false` / `true` / `false`, with no equality-driven data +or math-flag changes. The other predicates and non-numeric `report` use entropy. + Every assignment (outside `unobserved` blocks) updates an observer tracking the value's entropy and trend (for containers, one pass over the value's own elements — the walk stops at a reference, so a nested container @@ -777,3 +1028,88 @@ Routes and the static root are fixed once `http_serve` starts: `http_route`, `http_route_authed` and `http_static` then raise, including from a `code` route, and an uncaught error in a `code` route's source answers 500 with a generic body; the error message goes to the server's stderr only (#1140). + +Tensor sizes and indices follow N5: an integral float is accepted and a +fraction raises `value`. `gather` keeps the selected list cell's int/float +kind; buffer elements and numeric tensor calculations are floats. `shape` +returns integer dimension counts. `mean` and `norm` refuse non-finite buffer +cells with `value` in both strict modes. + +```eigenscript +g is gather of [[[1, 2], [3, 4]], [1.0, 0]] +print of [g, num_type of g[0], num_type of (shape of g)[0]] +try: + gather of [[10, 20], 0.5] +catch e: + print of e.kind +``` +```output +[[2, 3], "int", "int"] +value +``` + +Buffer sizes, indices, offsets and counts follow N5. Byte inputs to +`str_from_bytes`, `f64_from_bytes`, `inflate` and `buf_from_pcm16le` must be +exact integers in 0..255; fractions and out-of-range bytes raise `value`. +`buf_len`, `f64_to_bytes` and `inflate` return integer counts or bytes; +buffer storage and scalar buffer reads are floats. `buf_get`, `buf_peak`, +`buf_dot` and the byte/PCM codecs refuse non-finite input cells with `value`. + +Model token ids and generation limits follow N5 and the C int range; +fractional ids or token counts raise `value`. `eigen_generate` returns int +token ids, while `eigen_eval_loss` returns a float. Temperature, top_p and +training learning rates remain float quantities. Model JSON weights stay +float data; the loader status strings are unchanged. + +Graphics and audio magnitudes round half away from zero and raise `value` +when the result exceeds the C call's range. This refusal happens even before +SDL is loaded: + +```eigenscript +try: + gfx_delay of 1e300 +catch e: + print of e.kind +``` +```output +value +``` + +### Stored buffer data boundary + +Buffer kernels can retain non-finite data. Scalar indexing and iteration +raise `value` when that data is read, in every strict mode (N13). + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +try: + print of p[0] +catch e: + print of e.kind +``` +```output +value +``` + +### Buffer equality + +Buffers compare their stored doubles with IEEE equality: NaN differs from +everything, even itself; infinities compare by sign; signed zeros are equal. +Equality never raises or changes math flags, including under strict mode. + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +print of (p == p) +print of (p == g) +sgd_update of [p, p, 1.0] +print of (p == p) +``` +```output +true +false +false +``` diff --git a/docs/CONCURRENCY.md b/docs/CONCURRENCY.md index 127aac11e..a7f832f04 100644 --- a/docs/CONCURRENCY.md +++ b/docs/CONCURRENCY.md @@ -110,7 +110,8 @@ buf_set of [b, 0, 1] cb is channel of 1 send of [cb, b] buf_set of [b, 0, 99] -row of ["buffer ", "1", str of (buf_get of [(recv of cb), 0])] +# N6: compare buffer contents numerically; float text retains .0. +row of ["buffer ", 1.0, buf_get of [(recv of cb), 0]] t is text_builder_new of null text_builder_append of [t, "a"] @@ -168,10 +169,12 @@ depth-64 tail SHARES **The rows the table cannot construct in three lines, and why they behave the way they do.** `chan_clone_rec` (src/eigenscript.c) switches on `ValType` with no `default:`, so `-Werror=switch` forces every new type to choose a side; the -switch handles each type explicitly: `VAL_NUM`, `VAL_NULL`, `VAL_STR`, -`VAL_LIST`, `VAL_DICT`, `VAL_BUFFER`, and `VAL_TEXT_BUILDER` are rebuilt +switch handles each type explicitly: `VAL_FLOAT`, `VAL_INT`, `VAL_NULL`, `VAL_STR`, +`VAL_LIST`, `VAL_DICT`, `VAL_BUFFER`, `VAL_BOOL`, and `VAL_TEXT_BUILDER` are rebuilt (copy), while `VAL_FN` and `VAL_BUILTIN` take a refcount -(share). +(share). An int is a fresh exact copy, `make_int` of the same value +(src/eigenscript.c:2682), and like the other scalars it is copied at any depth +(src/eigenscript.c:2678). A new `ValType` must choose a side in the switch for the compiler to accept it. The copy is recursive but not an identity-preserving graph copy: two references to one list or dict become two independent copies. At recursion depth 65 diff --git a/docs/EMBEDDING.md b/docs/EMBEDDING.md index 86b7bb323..a6f6bf332 100644 --- a/docs/EMBEDDING.md +++ b/docs/EMBEDDING.md @@ -88,6 +88,13 @@ caller environment. Thus, an API call made re-entrantly by a host callback runs in the default environment even if that callback temporarily changed its rounding mode, and the callback's mode is back when the inner call returns. +The exact float-to-int conversion in `eigs_value_as_int` also saves and +restores this environment, so a host's DAZ setting cannot turn a nonzero +subnormal into a successful integer zero conversion. `eigs_value_new_int` +and `eigs_value_is_int` do no floating-point work and need no environment +swap. These are the R1.8 additions to the public-boundary census; the +existing code-running and number-text entry points keep their boundaries. + Host callbacks execute in EigenScript's default environment; the runtime does not swap around them. A callback that needs a different mode must save, set, and restore it itself before returning to EigenScript. Re-entrant API calls do @@ -361,6 +368,7 @@ embed API refuses it too: `eigs_set_global`, `eigs_get_global` and ```c EigsValue *eigs_value_new_num(double n); +EigsValue *eigs_value_new_int(int64_t n); EigsValue *eigs_value_new_string(const char *s); EigsValue *eigs_value_new_null(void); EigsValue *eigs_value_new_bool(int b); /* true/false (#1637) */ @@ -382,6 +390,8 @@ Inspection: ```c EigsValueType eigs_value_type(EigsValue *v); /* EIGS_TYPE_NUM, _STR, _LIST, _DICT, _NULL, _FN, _BUFFER, _OTHER, _BOOL */ double eigs_value_as_num(EigsValue *v); /* wrong type: sentinel + error; bool sentinel is NaN */ +int64_t eigs_value_as_int(EigsValue *v, int *ok); /* ok may be NULL */ +int eigs_value_is_int(EigsValue *v); /* int kind only; pure predicate */ int eigs_value_as_bool(EigsValue *v); /* wrong type: 0 + error */ const char *eigs_value_as_string(EigsValue *v); /* NULL if wrong type; borrowed pointer */ int eigs_value_list_len(EigsValue *v); /* wrong type: 0 + error */ @@ -394,6 +404,27 @@ double eigs_value_buffer_get(EigsValue *v, int i); /* OOB: 0.0 + error void eigs_value_buffer_set(EigsValue *v, int i, double x); /* OOB: no-op + error */ ``` +`EIGS_TYPE_NUM` covers signed int64 and float, like Lua's `LUA_TNUMBER`. +`eigs_value_new_int` constructs an exact signed int64, like `lua_pushinteger`; +`eigs_value_new_num` constructs a float, like `lua_pushnumber`. +`eigs_value_as_num` converts either kind to `double`, +like `lua_tonumber`; an int above 2^53 can lose precision in that conversion. +Use `eigs_value_as_int` for an exact integer result, like `lua_tointegerx`: + +| input | result | `*ok` | new error | +|---|---:|---:|---| +| int, including `9007199254740993` | exact int64 | 1 | none | +| integral float in [-2^63, 2^63), including `3.0` and -2^63 | exact int64 | 1 | none | +| fractional or out-of-range number, including `1.5` and float 2^63 | 0 | 0 | none | +| non-number, including `NULL` | 0 | 0 | `type_mismatch` | + +The success pointer may be `NULL`. The accessor never clears a pending +error, and a numeric refusal does not replace it. `eigs_value_is_int`, like +`lua_isinteger`, tests the stored kind: it returns false for `3.0`, and +returns false without setting an error for any non-number. All four numeric +operations leave ownership unchanged; each constructed value still needs +`eigs_value_release`. + The numeric, boolean, list-length, and buffer accessors leave a pending `type_mismatch` error when `v` has the wrong type (including `NULL`); buffer `get` and `set` leave `index_range` for a negative or past-the-end index. Their @@ -574,7 +605,7 @@ through lifetime metadata, never through repeated numeric IDs. An installed memory tape suspends the file replay context as a whole. Clearing memory restores the file's cursor, queued values, parser buffer, bindings and strictness; it neither rewinds nor takes data from the memory source. A refused -memory replacement leaves the active context untouched. The version 6 grammar +memory replacement leaves the active context untouched. The current tape grammar and older-format refusal rule are documented in `docs/TRACE.md`. Host builtins participate with the take/record pair, the same contract @@ -715,3 +746,15 @@ CHANGELOG entry. The opaque types (`EigsState`, `EigsThread`, `EigsValue`) intentionally hide their layout: those structs change between releases and the only safe way to touch them is through the documented accessors. + +### Stored non-finite buffer data + +`eigs_value_buffer_get` returns a valid element's stored double exactly, +including NaN payloads, infinities and negative zero. A set/get round trip +preserves bits; the getter neither normalizes nor writes math flags and never +raises for non-finite data. The setter already stores doubles unchanged. C +callers can use `isfinite()` when their application needs finite values. +Wrong-type and out-of-range reports and their sentinels keep the API contract +above. Script indexing and iteration instead enforce the finite data boundary. +This follows Lua's `lua_tonumberx` and CPython's `PyFloat_AsDouble` C boundary +(R1.6z-D1; design §2.6a). diff --git a/docs/GRAMMAR.md b/docs/GRAMMAR.md index 9c9604d77..eef374cf5 100644 --- a/docs/GRAMMAR.md +++ b/docs/GRAMMAR.md @@ -99,10 +99,10 @@ converged stable improving oscillating diverging equilibrium ### Operators and Punctuation ``` -+ - * / % ++ - * / // % & | ^ << >> ~ < > <= >= == != -+= -= *= /= %= &= |= ^= <<= >>= ++= -= *= /= //= %= &= |= ^= <<= >>= ( ) [ ] { } , : . |> pipe (left-associative, desugars a |> b to b of a) @@ -194,7 +194,7 @@ destructure_stmt = '[' IDENT { ',' IDENT } ']' 'is' expression local_assign_stmt = 'local' IDENT 'is' expression assign_stmt = IDENT assign_op expression -assign_op = 'is' | '+=' | '-=' | '*=' | '/=' | '%=' +assign_op = 'is' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' ; compound forms desugar: x += e == x is x + e @@ -228,7 +228,7 @@ shift_expr = addition { ( '<<' | '>>' ) addition } addition = multiply { ( '+' | '-' ) multiply } -multiply = unary { ( '*' | '/' | '%' ) unary } +multiply = unary { ( '*' | '/' | '//' | '%' ) unary } unary = '-' unary | 'not' unary @@ -322,7 +322,7 @@ From lowest to highest precedence: | 7 | `&` | Left | Bitwise AND | | 8 | `<<` `>>` | Left | Shifts | | 9 | `+` `-` | Left | Addition, subtraction | -| 10 | `*` `/` `%` | Left | Multiplication, division, modulo | +| 10 | `*` `/` `//` `%` | Left | Multiplication, true/floor division, floor modulo | | 11 | `-` (unary) `not` `~` | Right | Negation, logical NOT, bitwise NOT | | 12 | `of` | Right | Function call / observation (`f of g of x` = `f of (g of x)`) | | 13 | `[i]` `[a:b]` `.key` | Left | Index, slice, dot access | diff --git a/docs/LANGUAGE_CONTRACT.md b/docs/LANGUAGE_CONTRACT.md index bd928ffb4..ed64a39ad 100644 --- a/docs/LANGUAGE_CONTRACT.md +++ b/docs/LANGUAGE_CONTRACT.md @@ -138,11 +138,18 @@ examples (executed by the suite). ## Numbers **Promise:** -- One numeric type: IEEE-754 double. Integers are exact up to 2^53. -- No NaN, no Infinity reaches a program. By default a NaN-producing +- One surface type, `num`, with exact signed int64 and finite IEEE-754 double + kinds. `num_type of` gives `"int"` or `"float"`, or null for a non-number. + Decimal integer literals within int64 and wrapping hexadecimal literals are + ints; a point or exponent produces a float, as does decimal overflow. + Int `+ - *` raise `value` on overflow; unary minus and `abs` raise at + INT64_MIN. Mixed arithmetic converts to float; cross-kind comparison is exact. + Float integer exactness ends at 2^53. +- Scalar numbers exposed to a program are finite; buffer kernels may retain + non-finite work data as described below. By default a NaN-producing operation, an out-of-domain argument or a wrong-typed argument raises a catchable error (strict is the default, #1361); division by zero raises - in every mode. Overflow saturates at ±1e308. Under the per-run opt-out + in every mode. Float overflow saturates at ±1e308. Under the per-run opt-out `EIGS_STRICT=0` the arithmetic is finite by construction instead: a NaN collapses to 0 and domain functions substitute a stand-in. - **Every clamp is recorded.** The finite invariant keeps a program @@ -168,13 +175,17 @@ examples (executed by the suite). `1e308`, so a data column containing either used to parse to a plausible number with nothing to check; `pow` of a negative base with a fractional exponent, `f64_from_bytes` of a NaN bit pattern, - `matmul` reaching `inf - inf`, and `tensor_load` of a file carrying - NaN bytes collapse the same way. A buffer may retain the raw kernel value - internally, but every scalar read applies this same collapse (or raises in - strict mode), including structural equality and scalar reductions. Both bits are sticky until - FPU. In strict mode, the default, every one of those out-of-domain - calls and NaN sources raises a catchable `value` error naming the - builtin instead of substituting (SPEC.md, *Strict mode*). + and `tensor_load` of a file carrying NaN bytes collapse the same way. + A buffer kernel such as `matmul` reaching `inf - inf` may retain raw + non-finite data internally. Scalar buffer extraction and reductions refuse + stored non-finite cells with `value` in every strict mode (R1.6z-D1). + Structural equality, including observer snapshot equality, instead compares + the stored IEEE values: NaN is unequal to itself, without normalization, + mutation, an error or math-flag writes. The flags are sticky for the state's + lifetime. In strict mode, the default, those scalar NaN sources and + out-of-domain calls raise a catchable `value` error naming the builtin + instead of substituting (SPEC.md, *Strict mode*); the stored-buffer + boundary above applies in every mode. - **Saturation is not associative, and that is not detectable from the value alone.** `(1e300 * 1e300) / 1e300` is `1e8`; `1e300 * (1e300 / 1e300)` is `1e300`. The first overflowed and came back down, and @@ -182,13 +193,15 @@ examples (executed by the suite). are what the finite invariant requires — the `overflow` flag is how you tell. Stated here because the trade should be visible rather than discovered. -- `str of` produces the shortest representation that round-trips back to - the same double; `num of (str of x) == x`. +- Ints print exact decimal digits. For floats, `str of` produces the shortest + representation that round-trips back to the same double. During the builtin + transition, `num of` and JSON decoders still produce floats; an integer above + 2^53 must not be round-tripped through those readers yet. - **Every producer of number text obeys that same rule** — `str of`, `json_encode`, `json_build`, `json_path`, and the SIGUSR1 observer dump all share one implementation (`eigs_num_text`), so a JSON round-trip returns the same double and `json_encode of x` equals `str of x` for - every number (#875). + every float (#875). - `%` follows the dividend's sign (C semantics): `-7 % 3 == -1`. **Status:** Enforced — `tests/test_number_format.eigs`, @@ -230,9 +243,10 @@ integers. Every operand, the shift count included, must be an **exact integer in `[-2^63, 2^63)`**; a fractional operand (`1.5 | 0`, `1 << 0.5`) or one outside that range (`1e30 | 0`, `9223372036854775808 | 0`) raises a catchable `value` error ("number has no integer representation"). This is Lua 5.4's rule (reference manual -§3.4.3); nothing is truncated or wrapped. `-9223372036854775808 | 0` is valid. A shift count -that passes the check is masked to `[0,63]`, so large or negative shifts -are defined, not UB (`1 << 64` == `1 << 0` == `1`; `1 << 100` == `1 << 36`). The +§3.4.3); operand conversion never truncates or wraps. Results are exact ints. +`-9223372036854775808 | 0` is valid. Shifts follow Lua 5.4: a count outside +`(-64,64)` gives zero, a negative count shifts in the opposite direction, +and right shifts zero-fill (`1 << 64` is `0`; `-1 >> 1` is `9223372036854775807`). The infix operators and the `bit_*` builtins agree bit-for-bit on the same operands — they were once a divergent `int32` implementation (`0xEDB88320`, CRC-32's polynomial, was the first casualty). Non-numeric operands **raise** a runtime @@ -242,9 +256,11 @@ operator … raises") hold for *every* operator with no exceptions. **Status:** Enforced — `tests/test_bitwise.eigs` (builtin == infix parity across the high-bit range and out-of-range shift counts; a `value` raise for every -non-integer operand form, in the VM, the builtins and JIT-compiled hot loops), +non-integer operand form, including hot functions and loops), `num_to_i64_exact` in `eigenscript.h`, `INT_BINOP_R` / `CASE(BNOT)` in `vm.c`, -`bit_*` builtins in `builtins.c`, and the exactness bail in `jit.c`'s bitwise arms. +`bit_*` builtins in `builtins.c`, and `eigs_bit_shift` in `eigenscript.h`. +The JIT scanner and leaf evaluator refuse bitwise opcodes during round 1; +the interpreter executes them with these same rules. ## Truthiness @@ -462,17 +478,19 @@ unary and `of` are right-associative. - Out-of-range indices (including too-negative, e.g. `a[-(len+1)]`) raise a runtime error. - A non-integer index **raises** (`a[1.5]` → error). Integer-valued doubles - are accepted (`a[2.0]` works), since EigenScript has a single number type; + are accepted (`a[2.0]` works), since ints and floats share the numeric surface type; but a fractional value is never silently truncated. Because `/` always yields a double, a division-derived index must be collapsed explicitly — `a[floor of ((lo + hi) / 2)]` — which keeps the rounding decision in the programmer's hands. A value that is fractional only through float drift (`2.9999998`) also raises, surfacing the sloppy arithmetic rather than mis-indexing. -- An integer-valued number outside the runtime's C `int` index representation - raises `index_range`, just like any other out-of-bounds index. This applies - consistently to indexing, dispatch keys, and slice bounds; it is not - misdiagnosed as a fractional value. +- An exact int64 subscript or dispatch key outside the runtime's C `int` + index representation raises `index_range`, like other out-of-bounds indices; + it is not misdiagnosed as fractional. A float must first satisfy Phase 2's + exact int64 conversion rule. Slice-bound and trace-line readers retain their + explicit representability checks: an int outside their C `int` domain raises + `value` before any narrowing. **Status:** Enforced — `tests/test_trycatch.eigs`; `vm_index_is_int` guards every dynamic index site in `OP_INDEX_GET`/`OP_INDEX_SET` and diff --git a/docs/OBSERVER.md b/docs/OBSERVER.md index f5a60c329..fc6f3cb94 100644 --- a/docs/OBSERVER.md +++ b/docs/OBSERVER.md @@ -136,8 +136,11 @@ a built-in goal precisely so it does not choose the user's frame. `report of x` and the bare predicates (`converged`, `improving`, …) route by the binding's current type. **Numeric bindings use the value channel**: it classifies relative steps `Δv / max(|v|, |v_prev|, scale)` and retained raw -steps. **Non-numeric bindings use the entropy channel**: it classifies changes -in `entropy(value)` against the entropy thresholds. The resulting vocabulary +steps. **Non-numeric `report` and four predicates use the entropy channel**: +it classifies changes in `entropy(value)` against the entropy thresholds. +`converged` and ordinarily `stable` compare bounded assignment snapshots; +the non-reflexive buffer exception below gives `stable` its entropy rule. +The resulting vocabulary is `converged`, `equilibrium`, `stable`, `oscillating`, `improving`, and `diverging`; `report` can additionally answer `moving` when no band matches. See [PREDICATES](PREDICATES.md#which-channel-answers-861) for the precise @@ -164,8 +167,11 @@ variable first so there is a binding history to inspect. Dict fields such as **Since #861 the predicate words and `report` ROUTE: numeric bindings answer from the value channel described below. For non-numeric bindings, -`converged` and `stable` use deep equality across the full window (#1992), -while the other predicates and `report` answer from entropy. The explicit +`converged` uses deep equality across the full window (#1992). `stable` +uses the same test for snapshots that equal themselves; an accepted snapshot +containing a NaN buffer instead uses the entropy stability rule, after a full +bounded comparison window is available (S10-D1). +The other predicates and `report` answer from entropy. The explicit `classify of [t, "entropy"]` always answers from entropy.** The routing exists because of everything this section documents. The CI oracle now reports a per-predicate comparison: run `python3 tools/observer_oracle/score.py src/eigenscript` and read its @@ -182,9 +188,23 @@ change; the window never retains aliases to the program's mutable object. To bound observer overhead, the snapshot walk stops after **4,096 content units** (value nodes plus string/key bytes, buffer elements, and text-builder bytes). An assignment above that bound is a refusal to judge: it clears the equality -run, and `converged` and `stable` remain false until a complete window of -bounded assignments has subsequently been observed. Entropy diagnostics are -still recorded normally. Observer equality is non-raising: unlike the `==` +run and leaves both predicates false until a new full window of bounded +comparisons has been observed. Entropy diagnostics are +still recorded normally. + +Stored buffer cells compare with raw IEEE equality, including inside list or +dict snapshots: NaN is unequal to itself, with no normalization, mutation, +error or math-flag write. Such an accepted, bounded snapshot therefore cannot +converge. Once a full bounded comparison window is available, its `stable` +predicate uses the entropy-window stability criterion instead. A refused or +oversized snapshot clears that eligibility; a fresh full bounded window is +required even if the entropy window was already complete. +The real `matmul` scenario in `tests/test_observer_snapshot_fix2.sh` consequently +prints `false`, `true`, `false` for `x == x`, `stable of x`, and +`converged of x`. Finite snapshots retain the equality test and its protection +against in-place mutation through aliases. + +Observer equality is non-raising: unlike the `==` operator, a number and boolean at corresponding positions are simply unequal. Functions compare by identity through a non-owning token; a function nested in a container is refused, because retaining a closure would add an invisible @@ -267,13 +287,21 @@ lies past the loop's back-edge and `bked` is 0, so every iteration ran natively, tier only the OSR thunk's last exit is recorded, so the witness covers the last iteration's body: OSR compiles at the first back-edge, so `x_0` is always assigned interpreted, and an earlier iteration that bailed goes unwitnessed until #1768. A run whose body stops computing `x` on an inf, NaN or clamped value (the JIT's designed bail) is reported as interpreted and -passes, so its readings grade the interpreter's observer for those iterations; any other run that does not witness fails as -vacuous. The predicate READS never run natively: the JIT compiles neither `PREDICATE_SLOT` nor `PREDICATE_NAME`. +passes, so its readings grade the interpreter's observer for those iterations. The non-capability floor uses float +scaffolding and float recurrence operands, preserving its binary64 inputs and native witnesses after integer literals +arrive. Exact int64 capability rows retain int literals; until the R5 int fast paths, their JIT-enabled arms may instead +report an interpreted int-opcode refusal, requiring `INT_*` before observing `x`, no function thunk, and either no OSR +thunk or its recorded prefix exit exactly at that int opcode. Their readings remain scored in every arm. Every other run that does not witness fails as vacuous, and both +native tiers must still witness a body. The predicate READS never run natively: the JIT compiles neither +`PREDICATE_SLOT` nor `PREDICATE_NAME`. A row's capability is derived from its values (IEEE inf/-inf/NaN; an int a double cannot hold exactly). Such a row runs only on a binary whose probe shows the capability (one probe checks that `1e308 * 10` overflows to inf AND that inf × 0 is NaN; a binary that does one without the other is refused), skips are printed, `EXPECT=ieee,int64` makes an absent one -fail, and every verdict on them must be right. By default the run also applies the committed floor, +fail, and every verdict on them must be right, or be declared wrong: Phase 2 passes `--declared +tests/phase2/oracle_expect.txt` (#1830), whose rows name an arm, a `row@checkpoint` and a predicate with the round +that fixes it. An undeclared wrong verdict fails, so does a declaration whose verdict is right now or was not examined, +and the integration merge (#1776) requires that file empty. By default the run also applies the committed floor, `tools/observer_oracle/floor.json` (#1764): it fails when it gets wrong any verdict the v0.46.0 tag got right. An empty `FLOOR=`, on the command line or in the environment, skips the floor. Its anchor is the binary `tools/observer_oracle/build_anchor.sh` builds from the tag into diff --git a/docs/PREDICATES.md b/docs/PREDICATES.md index 1dc3614d7..51ae2eaad 100644 --- a/docs/PREDICATES.md +++ b/docs/PREDICATES.md @@ -82,11 +82,15 @@ The predicate words and `report` are **routed**: a binding whose most recent observed assignment is **numeric** answers from the **value channel** — the classifier below, over relative steps `rel = Δv / max(|v|, |v_prev|, scale)` (#1045); every -other binding (strings, containers) uses deep equality of successive values -for **`converged` and `stable`**: both are true exactly when every successive -pair in the full window is equal under the language's `==`. This makes order -changes visible and lets an unchanged non-empty value converge. Its other -four predicates, and `report`, answer from the **entropy channel**, the +other binding (strings, containers) uses deep equality of successive +assignment snapshots for **`converged`** and ordinarily **`stable`**: every +successive pair in the full window must be equal. Numeric cells compare +exactly across int/float kinds; a bool/number pair is simply unequal, without +raising. This makes finite content changes visible and lets an unchanged +non-empty value converge. The bounded NaN-buffer exception below routes +`stable` to its entropy rule after a full bounded comparison window. The other +four predicates, and `report`, answer +from the **entropy channel**, the windowed formulas in "The six predicates". `report_value of x` is the value-channel classifier by name (identical to the routed words on a numeric binding — the two surfaces cannot disagree); `classify of @@ -97,11 +101,25 @@ folds, the tape — is unchanged everywhere. Each equality entry compares against a private content snapshot, not a reference that can be changed through an alias. Snapshotting is bounded to 4,096 content units per assignment (value nodes plus string/key bytes, buffer -elements, and text-builder bytes). A larger value is **not sampled**: both -predicates refuse to judge (return false), reset the equality run, and require -a new full window of bounded samples before either can become true. Thus the -observer's additional comparison work is bounded independently of container -size; the pre-existing entropy calculation remains size-dependent. +elements, and text-builder bytes). A larger value is **not sampled**: both +predicates refuse to judge (return false) and reset the equality run. Both +remain ineligible until a new full window of bounded comparisons is available. + +An accepted snapshot containing a NaN buffer, directly or inside a list or +dict, is unequal to itself: buffer cells use raw IEEE equality without +normalizing data, raising or writing math flags. `converged` remains false. +For that bounded, non-reflexive snapshot only, after a full bounded comparison +window, `stable` uses the entropy-window formula below (S10-D1); it can be true +even though equality is false. This +preserves the bank's `false` / `true` / `false` result for the real matmul +scenario's `x == x`, `stable of x`, and `converged of x`. A current refused or +oversized snapshot gets no entropy fallback and clears its eligibility, even +when the entropy window is already complete. Finite snapshots keep the +comparison against detached contents, so mutating an aliased container before +assigning it again cannot manufacture equality. + +The observer's additional comparison work is bounded independently of +container size; the pre-existing entropy calculation remains size-dependent. Why: entropy is a function of `|x|` alone, so any clause on it is a clause on magnitude. The current CI corpus compares every predicate, not @@ -113,7 +131,8 @@ cell from the corpus and the selected binary; no score is copied into this document. The explicit entropy channel can certify a geometric runaway and can fail to certify a finite nonzero limit because magnitude is a lossy proxy for motion. Entropy and dH are still computed and exposed for non-numeric -bindings; they simply do not decide those bindings' `converged` and `stable`. +bindings. They do not decide equality-routed convergence or finite-snapshot +stability; they do decide stability for the bounded NaN-buffer exception. **The numeric definitions** (a fixed window of `N = 10` relative steps — see [The window](#the-window-1044) — `rel = Δv / max(|v|, |v_prev|, scale)`, raw @@ -227,7 +246,7 @@ and everything built on it, so these remain **elision-sensitive**: | `trajectory of x` | the `dh` list, `dH`, `last_entropy` (`rel`/`raw`/`last_value` are complete) | | `classify of [t, "entropy"]` | the explicit entropy channel | | `report` / the four entropy predicates on a **non-numeric** binding | the entropy route — its `dh_window` is missing the elided steps | -| `converged` / `stable` on a **non-numeric** binding | the equality window is missing the elided comparisons | +| `converged` / `stable` on a **non-numeric** binding | the equality window is missing the elided comparisons; NaN-buffer snapshot stability also lacks the elided entropy samples | | the loop stall backstop | reads the last observed slot's `dH` | | a **bare** predicate | reads the last **observed** binding — the alias is deliberately not moved by an elided assignment, so scratch work inside the block cannot hijack a `loop while not converged` | @@ -396,7 +415,8 @@ single-step rule that the windowed version superseded. Since #861 the kinds dispatch by route: a numeric binding answers from the value-channel definitions in "Which channel answers" above; the windowed-entropy formulas in the next section serve non-numeric bindings -except for equality-routed `converged`/`stable`, and serve the explicit +except for equality-routed `converged` and ordinary `stable`; the bounded +NaN-buffer stability exception does use them. They also serve the explicit `classify of [t, "entropy"]` channel. `report_value of x` (#294) is the value-channel classifier by name — on a numeric binding it and the predicate words are one classifier and cannot disagree. @@ -408,7 +428,8 @@ it and the predicate words are one classifier and cannot disagree. they answer the four entropy-routed predicates and `report` for non-numeric bindings (strings, containers), plus all six predicates on the explicit entropy channel; numeric bindings use the value-channel definitions above, -and routed non-numeric `converged`/`stable` use deep equality.** The traces and design notes are kept because the +and routed non-numeric `converged`/`stable` use deep equality except for the +bounded NaN-buffer stability case described above.** The traces and design notes are kept because the mechanics still run — on the signal they were always sound for. ### `converged` (kind 0) diff --git a/docs/SPEC.md b/docs/SPEC.md index 1b86f5fe5..f90051f8c 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,5 +1,21 @@ # EigenScript Language Specification + +Width arithmetic uses explicit wrapping, checked and saturating builtins. +N5 converts integral floats, then checks the named type range without masking. +Width results are ints; checked overflow raises `value`. `u64` uses int64 bit +patterns. `fround` returns a float after double-to-float32-to-double conversion +(ints convert to double first). See [the width builtins](BUILTINS.md#width-arithmetic-n9). + +```eigenscript +print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]] +print of (fround of 0.1) +``` +```output +[-9223372036854775808, 0] +0.10000000149011612 +``` + This is the canonical, executable specification of EigenScript. **Every** `eigenscript` code block here is EXECUTED by the test suite (`tests/test_doc_examples.py`, suite section [89]) — the gate is opt-OUT. @@ -135,7 +151,7 @@ EigenScript is dynamically typed. The runtime types are: | type label | description | literal | |---|---|---| -| `num` | 64-bit float (the only number type) | `42`, `3.14` | +| `num` | signed int64 or 64-bit float | `42`, `3.14` | | `str` | immutable byte string | `"text"` | | `list` | mutable ordered sequence | `[1, 2, 3]` | | `dict` | mutable string-keyed map | `{"k": 1}` | @@ -193,7 +209,7 @@ inner outer ``` -Compound assignment operators update in place: `+= -= *= /= %= &= |= ^= +Compound assignment operators update in place: `+= -= *= /= //= %= &= |= ^= <<= >>=`. They work on plain names, dict fields, and indexed elements. ```eigenscript @@ -218,9 +234,51 @@ print of xs ## Numbers and arithmetic -All numbers are 64-bit floats. Integer-valued numbers print without a -decimal point. Division is true division. `%` is modulo. There is no -exponent operator; use `pow of [base, exp]`. +Numbers have two kinds: signed int64 and 64-bit float. `type of` reports +`"num"` for both; `num_type of` reports `"int"` or `"float"`, and null +for a non-number. Decimal integer literals within int64 are exact ints. +A decimal point or exponent makes a float; an out-of-range decimal integer +literal falls back to float. Hex integer literals wrap modulo 2^64. + +Int `+`, `-` and `*` preserve the int kind and raise `value` on int64 overflow. +Unary minus and `abs` also raise on INT64_MIN. Mixed int/float arithmetic +converts the int to float; comparison across kinds is exact, including +above 2^53. `/` returns float; `//` floors (an int for two ints, a float otherwise). +`%` is floor modulo for both kinds, with the sign of the divisor. Both int +operators are exact, and `INT64_MIN // -1` raises `value` while +`INT64_MIN % -1` is the int 0. Division, floor division and modulo by zero +raise `value`, including float and mixed operands. Builtin result kinds +are documented individually; int-preserving +arithmetic and reductions use checked int64 operations. + +Floats use Python’s shortest round-trip spelling: integral floats retain +`.0`, negative zero prints as `-0.0`, and ints print their exact decimal +digits. `print`, `write`, `str`, f-strings, JSON and store records share +this number writer. There is no exponent operator; use +`pow of [base, exp]`. + + +`//` floors the quotient; `%` has the divisor's sign. `//=` applies the same +operation to its assignment target, like `/=`. Zero divisors raise `value` +for int, float and mixed operands. Two ints produce an int for +`//` and `%`; a float operand produces a float. `/` always produces a float. +Float and mixed `//` use Python’s `divmod` quotient correction: `1.0 // 0.1` +is `9.0`, and `1e-300 // -1e300` is `-1.0`. `//=` uses the same rule. + +```eigenscript +x is -7 +x //= 2 +print of [x, 7 // -2, -7 % 3, 7 % -3, -7.5 % 2] +print of [num_type of (7 // 2), num_type of (7.0 // 2)] +y is 1.0 +y //= 0.1 +print of [1.0 // 0.1, 1 // 0.1, 1e-300 // -1e300, y, num_type of y] +``` +```output +[-4, -4, 2, -2, 0.5] +["int", "float"] +[9.0, 9.0, -1.0, 9.0, "float"] +``` Numeric literals accept the usual decimal forms plus a leading or trailing dot and scientific notation: `.5`, `1.`, `1e5`, `1E5` are all @@ -246,7 +304,7 @@ print of (abs of -5) 3.5 1 0.3333333333333333 -1024 +1024.0 5 ``` @@ -269,18 +327,17 @@ still running ### The numeric model (deliberate position) -There is one number kind — the IEEE-754 f64 — and by design there is no +The numeric kinds are signed int64 and IEEE-754 f64; there is no bigint or decimal type. This is a chosen position, not an oversight; the consequences are contracts you can rely on: The IEEE 754 default floating-point environment is part of number semantics. -- **Integer exactness ends at 2^53.** Whole numbers up to `9007199254740992` +- **Float integer exactness ends at 2^53.** Whole floats up to `9007199254740992` are exact; past that the gaps between representable values exceed 1, so - `+ 1` can be invisible. Keep integer identifiers, counters, and money-in-cents - below 2^53, or use the bitwise seam below for wider exact integer work - (its operands must be exact integers below 2^63 in magnitude; past 2^53 only - the representable ones qualify). + `+ 1` can be invisible. For exact identifiers, counters, and money-in-cents, keep values in + the int kind rather than converting them to float. Bitwise operations preserve all int64 bits and return ints; integral float + operands convert only when they are exactly representable as int64. - **Finite by construction — no `NaN`, no `Infinity` ever reach your program.** Overflow saturates at `±1e308` instead of becoming `Infinity`. Every *defined* operation returns a usable finite number; an *undefined* one — @@ -325,14 +382,13 @@ The IEEE 754 default floating-point environment is part of number semantics. value: sqrt: argument out of domain (negative) ``` - Under `EIGS_STRICT=0` the same program prints `0` twice. - A `0`, `""` or `null` that is a genuine *answer* is untouched - in both modes: `try_parse` of invalid syntax still returns `0`, `task_alive` - of an unknown id still returns `0`, `char_at` past the end is still `""`, - and `num` still *coerces* (`num of ([1, 2])` is `0` — that is its documented + Under `EIGS_STRICT=0` the same program prints `0.0` twice. + A `false`, `0`, `""` or `null` that is a genuine *answer* is untouched + in both modes: `try_parse` of invalid syntax still returns `false`, `task_alive` + of an unknown id still returns `false`, `char_at` past the end is still `""`, + and `num` still *coerces* (`num of ([1, 2])` is float `0.0` — that is its documented contract, not a guard). - The distinction is not derivable from the code: `task_alive` has two - `return make_num(0)` lines four apart, one a type guard and one the + A return expression alone does not distinguish a type guard from a documented answer. `tools/strict_differential.sh` checks the distinction that can be executed: a guard probe must raise in strict mode, the default (with the variable unset or set to `1`), and a pinned documented answer must not. @@ -342,7 +398,7 @@ The IEEE 754 default floating-point environment is part of number semantics. - **The `NaN`→`0` collapse.** Under `EIGS_STRICT=0` a `NaN` still collapses to `0` and sets `math_flags.invalid`. In strict mode every reachable `NaN` source raises a catchable `value` error naming the builtin: `pow` of a - negative base with a fractional exponent, `num of "nan"`, + negative base with a fractional exponent, `f64_from_bytes` of a `NaN` bit pattern, `matmul` when its accumulation reaches `inf - inf`, `tensor_load` of a file carrying `NaN` bytes — and the elementwise `divide` by zero, which answers `0` under `EIGS_STRICT=0` where the @@ -351,9 +407,11 @@ The IEEE 754 default floating-point environment is part of number semantics. operand can hold an infinity), so any other source hits a backstop that raises as `arithmetic`. The JIT bails to the interpreter on a non-finite result, so both tiers raise from the same guard. Buffer storage may retain - a raw non-finite kernel result internally, but every scalar read uses the - same rule: infinity saturates at ±`1e308`, while under `EIGS_STRICT=0` a - `NaN` becomes `0` and sets `math_flags.invalid` (strict mode raises). + a raw non-finite kernel result internally. Indexing and iteration, like + `get_at`, refuse a stored NaN or infinity with `value` in every strict + mode (N13). Strict NaN preserves the first-NaN diagnostic and sets + `math_flags.invalid`; other non-finite data refusals leave flags alone. + The interpreter, JIT and leaf fallback agree on kind, message and line. - **JSON parse failure in `json_path`.** Under `EIGS_STRICT=0` a malformed document is walked leniently and a parse failure answers the same `""` an absent key does. Under strict `json_path` applies `json_decode`'s @@ -366,7 +424,7 @@ The IEEE 754 default floating-point environment is part of number semantics. `json_build`, `sort`, `random_int`, `random_hex`, `token_name`, sentinel for a valid-but-absent input — `index_of` miss `-1`, `file_exists` of a missing path `0` — is unchanged in both modes. - Overflow saturation is the same in both modes. (Division and modulo by zero + Float overflow saturation is the same in both modes; int overflow raises. (Division and modulo by zero raise in *both* modes — no defined value.) `zlib_*` forms) likewise reject a nonnumeric list element with a builtin-named `type_mismatch` error by default; `EIGS_STRICT=0` retains the @@ -380,26 +438,22 @@ The IEEE 754 default floating-point environment is part of number semantics. seam for checksums, hashing, and byte protocols (see docs/BUILTINS.md). ```eigenscript -print of (9007199254740992 + 1) # +1 is invisible past 2^53 +print of (9007199254740992 + 1) # exact int64 addition print of (9007199254740993 == 9007199254740992) print of (1e308 * 10) # saturates, never Infinity print of (bit_and of [0xFFFFFFFF, 0xFF]) # int64 bitwise print of (bit_shl of [1, 40]) # exact past 2^32 ``` ```output -9007199254740992 -true +9007199254740993 +false 1e+308 255 1099511627776 ``` -Bigint / decimal value kinds would be a VM representation change rippling through -the JIT and the AOT (whose numeric speed rests on everything-being-a-double), so -they are deferred until a consumer genuinely forces them — not adopted -speculatively. Nothing above says "a number *is* an f64" any more than it must: -the contracts are exactness-below-2^53, finiteness, and the int64 bitwise seam, -which a future wider numeric kind could still honor. +Ints are exact throughout int64; floats retain the finite model above. Bigint +and decimal arithmetic remain library needs rather than additional core kinds. ## Strings @@ -456,21 +510,75 @@ mine ``` Convert explicitly with `str of n` and `num of s`. `num of` accepts -decimal and hex-integer strings (hex converts identically on every -profile and stops at the first non-hex character); a string with no -leading number converts to `0`: +exactly the numeric-literal grammar, with an optional sign and surrounding +ASCII whitespace (N10). Decimal integer text fitting int64 gives an int; +hex integers wrap modulo 2^64. A point/exponent or decimal overflow gives +a float. Partial or empty text, `_`, `0b`/`0o`, hex floats, `inf` and `nan` +raise `value` in every strict mode. Unlike Lua's `tonumber`, hex floats are +refused because the language lexer refuses them: ```eigenscript print of ("value is " + (str of 42)) print of ((num of "10") + 5) print of (num of "0xFF") -print of (num of "zebra") +try: + print of (num of "zebra") +catch e: + print of e.kind ``` ```output value is 42 15 255 -0 +value +``` + + +Empty `min`/`max` raises `value` in both call forms (R1.6b-D2); empty +`sum` remains int zero (C2). `sign_extend` reads exact int64 arguments and +raises `value` if its existing subtraction underflows (R1.6b-D1). + +Counts and indices are ints. `floor`, `ceil` and half-away-from-zero `round` +return an int when the result fits int64. `min`/`max` return the winning +argument with its kind; the first argument wins a tie. `num` requires a +complete numeric-literal string (N10). JSON integer text fitting int64 +decodes exactly (N14). + +The [C embedding API](EMBEDDING.md#value-handles) follows Lua’s numeric +accessors: `eigs_value_new_int` constructs an exact int64, `eigs_value_is_int` +tests the stored kind (`lua_isinteger`), and `eigs_value_as_int` reports +whether a number converts exactly to int64 (`lua_tointegerx`). +`eigs_value_as_num` converts either kind to double (`lua_tonumber`); +`EIGS_TYPE_NUM` covers both. + +LSP hover on a numeric literal reports `int` or `float` from the literal’s +kind: `3.0` remains a float even though its value is integral. + +```eigenscript +print of [type of 3, num_type of 3, num_type of 3.0] +print of [9007199254740993, 3.0, -0.0] +print of [num_type of (len of "abc"), num_type of (floor of -2.5)] +print of [num of "9007199254740993", num_type of (num of "3.0")] +print of [num_type of (min of [3, 3.0]), num_type of (min of [3.0, 3])] +print of (json_path of ["{\"n\":9223372036854775807}", "n"]) +try: + min of [] +catch e: + print of [e.kind, (index_of of [e.message, "min"]) >= 0] +try: + print of (num of "12abc") +catch e: + print of e.kind +``` +```output +["num", "int", "float"] +[9007199254740993, 3.0, -0.0] +["int", "int"] +[9007199254740993, "float"] +["int", "float"] +9223372036854775807 +["value", true] +value ``` ### Text and Unicode (bytes by definition) @@ -673,8 +781,24 @@ print of (bit_shl of [1, 32]) Every operand, the shift count included, must be an **exact integer** in `[-2^63, 2^63)`, Lua 5.4's rule. A fractional operand or one outside int64 has no integer representation, and the operation raises a catchable -`value` error instead of truncating. A valid shift count is then masked -to `[0, 63]`: +`value` error instead of truncating. + +Bitwise operators and `bit_*` builtins return ints, including when an +operand is an integral float. They use Lua 5.4's 64-bit shift rules: `>>` +zero-fills; a negative count shifts in the opposite direction; a count of +64 or more, or -64 or less, gives zero. Each operand must be an exact int64; +a fractional or out-of-range float raises `value`. + +```eigenscript +print of [1 << 64, 1 << -1, -1 >> 1, 8 >> -1, 1 << 63] +print of [bit_shr of [-1, 1], bit_shl of [8, -1], num_type of (3.0 & 1)] +``` +```output +[0, 0, 9223372036854775807, 16, -9223372036854775808] +[9223372036854775807, 4, "int"] +``` + +The operand checks also apply to shift counts: ```eigenscript print of ((-9223372036854775808 | 0) == -9223372036854775808) @@ -694,7 +818,7 @@ catch e: ``` ```output true -1 +0 value '<<': number 0.5 has no integer representation value @@ -885,6 +1009,74 @@ print of ev.loop 2 ``` +### Stored records + +Store handles carry integer `_store_id` and `_store_gen` fields. Every store operation requires exact, in-range integer handle fields; integral floats convert, while fractional, non-finite or out-of-range numeric fields raise `value` before store access (R1.6f-D1). `store_count` returns an integer. + +`store_get`, `store_delete`, and `store_update` accept string keys or exact +int64 numeric keys. Integral floats convert; fractional or out-of-range floats +raise `value` before lookup or mutation. Numeric keys retain all int64 digits. +Stored decimal integer text within int64 decodes as an int; a decimal point or +exponent selects a float. Buffer payloads remain floats. Store records preserve +int64 digits and use shortest round-trip float text, including `.0` for +integral floats and the sign of `-0.0`, so `3.0` reads back as a float. + +```eigenscript +path is "store-numbers-example.db" +if file_exists of path: + remove_file of path +s is store_open of path +print of [num_type of s["_store_id"], num_type of s["_store_gen"]] +store_put of [s, "c", {"_id": "9007199254740993", "n": 9007199254740993, "f": 3.0}] +r is store_get of [s, "c", 9007199254740993] +print of [r.n, num_type of r.n, num_type of r.f] +try: + store_delete of [s, "c", 1.5] +catch e: + print of e.kind +print of ((store_get of [s, "c", 9007199254740993]) != null) +store_close of s +remove_file of path +``` +```output +["int", "int"] +[9007199254740993, "int", "float"] +value +true +``` + +PostgreSQL integer columns (`smallint`, `integer`, `bigint`, `oid`) return +exact int64 values through `db_query_value`; `db_query_json` retains their +exact decimal digits (N14, Q-DB253). A `bigint` beyond 2^53 is an int, +including both int64 boundaries. `numeric` remains text, and finite real +columns returned directly remain floats. Float parameters use N6 shortest round-trip text, preserving `.0` and +signed zero. Store records use the same float writer and exact int64 digits +(N14, #1762). The executable DB examples and boundary assertions +in `tests/test_db.eigs` run on the server-db profile with PostgreSQL. + +TCP handles, ports and sent-byte counts are integers. Numeric socket handles, ports, receive counts and outgoing bytes require exact integers (integral floats convert); bytes must be in 0..255. `net_send` validates the entire payload before sending any byte. Poll timeouts are continuous milliseconds rounded to the C int range; an unrepresentable duration raises `value`. + +`http_early_bind` and `http_serve` require an exact integer port in 0..65535; integral floats convert and port zero requests an ephemeral port. Fractional or out-of-range ports raise `value` before binding. `shared_delete` and `shared_size` return integer counts. + +N6 prints each float with Python's shortest round-trip spelling: integral +floats retain `.0`, negative zero is `-0.0`, and scientific notation starts +below `1e-4` or at `1e16`. Ints keep their exact decimal digits. `print`, +`write`, `str`, f-strings and JSON share the number writer. The executed +examples are `tests/phase2/rows/N6.float-repr.eigs` and +`tests/phase2/rows/N6.str-fstring.eigs`. + +The HTTP shared counter `shared_incr` follows numeric `+`: two int operands +return an exact int and int64 overflow raises `value` without changing the +stored counter (A). A missing counter starts at int zero. A float operand +produces a float. The stored operand is its JSON-decoded value; N6 preserves an +integer-valued float as a float by retaining its `.0`. Executable shared-counter examples in +`tests/test_http.eigs` cover exact values, both overflow directions and the +next operation after a raise. + +`http_post` also preserves every digit of JSON integer header values within +int64 before sending them as text (N14, B6). The executed loopback example +is `tests/phase2/builtins/builtins/http_post.eigs`. + ## Functions `define name(params) as:` introduces a function. `return` exits with a @@ -1146,6 +1338,9 @@ print of (apply of [add5, 10]) 15 ``` +`sort` sorts a list in place; `sort_by` returns a new list using numeric keys. +Both are stable on every platform: equal elements or keys keep their input order. + `sort_by` is a builtin; `map` and `filter` come from the standard library (`lib/list.eigs`): @@ -1208,6 +1403,54 @@ expressions match too `vm_run_bytecode` raises a catchable `value` error naming a rejected chunk descriptor; a valid program may still return `null`. `sandbox_run` reports descriptor rejection in its structured `{ok: false, error: ...}` result. Its optional fourth limit, `max_work`, bounds cumulative bytecode instructions across function calls and callback re-entry (default 10,000,000), independently of loop and allocation limits. This meters VM work, not elapsed time: a blocking native callback must return before the sandbox can stop. +The positions and counts of `char_at`, `substr`, `get_at`, `set_at`, +`copy_into`, `fill` and the `list_*` mutation/slice builtins use exact conversion +(N5). So do handle fields, bytecode ABI/counts/bytes, sandbox limits and numeric +exit codes. Integral floats are accepted; fractions and values outside the +receiving C type raise `value` before indexing or effects. +`recv_timeout` rounds milliseconds half away from zero and raises beyond the +C long range, with no one-year cap. `usleep` rounds microseconds and raises +beyond the C int range. `record_history` returns its previous flag as an int; +nonzero numeric flags still enable history. `task_detach` and sandbox error lines return ints. +`read_bytes` produces int bytes; `write_bytes` requires exact bytes in 0..255 +and returns an int count. File caps and corpus/stream counts follow N5; an +integral stream count outside its cap still raises `limit`. Subprocess exit +statuses are ints, and timeout seconds round to representable poll milliseconds. + +```eigenscript +print of (substr of ["hello", 1.0, 2.0]) +try: + print of (substr of ["hello", 1.5, 2]) +catch e: + print of e.kind +print of (num_type of (record_history of 0.5)) +print of (usleep of 1.5) +try: + usleep of 1e300 +catch e: + print of e.kind +path is mktemp of null +written is write_bytes of [path, [65.0, 66.0]] +bytes is read_bytes of path +print of [num_type of written, num_type of bytes[0], str_from_bytes of bytes] +try: + write_bytes of [path, [65.5]] +catch e: + print of e.kind +remove_file of path +``` +```output +el +value +int +null +value +["int", "int", "AB"] +value +``` + +Runtime-published counts and indices are exact ints (S9-K1): `__loop_iterations__`, live and historical `when` assignment counts, aggregate `what` lengths, trajectory `window`, error `line`, and scheduler trace `seq` and task ids. Scheduler `tick` remains a continuous virtual time. + Sandbox execution does not update shared temporal history: assignment values, names, counts and observer snapshots stay outside that history even when recording is armed. Ordinary tape assignment records still emit. Host and trusted descriptor history recording resumes normally outside the sandbox; sandbox temporal reads remain refused. `try:` / `catch name:` captures runtime errors. A **built-in** runtime @@ -1684,10 +1927,12 @@ reference**: a list or dict computes over its own elements, and an element that is itself a container contributes only its size term `log2(count+1)` rather than being entered. This is the rule buffers and text builders have always followed, so a dict holding a 5-element list measures exactly -as one holding a 5-element buffer. The cost of an observed assignment is +as one holding a 5-element buffer. The cost of the entropy walk is therefore proportional to the value's own size, never to everything it can -reach, and cyclic or shared object graphs are well-defined because they are -never traversed (see [OBSERVER.md](OBSERVER.md)). Six bare-keyword predicates query +reach. Cyclic or shared graphs are well-defined for that measurement because +the entropy walk never enters them. Non-numeric assignment snapshots add the +bounded recursive comparison work described below (see +[OBSERVER.md](OBSERVER.md)). Six bare-keyword predicates query the most recently observed variable: `converged`, `stable`, `improving`, `oscillating`, `diverging`, `equilibrium`. The canonical use is a self-terminating loop: @@ -1712,9 +1957,21 @@ and `dh_zero · scale` as the absolute floor. The runtime computes `scale` as the floor in `max(|v|, |v_prev|, scale)`; above that floor, the limit's magnitude and the **unit** the value is stored in do not matter. A loop converging to `5`, `5000` or `0.005` certifies identically, and a -bank angle reads the same in radians and degrees. Non-numeric bindings (strings, -containers) classify their entropy trajectory as before; the entropy -MEASUREMENT (`where is x`) is unchanged for everything. +bank angle reads the same in radians and degrees. For non-numeric bindings +(strings, containers), `converged` and ordinarily `stable` compare detached +assignment snapshots across a full window, bounded to 4,096 content units per +assignment. Finite in-place mutations remain visible when the binding is +assigned again. A refused or oversized snapshot leaves both predicates false. +For an accepted snapshot containing a NaN buffer, directly or nested, +`converged` stays false because raw IEEE equality is non-reflexive, while +`stable` uses the entropy-window criterion only after a full bounded comparison +window (S10-D1). Refusal clears that eligibility, so a fresh full bounded +window is required even if the entropy window is already complete. +The real matmul scenario +in `tests/test_observer_snapshot_fix2.sh` gives `false`, `true`, `false` for +`x == x`, `stable of x`, and `converged of x`, without equality changing data +or math flags. The other four predicates and non-numeric `report` use entropy; +the entropy MEASUREMENT (`where is x`) is unchanged for everything. **Convergence-halting is opt-in.** A `loop while` is auto-halted on a quiet observer trajectory (~100 iterations without motion, at any @@ -1761,9 +2018,11 @@ trajectory the predicates read (value channel for numerics, entropy otherwise — #861), resolving `oscillating` → `diverging` → `improving` → `converged` → `equilibrium` → `stable`. This arrow is report priority, not an implication lattice: a converged one-direction tail is not `equilibrium`. -At a full window it agrees with the +For a numeric binding, at a full window it agrees with the bare predicates by construction: it either names a band whose predicate is -true, or — when a full window matches none of them — returns `moving`. The +true, or — when a full window matches none of them — returns `moving`. +For a non-numeric binding it reports the entropy classification and need not +agree with the snapshot-based `converged` or `stable` test. The bands are not exhaustive, and `moving` is the honest answer for the gap (#735); only while the window is still filling may `report` fall back to an instantaneous label the predicates don't yet confirm. See @@ -1789,7 +2048,7 @@ print of why is d ``` ```output true -0 +0.0 ``` **Function values are `opaque`** (#708). A function has no content the @@ -1951,7 +2210,7 @@ print of (why is x) ```output moving 2 -0 +0.0 ``` ```eigenscript @@ -2423,7 +2682,7 @@ buffer 2.5 list -[[0, 0, 0], [0, 0, 0]] +[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] buffer ``` @@ -2438,6 +2697,37 @@ a tensor builtin needs no change, because a buffer supports all of those. `sum of a` returns the total of a buffer's (or tensor's) elements, and `norm of a` returns the L2 (Euclidean) norm, `sqrt(sum_i a[i]*a[i])`. +`add`, `subtract` and `multiply` preserve exact ints for scalar operands and +for each pair of list elements, with `value` on int64 overflow. `negative` +preserves int scalar and list elements and raises at INT64_MIN. Buffer +arithmetic remains float. A buffer kernel stops at its first raised result, +releases its partial output and does not read later elements (#1417). +An all-int `sum` returns an exact int, while a +float anywhere in a nested input selects float addition for the entire +reduction. A buffer selects float addition too. An empty list's sum is int +zero, in both call forms. `sum` refuses non-finite buffer elements with a +`value` error in every strict mode (N13). In strict mode, it stops at the first NaN with a "not a number" diagnostic and sets `math_flags.invalid`; later elements are not read (#1417). + +```eigenscript +exact is sum of [9007199254740993, 1] +print of [exact, num_type of exact] +mixed is sum of [9223372036854775807, 1, 0.5] +print of [num_type of mixed, mixed == 9223372036854775808.0] +print of [num_type of (sum of []), num_type of (sum of ([]))] +print of (add of [[9007199254740993, 2], [1, 3]]) +try: + negative of (-9223372036854775807 - 1) +catch e: + print of e.kind +``` +```output +[9007199254740994, "int"] +["float", true] +["int", "int"] +[9007199254740994, 5] +value +``` + ```eigenscript v is buffer of 4 v[0] is 1 @@ -2448,7 +2738,7 @@ print of (sum of v) print of (norm of v) ``` ```output -10 +10.0 5.477225575051661 ``` @@ -2469,15 +2759,16 @@ b[3] is 3.5 print of (dot of [a, b]) ``` ```output -25 +25.0 ``` -For all three reductions (`sum`, `norm`, `dot`) the summation **order -(association) is unspecified**: callers must not depend on the exact low-bit -rounding of the result. This is a deliberate opt-in — it licenses an +For the floating forms of these reductions (`sum`, `norm`, `dot`), the +summation **order (association) is unspecified**: callers must not depend on +the exact low-bit rounding of the result. This is a deliberate opt-in — it licenses an optimizing backend (such as the AOT native compiler) to reassociate the sum across SIMD lanes, which a strict left-to-right `loop while` accumulation -forbids. The no-NaN/no-Inf invariant still holds. Write the explicit loop when +forbids. Integer `sum` checks int64 overflow and never silently rounds or +wraps. The no-NaN/no-Inf invariant still holds. Write the explicit loop when you need a fixed reduction order. ### Shaped buffers (tensors) @@ -2487,15 +2778,18 @@ A buffer can carry a 2-D shape, making it a flat-backed matrix. `buffer of [buf, rows, cols]` shapes an existing flat buffer (the element count must match). `shape of buf` returns `[rows, cols]` for a shaped buffer, or `[count]` when unshaped. Indexing stays flat (`buf[r*cols + c]`). -Every element crossing the buffer/scalar boundary uses the numeric guard: -infinity saturates at ±`1e308`, while a `NaN` raises in strict mode or becomes -`0` and sets `math_flags.invalid` under `EIGS_STRICT=0`. Structural buffer -equality and scalar reductions normalize each input before comparison or -arithmetic. A strict read that raises inside a function retains that +At the buffer/scalar boundary, `sum`, `get_at`, indexing, iteration, `dot` and `write_bytes` refuse +non-finite elements in every strict mode (N13). Other consumers use the numeric guard: infinity saturates +at ±`1e308`, while a `NaN` raises in strict mode or becomes `0` and sets +`math_flags.invalid` under `EIGS_STRICT=0`. Structural buffer equality compares +stored doubles directly with IEEE equality; list/dict self-equality follows +that rule through nested and cyclic containers. The other scalar reductions +normalize each input before arithmetic. A strict read that raises inside a function retains that function's source location and call frame. Buffer-to-list tensor materialization and numeric byte/sample/device conversions apply the same read rule, stopping -at the first raised read. Numeric bytes truncate and wrap modulo 256 after -normalization; audio samples then clamp to their documented sample range. +at the first raised read. `write_bytes` requires exact bytes in 0..255 (N5/Q1); +the remaining byte converters retain truncation and wrapping at this stage. +Audio samples clamp to their documented sample range. Raw buffer copies, typed serialization and internal buffer-only kernel work areas retain their stored representation. @@ -2511,9 +2805,11 @@ elementwise, with a `[cols]` buffer broadcast over the rows of a `[rows × cols]` buffer and a number broadcast over every element; `relu`, `leaky_relu`, `softmax`, `log_softmax`, `sum`, `mean`, `norm`, `gather` compute on the shape, and `scatter_add` is `gather`'s in-place dual. The -result is identical to the nested-list tensor form, so storing weights as -shaped buffers is purely a performance choice — and it is the substrate the -reverse-mode autograd tape in `lib/autograd.eigs` runs on. +result for float inputs is identical to the nested-list tensor form. Int +list arithmetic stays exact, while buffer storage converts ints to floats, +so storing integer weights as shaped buffers can change their precision. +Shaped buffers are the substrate the reverse-mode autograd tape in +`lib/autograd.eigs` runs on. ### `gather` and an out-of-range index @@ -2534,7 +2830,7 @@ catch e: print of e["message"] ``` ```output -[3, 4] +[3.0, 4.0] index_range gather: column index 3 out of range for row 1 (cols 3) ``` @@ -2586,8 +2882,8 @@ print of (type of (add of [b, l])) print of (mean of b) ``` ```output -[1, 2, 3] -3 +[1.0, 2.0, 3.0] +3.0 buffer list 4.666666666666667 @@ -2608,8 +2904,8 @@ print of y[1] print of (shape of w) ``` ```output -4 -6 +4.0 +6.0 [2, 2] ``` @@ -2632,9 +2928,10 @@ The facts that govern every program, in one place: 4. **Scope**: `is` updates the nearest enclosing binding or creates a local; `local` forces the current scope. Functions see and may mutate their defining environment (closure capture by reference). -5. **Values**: numbers are 64-bit floats; strings immutable; lists, - dicts, and buffers mutable and passed by reference; `==` is - structural. +5. **Values**: numbers are signed int64 or 64-bit floats under the + shared `num` type; `num_type` distinguishes their kinds. Strings + are immutable; lists, dicts, and buffers are mutable and passed + by reference; `==` is structural. 6. **Truthiness**: `0`, `null`, `""`, `[]`, `{}` are falsy; everything else is truthy. Comparisons yield `1`/`0`. 7. **Errors**: runtime errors unwind to the nearest `try`; uncaught @@ -2679,3 +2976,93 @@ Routes and the static root are fixed once `http_serve` starts: `http_route`, `http_route_authed` and `http_static` then raise, including from a `code` route, and an uncaught error in a `code` route's source answers 500 with a generic body; the error message goes to the server's stderr only (#1140). + +Tensor sizes and indices follow N5: an integral float is accepted and a +fraction raises `value`. `gather` keeps the selected list cell's int/float +kind; buffer elements and numeric tensor calculations are floats. `shape` +returns integer dimension counts. `mean` and `norm` refuse non-finite buffer +cells with `value` in both strict modes. + +```eigenscript +g is gather of [[[1, 2], [3, 4]], [1.0, 0]] +print of [g, num_type of g[0], num_type of (shape of g)[0]] +try: + gather of [[10, 20], 0.5] +catch e: + print of e.kind +``` +```output +[[2, 3], "int", "int"] +value +``` + +Buffer sizes, indices, offsets and counts follow N5. Byte inputs to +`str_from_bytes`, `f64_from_bytes`, `inflate` and `buf_from_pcm16le` must be +exact integers in 0..255; fractions and out-of-range bytes raise `value`. +`buf_len`, `f64_to_bytes` and `inflate` return integer counts or bytes; +buffer storage and scalar buffer reads are floats. `buf_get`, `buf_peak`, +`buf_dot` and the byte/PCM codecs refuse non-finite input cells with `value`. + +Model token ids and generation limits follow N5 and the C int range; +fractional ids or token counts raise `value`. `eigen_generate` returns int +token ids, while `eigen_eval_loss` returns a float. Temperature, top_p and +training learning rates remain float quantities. Model JSON weights stay +float data; the loader status strings are unchanged. + +Audio playback (`audio_play`, `audio_play_loop`, `audio_stream_push`) refuses +stored non-finite buffer samples at the scalar boundary in both strict modes +(N13). Strict NaN preserves the first diagnostic and INVALID flag. Finite +samples clamp to [-1,1], then round PCM16 half away from zero (Q7/Q4). +Channel indices and loop counts follow N5; continuous mixer gain stays +fractional after clamping to [0,4]. + +Q7 magnitude validation also applies before SDL is loaded: + +```eigenscript +try: + gfx_delay of 1e300 +catch e: + print of e.kind +``` +```output +value +``` + +### Stored buffer data boundary + +Buffer kernels can retain non-finite data. Scalar indexing and iteration +raise `value` when that data is read, in every strict mode (N13). + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +try: + print of p[0] +catch e: + print of e.kind +``` +```output +value +``` + +### Buffer equality + +Buffers compare their stored doubles with IEEE equality: NaN differs from +everything, even itself; infinities compare by sign; signed zeros are equal. +Equality never raises or changes math flags, including under strict mode. + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +print of (p == p) +print of (p == g) +sgd_update of [p, p, 1.0] +print of (p == p) +``` +```output +true +false +false +``` diff --git a/docs/STDLIB.md b/docs/STDLIB.md index c7d62d1e6..3ec472b04 100644 --- a/docs/STDLIB.md +++ b/docs/STDLIB.md @@ -374,9 +374,9 @@ print of g[2] ``` ```output 3 -5 -7 -9 +5.0 +7.0 +9.0 ``` `x` is `[[1, 2, 3], [4, 5, 6]]` and `s` scales each column, so the gradient @@ -407,9 +407,9 @@ autograd.ag_sgd_step of [wn, 0.1] print of w[0] ``` ```output -7 -4 -6 +7.0 +4.0 +6.0 0.6 ``` @@ -1265,7 +1265,7 @@ print of (data.df_mean of [rows, "score"]) ```output 85.4 88 -80 +80.0 ``` ### lib/data.eigs diff --git a/docs/SYNTAX.md b/docs/SYNTAX.md index 4d640abee..0f4488f1a 100644 --- a/docs/SYNTAX.md +++ b/docs/SYNTAX.md @@ -512,7 +512,7 @@ print of (when is loss) # 3 — three assignments ```output 0.2 -0.26827341240613545 -0 +0.0 3 ``` @@ -540,7 +540,7 @@ loss is 80.0 print of (prev of loss) # 100 — the value before the latest assign ``` ```output -100 +100.0 ``` Returns `null` when `x` has been assigned fewer than two times. Note diff --git a/docs/TRACE.md b/docs/TRACE.md index bb1f237b9..161823e63 100644 --- a/docs/TRACE.md +++ b/docs/TRACE.md @@ -89,13 +89,13 @@ The tape is plain text, one record per line: | Record | Meaning | |--------|---------| -| `V ` | Version header — always the first record (e.g. `V 8 0.47.0`). Stamped once per tape-open; a journal appended across sessions carries one per session. See [Format Versioning](#format-versioning-411). | +| `V ` | Version header — always the first record (e.g. `V 9 0.47.0`). Stamped once per tape-open; a journal appended across sessions carries one per session. See [Format Versioning](#format-versioning-411). | | `B ` | Stream association, before that stream's events. Describes state grouping and host/causal correspondence; folded as metadata by stepping, never a source-line stop. See below. | | `L ` | Source-line event (from `OP_LINE`, and when native code that ran EigenScript — a builtin's callback, an embedder's eval — gets control back: the line it entered with, so an assignment it makes next is filed under the same line on the tape as in live history, #1434; v5 carries the stream ID shown here). Duplicate lines within the same stream with no intervening `A`/`N` in that stream are deduped — the compiler emits per-statement LINEs and bare repeats are noise. | | `S ` | Scope transition (#539 v2): the `A` records that follow belong to this frame instance — `` is the chunk name (``, ``, or the function name), `` the 0-based frame depth, `` a per-thread monotonically increasing frame-instance id stamped at frame push. Emitted lazily with the same dedup discipline as `L`: only when the frame owning the next assignment differs from the last `S`, so the byte cost lands at call boundaries that actually assign. Two invocations of the same function carry different serials — their local streams never merge. Skipped on replay; folded by `--step`. | | `A =` | Assignment delta: a binding changed. Fires at **every scope** — function locals included — and is scope-qualified by the preceding `S` record, so a function-local `i` and the top-level `i` are separate streams (`--step` resolves names innermost-first along the reconstructed call chain, with shadowing). | | `N =` | Nondeterministic builtin return — the replay-determinism substrate. | -| `C ` | Immediate continuation of an oversized `N` value (v8). See [Value serialization](#value-serialization). | +| `C ` | Immediate continuation of an oversized `N` value (v9). See [Value serialization](#value-serialization). | | `O cfg ` | Observer configuration in force (v3). Written whenever the state's observer knobs differ from what this stream last announced, before its next `L`/`A`/`N`/`O win` record. See [Observer Configuration](#observer-configuration-10441045). | ### Stream identity and correspondence (v5, #1286) @@ -243,8 +243,12 @@ identity: `what is x at N`, `e.line` and error headers. `N` records are written with full fidelity so they can be parsed back into real values on replay: -- Numbers and `null` are written verbatim; a bool is written `true` or - `false` and read back as a bool (format v6, #1637). +- Ints are exact signed decimal digits; floats retain a decimal point or + exponent (`3.0`, `-0.0`, `1e+300`) so replay preserves the numeric kind + (format v9, #1758). An out-of-range int digit token is malformed rather + than rounded through a double. Version 6 tapes are refused by the format + check; re-record them with this binary. `null` is written verbatim; a bool + is written `true` or `false` and read back as a bool (#1637). - Strings are double-quoted; `\"`, `\\`, `\n`, `\r` are escaped, other control/non-printable bytes become `\xNN`. - Lists and dicts are emitted recursively: `[1, 2, 3]`, @@ -291,7 +295,7 @@ perspective lands on the tape as an `N` record: otherwise-impossible result shapes and re-raises the identical error without live filesystem access. The fixed-cap readers `read_text`, `read_bytes`, and `read_bytes_buf` instead record an over-cap - file's observed size as a `VAL_NUM` `N` record and reconstruct the error + file's observed size as a `VAL_INT` `N` record and reconstruct the error containing both size and active cap. Empty files remain ordinary successful empty string/list/buffer records. Mutating file builtins return their taped success value or re-raise their @@ -378,7 +382,7 @@ perspective lands on the tape as an `N` record: microphone; the recorded device id and sample buffers (the `b[…]` encoding) are served instead. `audio_capture_read` retains its 2048-sample per-call device-read bound; those values fit one `N` record without needing - v8 continuation records. Drain loops + v9 continuation records. Drain loops ("read until empty") replay faithfully: one record per call, empties included. (`audio_capture_close` is deterministic — always `null` — and untraced; under replay it is a no-op because no device was @@ -812,11 +816,18 @@ everywhere else in the runtime (version-and-reject, never migrate). answer (`file_exists`, `is_dir`, `mkdir`, ...) as `1`/`0`, so replaying it on a v6 binary would hand a number where the program now gets a bool; it is refused instead (covered by the existing replay kind-validation cases in - `tests/test_replay.sh`). **v7** (#1885) adds metadata plus bounded chunk records for `import` and + `tests/test_replay.sh`). **v7** (#1885) added metadata plus bounded chunk records for `import` and `load_file` source, making module acquisition replayable without live reads. - **v8** (#1816) adds length-framed `N` values and `C` continuation records so - answers larger than 64 KiB round-trip without truncation. - Within a v8 tape, replay also refuses a recorded value of a kind its builtin cannot + Main's **v8** (#1816) adds length-framed `N` values and `C` continuation + records for answers larger than 64 KiB. The Phase 2 bank independently + used v8 for exact int digit tokens, integral-float `.0` markers (#1758), + and chunked module-source records (#1885). + **v9** combines both v8 contracts: exact numeric kinds, replayable module + sources, and length-framed oversized answers. An old main v8 numeric + digit token denotes a float, whereas the Phase 2 reader gives it int kind, + so either old v8 format is refused rather than silently reinterpreted. + Every older format is refused by replay, stepping and DAP. + Within a current-format tape, replay also refuses a recorded value of a kind its builtin cannot return, with exit 3 and a message naming the record, the builtin, the kinds it returns and the tape version. Every taped builtin declares its return kinds in one table (`k_tape_kinds` in `src/trace.c`; for example diff --git a/docs/design/phase2-inventory.txt b/docs/design/phase2-inventory.txt new file mode 100644 index 000000000..35386caa2 --- /dev/null +++ b/docs/design/phase2-inventory.txt @@ -0,0 +1,2923 @@ +# docs/design/phase2-inventory.txt -- generated by tools/phase2_inventory.py; do not edit. +# Population: VOCAB token occurrences in src/*.c src/*.h, comments and literals stripped. +examined=2206 classified=2206 unclassified=0 +files=67 tokens=117 role_rules=8 derived_accessors=21 wrappers=8 + +# Retired (required absent): buf_num_arg -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): buffer_read_byte -- R1.6e: gfx_fb and ppu_render_frame use buffer_byte_at +# Retired (required absent): buffer_read_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): dict_value_get_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): dict_value_rewrite_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): dict_value_set_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_elem_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_list_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_num_arg -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_num_arg_slow -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_opt_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_slot_elem_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_slot_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): eigs_slot_opt_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): finite_num_to_byte -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): list_append_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): list_get_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): list_rewrite_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): list_set_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): make_num -- R1.6z: bridge deleted; every caller migrated +# Retired (required absent): make_num_permanent -- R1.6z: bridge deleted; every caller migrated +## Per category +(a) type tests 503 CASE=61 DATA=4 TEST=435 YIELD=3 +(b) payload reads 615 RAW=133 READ=482 +(c) producers 536 GUARD=79 PARSE=3 PRODUCE=428 TAGSET=9 WRITE=17 +(d) JIT emitters and guards 125 EMIT=87 GUARD=1 PRODUCE=6 RAW=27 TAGSET=1 TEST=2 WRITE=1 +(e) number formatting 23 CASE=2 FORMAT=4 PARSE=1 RAW=8 READ=3 TEST=5 +(f) equality, ordering and hashing 51 CASE=2 PRODUCE=4 RAW=15 READ=7 TEST=23 +(g) tape encode and decode 68 CASE=4 DATA=5 FORMAT=6 PARSE=7 PRODUCE=8 RAW=31 READ=2 TEST=5 +(h) JSON, store and db encoders 89 CASE=4 FORMAT=7 PARSE=5 PRODUCE=22 RAW=8 READ=24 TEST=19 +(i) observer numeric paths 112 CASE=6 FORMAT=1 GUARD=1 PARSE=1 PRODUCE=35 RAW=26 READ=6 TEST=36 +(j) embed API 84 CASE=2 DATA=2 PRODUCE=2 RAW=8 READ=60 TEST=6 WRITE=4 + +## Per token +make_int 229 +val_is_number 184 +make_float 181 +VAL_INT 163 +VAL_FLOAT 156 +SLOT_F64_RAW 75 +eigs_float_arg 73 +num_guard 64 +VAL_F64_RAW 62 +number 59 +eigs_value_as_num 57 +VAL_I64_RAW 56 +AST_NUM 51 +eigs_list_float 47 +slot_from_f64 42 +slot_is_f64 42 +eigs_int_arg_range 38 +eigs_int_arg 35 +slot_is_smi 32 +eigs_slot_float 27 +buffer_read_f64 26 +eigs_number_arg 24 +slot_f64_value 23 +eigs_list_int_range 21 +slot_from_int 19 +eigs_num_text 18 +strtod 16 +slot_smi_val 15 +list_set_f64 14 +eigs_list_int 13 +num_to_i64_exact 13 +slot_int_value 13 +EIGS_NUM_MAX 11 +eigs_magnitude_arg 11 +NUM_REUSE 10 +buffer_byte_at 10 +eigs_geom_shape 10 +eigs_slot_opt_float 9 +eigs_byte_arg 8 +eigs_bounded_arg 7 +eigs_list_bounded 6 +eigs_list_magnitude 6 +eigs_opt_int_range 6 +eigs_slot_int 6 +num_guard_named 6 +buffer_index_at 5 +eigs_slot_int_range 5 +eigs_slot_magnitude 5 +VAL_F64_OFFSET 4 +buf_float_arg 4 +dict_value_rewrite_f64 4 +eigs_elem_byte 4 +eigs_opt_magnitude 4 +eigs_elem_bounded 3 +eigs_elem_float 3 +eigs_elem_int 3 +eigs_elem_int_range 3 +eigs_elem_magnitude 3 +eigs_elem_number 3 +eigs_opt_bounded 3 +eigs_opt_byte 3 +eigs_opt_float 3 +eigs_opt_int 3 +eigs_opt_number 3 +eigs_slot_byte 3 +eigs_slot_elem_byte 3 +eigs_slot_elem_number 3 +list_get_f64 3 +list_rewrite_f64 3 +list_set_int 3 +dict_value_rewrite_int 2 +eigs_list_byte 2 +eigs_list_number 2 +eigs_slot_bounded 2 +eigs_slot_number 2 +eigs_slot_opt_int_range 2 +f64_ 2 +list_rewrite_int 2 +make_float_permanent 2 +atof 1 +d_ 1 +dict_value_get_f64 1 +dict_value_get_int 1 +dict_value_set_f64 1 +dict_value_set_int 1 +eigs_slot_elem_bounded 1 +eigs_slot_elem_float 1 +eigs_slot_elem_int 1 +eigs_slot_elem_int_range 1 +eigs_slot_elem_magnitude 1 +eigs_slot_opt_bounded 1 +eigs_slot_opt_byte 1 +eigs_slot_opt_int 1 +eigs_slot_opt_magnitude 1 +eigs_slot_opt_number 1 +i64_ 1 +list_append_f64 1 +list_append_int 1 +list_get_int 1 +buf_num_arg 0 +buffer_read_byte 0 +buffer_read_num 0 +dict_value_get_num 0 +dict_value_rewrite_num 0 +dict_value_set_num 0 +eigs_elem_num 0 +eigs_list_num 0 +eigs_num_arg 0 +eigs_num_arg_slow 0 +eigs_opt_num 0 +eigs_slot_elem_num 0 +eigs_slot_num 0 +eigs_slot_opt_num 0 +finite_num_to_byte 0 +list_append_num 0 +list_get_num 0 +list_rewrite_num 0 +list_set_num 0 +make_num 0 +make_num_permanent 0 +data.number (AST) 3 + +## Per file (category counts) +builtins.c 323 a=84 b=75 c=115 e=4 f=13 h=22 i=10 +vm.c 320 a=74 b=102 c=80 e=2 f=10 i=52 +ext_gfx.c 260 a=100 b=73 c=87 +eigenscript.c 207 a=29 b=83 c=25 e=7 f=16 i=47 +builtins_tensor.c 194 a=53 b=58 c=83 +container.h 122 a=25 b=60 c=37 +builtins_buf.c 105 a=33 b=33 c=39 +jit.c 94 d=94 +eigenscript.h 63 a=6 b=41 c=15 e=1 +trace.c 63 g=63 +embed_smoke.c 60 j=60 +ext_store.c 58 h=58 +builtins_host.c 54 a=22 b=19 c=13 +jit_smoke.c 31 d=31 +lint.c 29 a=29 +chunk.c 25 a=4 e=9 f=12 +compiler.c 24 a=12 b=8 c=2 i=2 +value_slot.h 23 a=8 b=11 c=4 +ext_net.c 22 a=4 b=11 c=7 +eigs_embed.c 17 j=17 +model_infer.c 16 a=2 b=6 c=8 +ext_http.c 14 a=3 b=4 c=7 +builtins_int.c 11 b=4 c=7 +lexer.c 11 b=10 c=1 +model_io.c 11 a=5 b=5 h=1 +ext_db.c 8 h=8 +model_train.c 8 a=2 b=4 c=1 i=1 +embed_concurrent.c 6 j=6 +task.c 6 a=1 b=1 c=4 +parser.c 5 a=2 b=2 c=1 +tape_read.c 5 g=5 +eigenlsp.c 3 a=2 b=1 +lint_host.c 3 a=3 +main.c 3 b=3 +eigs_embed.h 1 j=1 +vm.h 1 b=1 + +## Executors (arms per opcode; roles: interp jit_scan jit_emit leaf_scan leaf_exec) +OP_ADD interp jit_scan jit_emit leaf_scan leaf_exec +OP_BAND interp - jit_emit - - +OP_BINARY_LINE interp jit_scan jit_emit leaf_scan leaf_exec +OP_BINARY_LINE_END interp jit_scan jit_emit leaf_scan leaf_exec +OP_BNOT interp - jit_emit - - +OP_BOR interp - jit_emit - - +OP_BXOR interp - jit_emit - - +OP_CALL interp jit_scan jit_emit - - +OP_CONST interp jit_scan jit_emit leaf_scan leaf_exec +OP_DIV interp jit_scan jit_emit - - +OP_DOT_GET interp jit_scan jit_emit - - +OP_DOT_SET interp jit_scan jit_emit - - +OP_DUP interp jit_scan jit_emit - - +OP_DUP2 interp jit_scan jit_emit - - +OP_EQ interp jit_scan jit_emit - - +OP_FALSE interp jit_scan jit_emit - - +OP_GE interp jit_scan jit_emit - - +OP_GET_LOCAL interp jit_scan jit_emit leaf_scan leaf_exec +OP_GET_NAME interp jit_scan jit_emit - - +OP_GT interp jit_scan jit_emit - - +OP_INDEX_GET interp jit_scan jit_emit leaf_scan leaf_exec +OP_INDEX_SET interp jit_scan jit_emit - - +OP_ITER_NEXT interp jit_scan jit_emit - - +OP_JUMP interp jit_scan jit_emit - - +OP_JUMP_BACK interp jit_scan jit_emit - - +OP_JUMP_IF_FALSE interp jit_scan jit_emit - - +OP_JUMP_IF_FALSE_PEEK interp jit_scan jit_emit - - +OP_JUMP_IF_TRUE interp jit_scan jit_emit - - +OP_JUMP_IF_TRUE_PEEK interp jit_scan jit_emit - - +OP_LE interp jit_scan jit_emit - - +OP_LINE interp jit_scan - leaf_scan leaf_exec +OP_LOCAL_DOT_GET interp jit_scan jit_emit leaf_scan leaf_exec +OP_LOCAL_DOT_SET interp jit_scan jit_emit - - +OP_LOCAL_IDX_DOT_GET interp jit_scan jit_emit - - +OP_LOCAL_IDX_GET interp jit_scan jit_emit - - +OP_LOOP_CAP_NAMED interp jit_scan jit_emit - - +OP_LOOP_ENTER_NAMED interp jit_scan jit_emit - - +OP_LOOP_EXIT_NAMED interp jit_scan jit_emit - - +OP_LOOP_STALL_NAMED interp jit_scan jit_emit - - +OP_LT interp jit_scan jit_emit - - +OP_MOD interp - jit_emit - - +OP_MUL interp jit_scan jit_emit leaf_scan leaf_exec +OP_NE interp jit_scan jit_emit - - +OP_NEG interp jit_scan jit_emit - - +OP_NOT interp jit_scan jit_emit - - +OP_NULL interp jit_scan jit_emit - - +OP_NUM_ONE interp jit_scan jit_emit leaf_scan leaf_exec +OP_NUM_ZERO interp jit_scan jit_emit leaf_scan leaf_exec +OP_OBSERVE_ASSIGN interp jit_scan jit_emit - - +OP_OBSERVE_ASSIGN_LOCAL interp jit_scan jit_emit - - +OP_OBSERVE_NAME_POST interp jit_scan jit_emit - - +OP_POP interp jit_scan jit_emit - - +OP_REPORT_SLOT interp jit_scan jit_emit - - +OP_RETURN interp jit_scan jit_emit leaf_scan leaf_exec +OP_RETURN_NULL interp jit_scan jit_emit - - +OP_SET_FN_NAME_LOCAL interp jit_scan jit_emit - - +OP_SET_LOCAL interp jit_scan jit_emit - - +OP_SET_NAME interp jit_scan jit_emit - - +OP_SET_NAME_LOCAL interp jit_scan jit_emit - - +OP_SHL interp - jit_emit - - +OP_SHR interp - jit_emit - - +OP_SUB interp jit_scan jit_emit leaf_scan leaf_exec +OP_TRUE interp jit_scan jit_emit - - +OP_UNOBSERVED_BEGIN interp jit_scan jit_emit - - +OP_UNOBSERVED_END interp jit_scan jit_emit - - + +## Numeric arms (JIT emitter and leaf evaluator arms holding a numeric site) +jit_emit OP_ADD OP_ADD OP_DIV OP_MUL OP_SUB +jit_emit OP_BAND OP_BAND OP_BOR OP_BXOR OP_SHL OP_SHR +jit_emit OP_BINARY_LINE OP_BINARY_LINE +jit_emit OP_CONST OP_CONST +jit_emit OP_EQ OP_EQ OP_GE OP_GT OP_LE OP_LT OP_NE +jit_emit OP_INDEX_SET OP_INDEX_SET +jit_emit OP_JUMP_IF_FALSE OP_JUMP_IF_FALSE OP_JUMP_IF_FALSE_PEEK OP_JUMP_IF_TRUE OP_JUMP_IF_TRUE_PEEK +jit_emit OP_LOCAL_DOT_GET OP_LOCAL_DOT_GET +jit_emit OP_LOCAL_DOT_SET OP_LOCAL_DOT_SET +jit_emit OP_MOD OP_MOD +jit_emit OP_NEG OP_BNOT OP_NEG OP_NOT +jit_emit OP_NOT OP_NOT +jit_emit OP_NULL OP_FALSE OP_NULL OP_NUM_ONE OP_NUM_ZERO OP_TRUE +jit_emit OP_SET_LOCAL OP_SET_LOCAL +jit_emit OP_SET_NAME OP_SET_FN_NAME_LOCAL OP_SET_NAME OP_SET_NAME_LOCAL +leaf_exec OP_ADD|OP_SUB|OP_MUL OP_ADD OP_MUL OP_SUB +leaf_exec OP_CONST OP_CONST +leaf_exec OP_INDEX_GET OP_INDEX_GET +leaf_exec OP_NUM_ONE OP_NUM_ONE +leaf_exec OP_NUM_ZERO OP_NUM_ZERO + +## Slot classifiers (R1.1 worklist) +D1=65 D2=19 D3=26 T=69 dispositions=110 missing=0 stale=0 +D1 container.h:dict_value_get_f64 guarded slot_f64_value=1 +D1 container.h:dict_value_get_int smi-arm slot_int_value=1 +D1 container.h:dict_value_get_ref smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 +D1 container.h:dict_value_get_return smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 +D1 container.h:eigs_slot_box smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 +D1 container.h:eigs_slot_store_borrow guarded slot_as_ptr=1 slot_is_f64=1 slot_is_heap=1 +D1 container.h:eigs_slot_store_owned guarded slot_as_ptr=1 slot_is_f64=1 slot_is_heap=1 +D1 container.h:list_get_f64 guarded slot_f64_value=1 +D1 container.h:list_get_int smi-arm slot_int_value=1 +D1 container.h:list_get_ref smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 +D1 container.h:list_get_return smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 +D1 container.h:slot_type smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_heap=1 slot_is_null=1 slot_is_smi=1 slot_type=1 +D1 container.h:slot_view smi-arm eigs_slot_ref=1 slot_f64_value=1 slot_is_f64=1 slot_is_smi=1 slot_smi_val=1 slot_type=1 +D1 eigenscript.c:ent_child_slot smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 slot_smi_val=1 +D1 eigenscript.c:module_ns_project guarded slot_is_f64=2 +D1 eigenscript.c:observer_entropy_now smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_ptr=1 slot_is_smi=1 slot_smi_val=1 +D1 eigenscript.c:slot_to_value smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_heap=1 slot_is_null=1 slot_is_smi=1 slot_smi_val=1 +D1 embed_smoke.c:main guarded slot_is_bool=1 slot_is_smi=1 slot_smi_val=1 +D1 ext_store.c:store_json_encode_slot smi-arm eigs_slot_ref=1 slot_is_f64=1 slot_is_smi=1 slot_smi_val=1 +D1 trace.c:trace_source_replay guarded list_elem_type=5 slot_f64_value=1 slot_int_value=1 +D1 trace.c:write_slot smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_heap=1 slot_is_null=1 slot_is_smi=1 slot_smi_val=1 +D1 value_slot.h:slot_f64_value guarded slot_as_ptr=1 slot_f64_value=1 slot_is_f64=1 slot_is_heap=1 +D1 value_slot.h:slot_int_value smi-arm slot_as_ptr=1 slot_int_value=1 slot_is_heap=1 slot_is_smi=1 slot_smi_val=1 +D1 value_slot.h:slot_is_f64 guarded SLOT_QNAN_MASK=2 slot_is_f64=1 +D1 value_slot.h:slot_is_smi smi-arm SLOT_TAG_MASK=1 TAG_SMI=1 slot_is_smi=1 +D1 value_slot.h:slot_smi_val smi-arm SLOT_PAYLOAD_MASK=1 SLOT_SMI_SIGN=2 slot_smi_val=1 +D1 value_slot.h:slot_truthy_immediate smi-arm SLOT_FALSE_BITS=1 SLOT_TRUE_BITS=1 slot_is_f64=1 slot_is_null=1 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:#define ARITH_FAST guarded slot_as_ptr=2 slot_f64_value=2 slot_is_ptr=2 +D1 vm.c:#define INT_ARITH smi-arm slot_int_value=2 +D1 vm.c:#define INT_BINOP_R smi-arm slot_f64_value=1 +D1 vm.c:#define NUM_CMP smi-arm slot_as_ptr=2 slot_f64_value=2 slot_is_ptr=2 +D1 vm.c:#define READ_SLICE_BOUND smi-arm slot_as_ptr=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:jit_helper_dot_set guarded slot_as_ptr=1 slot_is_f64=1 slot_is_ptr=1 +D1 vm.c:jit_helper_index_get guarded slot_as_ptr=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 +D1 vm.c:jit_helper_index_set guarded slot_as_ptr=1 slot_is_f64=3 slot_is_ptr=1 +D1 vm.c:jit_helper_iter_next guarded slot_as_ptr=1 slot_is_ptr=1 slot_is_smi=1 +D1 vm.c:jit_helper_local_dot_set guarded slot_is_f64=1 +D1 vm.c:loop_counter_load smi-arm slot_int_value=1 +D1 vm.c:loop_counter_store smi-arm SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 slot_is_smi=1 +D1 vm.c:loop_iter_store smi-arm SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 slot_is_smi=1 +D1 vm.c:obs_dump_scope smi-arm slot_as_ptr=3 slot_is_bool=1 slot_is_f64=1 slot_is_ptr=3 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:obs_dump_value smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:obs_slot_num smi-arm slot_as_ptr=2 slot_is_f64=1 slot_is_heap=1 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:obs_slot_observable refuse slot_is_bool=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 +D1 vm.c:slot_bridge_unwrap smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_null=1 slot_is_smi=1 +D1 vm.c:slot_truthy smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_heap=1 slot_is_null=1 slot_is_smi=1 slot_smi_val=1 +D1 vm.c:slot_type_name smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 slot_is_smi=1 +D1 vm.c:vm_bit_operand smi-arm slot_f64_value=1 slot_int_value=1 +D1 vm.c:vm_iter_index smi-arm slot_f64_value=1 slot_int_value=1 +D1 vm.c:vm_iter_limit smi-arm slot_int_value=1 +D1 vm.c:vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL guarded slot_f64_value=2 +D1 vm.c:vm_leaf_accessor_exec/OP_INDEX_GET guarded slot_as_ptr=1 slot_f64_value=1 slot_is_ptr=1 +D1 vm.c:vm_number_compare smi-arm slot_f64_value=2 slot_int_value=2 +D1 vm.c:vm_run_ex/CASE(BNOT) smi-arm slot_f64_value=1 +D1 vm.c:vm_run_ex/CASE(DOT_SET) guarded slot_as_ptr=1 slot_is_f64=1 slot_is_ptr=1 +D1 vm.c:vm_run_ex/CASE(EQ) guarded slot_f64_value=2 slot_is_smi=2 +D1 vm.c:vm_run_ex/CASE(INDEX_GET) guarded slot_as_ptr=1 slot_is_f64=1 slot_is_ptr=1 +D1 vm.c:vm_run_ex/CASE(INDEX_SET) guarded slot_as_ptr=1 slot_is_f64=3 slot_is_ptr=1 +D1 vm.c:vm_run_ex/CASE(ITER_NEXT) guarded slot_is_smi=1 +D1 vm.c:vm_run_ex/CASE(LOCAL_DOT_SET) guarded slot_is_f64=1 +D1 vm.c:vm_run_ex/CASE(LOCAL_IDX_DOT_SET) guarded slot_is_f64=1 +D1 vm.c:vm_run_ex/CASE(NE) guarded slot_f64_value=2 slot_is_smi=2 +D1 vm.c:vm_run_ex/CASE(NEG) smi-arm slot_as_ptr=1 slot_f64_value=1 slot_int_value=1 slot_is_ptr=1 +D1 vm.c:vm_slot_lift smi-arm slot_as_ptr=1 slot_is_bool=1 slot_is_f64=1 slot_is_null=1 slot_is_ptr=1 slot_is_smi=1 +D1 vm.c:vm_store_local_slot guarded slot_as_ptr=1 slot_is_f64=1 slot_is_heap=1 +D2 builtins.c:bool_gate_name guarded slot_as_ptr=1 slot_is_ptr=1 +D2 builtins.c:sort_cmp_str guarded slot_as_ptr=2 +D2 builtins_host.c:ls_entry_cmp guarded slot_as_ptr=2 +D2 container.h:eigs_slot_ref refuse eigs_slot_ref=1 slot_as_ptr=1 slot_is_bool=1 slot_is_heap=1 slot_is_null=1 +D2 eigenscript.c:#define GC_EDGE_TABLE guarded slot_as_ptr=6 slot_is_ptr=3 +D2 eigenscript.c:env_get_hashed guarded slot_as_ptr=1 slot_is_ptr=1 +D2 eigenscript.c:env_get_local_hashed guarded slot_as_ptr=1 slot_is_ptr=1 +D2 eigenscript.c:gc_collect_at_exit guarded slot_as_ptr=1 slot_is_ptr=1 +D2 value_slot.h:#define slot_as_heap refuse slot_as_heap=1 slot_as_ptr=1 +D2 value_slot.h:slot_as_ptr refuse SLOT_PAYLOAD_MASK=1 slot_as_ptr=1 slot_is_heap=1 +D2 vm.c:obs_slot_value guarded slot_as_ptr=1 slot_is_bool=1 +D2 vm.c:vm_borrow_scan guarded slot_as_ptr=2 slot_is_heap=2 +D2 vm.c:vm_builtin_name_for guarded slot_as_ptr=1 slot_is_heap=1 +D2 vm.c:vm_leaf_accessor_exec/OP_LOCAL_DOT_GET guarded slot_as_ptr=1 slot_is_null=1 slot_is_ptr=1 +D2 vm.c:vm_local_lift guarded slot_as_ptr=1 slot_is_null=1 slot_is_ptr=1 +D2 vm.c:vm_run_ex/CASE(DESTRUCTURE_UNPACK) guarded slot_as_ptr=2 slot_is_ptr=1 +D2 vm.c:vm_run_ex/CASE(DISPATCH) smi-arm slot_as_ptr=1 slot_is_ptr=1 slot_type=1 +D2 vm.c:vm_run_ex/CASE(SLICE_GET) guarded slot_as_ptr=1 slot_is_ptr=1 +D2 vm.c:vm_slot_value_opaque guarded slot_as_ptr=1 slot_is_ptr=1 +D3 container.h:eigs_container_slot not-a-classifier SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 +D3 container.h:eigs_slot_adopt guarded slot_is_heap=1 +D3 eigenscript.c:slot_from_value not-a-classifier SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 +D3 jit.c:jit_compile_to_thunk/OP_EQ not-a-classifier SLOT_FALSE_BITS=1 SLOT_TRUE_BITS=1 +D3 jit.c:jit_compile_to_thunk/OP_NEG not-a-classifier SLOT_FALSE_BITS=1 SLOT_TRUE_BITS=1 +D3 jit.c:jit_compile_to_thunk/OP_NULL not-a-classifier SLOT_FALSE_BITS=1 SLOT_TRUE_BITS=1 +D3 jit_smoke.c:run_bool_cases not-a-classifier SLOT_FALSE_BITS=2 SLOT_TRUE_BITS=3 +D3 jit_smoke.c:run_index_bail_cases guarded slot_is_null=1 +D3 value_slot.h:slot_decref guarded SLOT_PAYLOAD_MASK=1 SLOT_QNAN_MASK=2 SLOT_TAG_MASK=1 TAG_HEAP=1 +D3 value_slot.h:slot_false not-a-classifier SLOT_FALSE_BITS=1 +D3 value_slot.h:slot_from_bool not-a-classifier TAG_BOOL=1 +D3 value_slot.h:slot_from_heap guarded SLOT_NULL_BITS=1 SLOT_PAYLOAD_MASK=1 TAG_HEAP=1 +D3 value_slot.h:slot_from_int not-a-classifier SLOT_PAYLOAD_MASK=1 SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 TAG_SMI=1 +D3 value_slot.h:slot_incref guarded SLOT_PAYLOAD_MASK=1 SLOT_QNAN_MASK=2 SLOT_TAG_MASK=1 TAG_HEAP=1 +D3 value_slot.h:slot_is_bool guarded SLOT_TAG_MASK=1 TAG_BOOL=1 slot_is_bool=1 +D3 value_slot.h:slot_is_heap guarded SLOT_TAG_MASK=1 TAG_HEAP=1 slot_is_heap=1 +D3 value_slot.h:slot_is_null guarded SLOT_NULL_BITS=1 slot_is_null=1 +D3 value_slot.h:slot_is_ptr guarded SLOT_TAG_MASK=1 TAG_HEAP=1 slot_is_ptr=1 +D3 value_slot.h:slot_null not-a-classifier SLOT_NULL_BITS=1 +D3 value_slot.h:slot_true not-a-classifier SLOT_TRUE_BITS=1 +D3 vm.c:jit_helper_dot_get guarded slot_is_null=1 +D3 vm.c:jit_helper_local_idx_dot_get guarded slot_is_null=1 +D3 vm.c:vm_local_is_null guarded slot_is_null=1 +D3 vm.c:vm_run_ex/CASE(DOT_GET) guarded slot_is_null=1 +D3 vm.c:vm_run_ex/CASE(INT_CONST) not-a-classifier SLOT_SMI_MAX=1 SLOT_SMI_MIN=1 +D3 vm.c:vm_run_ex/CASE(LOCAL_IDX_DOT_GET) guarded slot_is_null=1 + +## Builtins +registered=388 numeric=309 classified=309 unclassified=0 stale=0 +classes: float-producing=58 int-preserving=37 int-requiring=161 other=53 +LOSS (37): abs add ceil db_execute db_query_json db_query_value floor hex http_post json_build json_decode json_encode json_path max min monotonic_ns multiply negative num num_copy random_int round scan_int_tokens scan_ints seed_random shared_incr sign_extend sort sort_by store_delete store_get store_put store_query store_update subtract sum task_sched_seed +Q7: geometry=9 bounded=13 magnitude=13 +abs builtin_abs int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +acos builtin_acos float-producing - - eigs_float_arg,make_float,val_is_number +add builtin_tensor_add int-preserving LOSS - eigs_float_arg,eigs_number_arg,make_float,make_int,num_guard,val_is_number +append builtin_append other - - VAL_FLOAT +asin builtin_asin float-producing - - eigs_float_arg,make_float,val_is_number +assert builtin_assert other - - eigs_num_text +atan builtin_atan float-producing - - eigs_float_arg,make_float,val_is_number +atan2 builtin_atan2 float-producing - - eigs_float_arg,make_float,val_is_number +audio_capture_open builtin_audio_capture_open int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_int_range,make_int +audio_envelope builtin_audio_envelope float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_slot_magnitude,make_float,slot_from_f64 +audio_gain builtin_audio_gain float-producing - - VAL_FLOAT,VAL_INT,eigs_list_float,make_float +audio_mix builtin_audio_mix float-producing - - VAL_FLOAT,VAL_INT,eigs_list_float,make_float +audio_music_play builtin_audio_music_play int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_int_range +audio_music_volume builtin_audio_music_volume other - bounded VAL_FLOAT,VAL_INT,eigs_bounded_arg,eigs_list_bounded,val_is_number +audio_noise builtin_audio_noise float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_slot_magnitude,make_float,slot_from_f64 +audio_open builtin_audio_open int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_int_range,make_int +audio_pause builtin_audio_pause int-requiring - - eigs_int_arg_range,val_is_number +audio_play builtin_audio_play other - bounded VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_slot_bounded,eigs_slot_float,make_int,slot_from_f64 +audio_play_loop builtin_audio_play_loop int-requiring - bounded VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_list_int_range,eigs_slot_bounded,eigs_slot_float,make_int,slot_from_f64 +audio_queue_size builtin_audio_queue_size int-preserving - - make_int +audio_saw builtin_audio_saw float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_slot_magnitude,make_float,slot_from_f64 +audio_sine builtin_audio_sine float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_slot_magnitude,make_float,slot_from_f64 +audio_square builtin_audio_square float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_slot_magnitude,make_float,slot_from_f64 +audio_stop builtin_audio_stop int-requiring - - eigs_int_arg_range,make_int,val_is_number +audio_stream_open builtin_audio_stream_open int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_int_range,make_int +audio_stream_push builtin_audio_stream_push other - bounded VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_slot_bounded,eigs_slot_float,slot_from_f64 +audio_stream_queued builtin_audio_stream_queued int-preserving - - make_int +audio_sweep builtin_audio_sweep float-producing - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_list_int,eigs_slot_magnitude,make_float,slot_from_f64 +audio_volume builtin_audio_volume int-requiring - bounded VAL_FLOAT,VAL_INT,eigs_list_bounded,eigs_list_int_range,make_int +bit_and builtin_bit_and int-requiring - - eigs_int_arg,make_int,val_is_number +bit_not builtin_bit_not int-requiring - - eigs_int_arg,make_int,val_is_number +bit_or builtin_bit_or int-requiring - - eigs_int_arg,make_int,val_is_number +bit_shl builtin_bit_shift_left int-requiring - - eigs_int_arg,make_int,val_is_number +bit_shr builtin_bit_shift_right int-requiring - - eigs_int_arg,make_int,val_is_number +bit_xor builtin_bit_xor int-requiring - - eigs_int_arg,make_int,val_is_number +buf_copy builtin_buf_copy int-requiring - - VAL_FLOAT,VAL_INT,eigs_slot_int +buf_deinterleave builtin_buf_deinterleave int-requiring - - VAL_FLOAT,VAL_INT,eigs_int_arg,eigs_slot_int,val_is_number +buf_dot builtin_buf_dot float-producing - - VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_slot_int,make_float,num_guard +buf_fill builtin_buf_fill float-producing - - VAL_FLOAT,VAL_INT,buf_float_arg,eigs_slot_float,eigs_slot_int +buf_from_list builtin_buf_from_list float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float +buf_from_pcm16le builtin_buf_from_pcm16le int-requiring - - VAL_FLOAT,VAL_INT,buffer_byte_at,eigs_slot_int,num_guard +buf_get builtin_buf_get int-requiring - - buffer_read_f64,eigs_list_int,make_float +buf_len builtin_buf_len int-preserving - - make_int +buf_mix builtin_buf_mix float-producing - - VAL_FLOAT,VAL_INT,buf_float_arg,eigs_slot_float,eigs_slot_int,num_guard +buf_peak builtin_buf_peak float-producing - - VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_slot_int,make_float +buf_resample_linear builtin_buf_resample_linear float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_int,num_guard +buf_scale_range builtin_buf_scale_range float-producing - - VAL_FLOAT,VAL_INT,buf_float_arg,eigs_slot_float,eigs_slot_int,num_guard +buf_set builtin_buf_set int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_float,eigs_list_int +buf_to_pcm16le builtin_buf_to_pcm16le other - bounded VAL_FLOAT,VAL_INT,buffer_read_f64,eigs_slot_int,num_guard +buffer builtin_buffer int-requiring - - VAL_FLOAT,VAL_INT,eigs_int_arg_range,eigs_list_int_range,val_is_number +build_corpus builtin_build_corpus int-requiring - - VAL_FLOAT,VAL_INT,eigs_int_arg_range,eigs_opt_int_range,make_int,number,val_is_number +ceil builtin_ceil int-preserving LOSS - eigs_number_arg,make_float,make_int,num_to_i64_exact,val_is_number +channel builtin_channel int-preserving - - make_int +channel_closed builtin_channel_closed int-requiring - - eigs_int_arg_range,val_is_number +char_at builtin_char_at int-requiring - - eigs_int_arg_range,val_is_number +chr builtin_chr int-requiring - - eigs_int_arg,val_is_number +clock_unix builtin_clock_unix float-producing - - make_float +close_channel builtin_close_channel int-requiring - - eigs_int_arg_range,val_is_number +copy_into builtin_copy_into int-requiring - - eigs_float_arg,eigs_int_arg_range,val_is_number +cos builtin_cos float-producing - - eigs_float_arg,make_float,val_is_number +db_execute builtin_db_execute other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text +db_query_json builtin_db_query_json other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text +db_query_value builtin_db_query_value other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text,make_float,make_int,strtod +dispatch builtin_dispatch int-requiring - - eigs_number_arg,num_to_i64_exact,val_is_number +divide builtin_tensor_divide float-producing - - eigs_float_arg,eigs_number_arg,make_float,make_int,num_guard,val_is_number +dot builtin_dot float-producing - - buffer_read_f64,make_float,num_guard +eigen_eval_loss builtin_eigen_eval_loss float-producing - - VAL_FLOAT,VAL_INT,eigs_list_int_range,eigs_slot_int_range,make_float +eigen_generate builtin_eigen_generate int-requiring - - eigs_slot_int_range,eigs_slot_opt_float,eigs_slot_opt_int_range,make_int +eigen_model_load builtin_eigen_model_load other - - eigs_int_arg_range,strtod,val_is_number +exec_capture builtin_exec_capture other - magnitude VAL_FLOAT,VAL_INT,eigs_list_float,eigs_magnitude_arg,make_float,make_int +exit builtin_exit int-requiring - - eigs_int_arg_range,val_is_number +exp builtin_tensor_exp float-producing - - eigs_number_arg,make_float,make_int,num_guard,val_is_number +f64_from_bytes builtin_f64_from_bytes int-requiring - - buffer_byte_at,eigs_slot_elem_byte,make_float,num_guard_named +f64_to_bytes builtin_f64_to_bytes other - - eigs_float_arg,make_int,val_is_number +fill builtin_fill int-requiring - - eigs_int_arg_range +floor builtin_floor int-preserving LOSS - eigs_number_arg,make_float,make_int,num_to_i64_exact,val_is_number +fround builtin_fround float-producing - - eigs_float_arg,make_float +gather builtin_tensor_gather int-requiring - - VAL_FLOAT,VAL_INT,buffer_index_at,buffer_read_f64,eigs_float_arg,eigs_int_arg,eigs_slot_elem_number,eigs_slot_int,make_float,make_int,val_is_number +get_at builtin_get_at int-requiring - - buffer_read_f64,eigs_int_arg_range,make_float,val_is_number +get_observer_thresholds builtin_get_observer_thresholds float-producing - - make_float +gfx_circle builtin_gfx_circle other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded,slot_from_f64 +gfx_clear builtin_gfx_clear other - bounded VAL_FLOAT,VAL_INT,eigs_list_bounded +gfx_clip builtin_gfx_clip other - geometry VAL_FLOAT,VAL_INT,eigs_geom_shape +gfx_delay builtin_gfx_delay other - magnitude eigs_magnitude_arg,val_is_number +gfx_fb builtin_gfx_fb int-requiring - geometry VAL_FLOAT,VAL_INT,buffer_byte_at,eigs_geom_shape,eigs_list_float,eigs_list_int_range,slot_from_f64 +gfx_line builtin_gfx_line other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded +gfx_open builtin_gfx_open other - magnitude VAL_FLOAT,VAL_INT,eigs_list_magnitude +gfx_point builtin_gfx_point other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded +gfx_poll builtin_gfx_poll int-preserving - - make_int +gfx_read builtin_gfx_read other - geometry VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_float,make_int +gfx_rect builtin_gfx_rect other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded +gfx_rrect builtin_gfx_rrect other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded,slot_from_f64 +gfx_text builtin_gfx_text other - geometry,bounded VAL_FLOAT,VAL_INT,eigs_geom_shape,eigs_list_bounded,eigs_list_float,slot_from_f64 +gfx_text_height builtin_gfx_text_height int-preserving - magnitude VAL_FLOAT,VAL_INT,eigs_list_magnitude,eigs_magnitude_arg,make_int,val_is_number +gfx_text_width builtin_gfx_text_width int-preserving - magnitude VAL_FLOAT,VAL_INT,eigs_list_magnitude,make_int +gfx_ticks builtin_gfx_ticks int-preserving - - make_int +hex builtin_hex int-requiring LOSS - eigs_int_arg,eigs_list_int,val_is_number +http_early_bind builtin_http_early_bind int-requiring - - eigs_int_arg_range,val_is_number +http_post builtin_http_post other LOSS - eigs_num_text +http_route builtin_http_route other - - eigs_num_text +http_route_authed builtin_http_route_authed other - - eigs_num_text +http_serve builtin_http_serve int-requiring - - eigs_int_arg_range,eigs_num_text +http_static builtin_http_static other - - eigs_num_text +i16_checked_add builtin_i16_checked_add int-requiring - - eigs_int_arg_range,make_int +i16_checked_mul builtin_i16_checked_mul int-requiring - - eigs_int_arg_range,make_int +i16_checked_sub builtin_i16_checked_sub int-requiring - - eigs_int_arg_range,make_int +i16_saturating_add builtin_i16_saturating_add int-requiring - - eigs_int_arg_range,make_int +i16_saturating_mul builtin_i16_saturating_mul int-requiring - - eigs_int_arg_range,make_int +i16_saturating_sub builtin_i16_saturating_sub int-requiring - - eigs_int_arg_range,make_int +i16_wrapping_add builtin_i16_wrapping_add int-requiring - - eigs_int_arg_range,make_int +i16_wrapping_mul builtin_i16_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +i16_wrapping_sub builtin_i16_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +i32_checked_add builtin_i32_checked_add int-requiring - - eigs_int_arg_range,make_int +i32_checked_mul builtin_i32_checked_mul int-requiring - - eigs_int_arg_range,make_int +i32_checked_sub builtin_i32_checked_sub int-requiring - - eigs_int_arg_range,make_int +i32_saturating_add builtin_i32_saturating_add int-requiring - - eigs_int_arg_range,make_int +i32_saturating_mul builtin_i32_saturating_mul int-requiring - - eigs_int_arg_range,make_int +i32_saturating_sub builtin_i32_saturating_sub int-requiring - - eigs_int_arg_range,make_int +i32_wrapping_add builtin_i32_wrapping_add int-requiring - - eigs_int_arg_range,make_int +i32_wrapping_mul builtin_i32_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +i32_wrapping_sub builtin_i32_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +i64_checked_add builtin_i64_checked_add int-requiring - - eigs_int_arg_range,make_int +i64_checked_mul builtin_i64_checked_mul int-requiring - - eigs_int_arg_range,make_int +i64_checked_sub builtin_i64_checked_sub int-requiring - - eigs_int_arg_range,make_int +i64_saturating_add builtin_i64_saturating_add int-requiring - - eigs_int_arg_range,make_int +i64_saturating_mul builtin_i64_saturating_mul int-requiring - - eigs_int_arg_range,make_int +i64_saturating_sub builtin_i64_saturating_sub int-requiring - - eigs_int_arg_range,make_int +i64_wrapping_add builtin_i64_wrapping_add int-requiring - - eigs_int_arg_range,make_int +i64_wrapping_mul builtin_i64_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +i64_wrapping_sub builtin_i64_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +i8_checked_add builtin_i8_checked_add int-requiring - - eigs_int_arg_range,make_int +i8_checked_mul builtin_i8_checked_mul int-requiring - - eigs_int_arg_range,make_int +i8_checked_sub builtin_i8_checked_sub int-requiring - - eigs_int_arg_range,make_int +i8_saturating_add builtin_i8_saturating_add int-requiring - - eigs_int_arg_range,make_int +i8_saturating_mul builtin_i8_saturating_mul int-requiring - - eigs_int_arg_range,make_int +i8_saturating_sub builtin_i8_saturating_sub int-requiring - - eigs_int_arg_range,make_int +i8_wrapping_add builtin_i8_wrapping_add int-requiring - - eigs_int_arg_range,make_int +i8_wrapping_mul builtin_i8_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +i8_wrapping_sub builtin_i8_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +index_of builtin_index_of int-preserving - - make_int +inflate builtin_inflate int-requiring - - VAL_FLOAT,VAL_INT,buffer_byte_at,eigs_byte_arg,eigs_slot_byte,make_int,val_is_number +join builtin_join other - - eigs_num_text +json_build builtin_json_build other LOSS - VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text,val_is_number +json_decode builtin_json_decode other LOSS - make_float,make_int,strtod +json_encode builtin_json_encode other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text +json_path builtin_json_path other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,eigs_num_text,make_float,make_int,strtod,val_is_number +leaky_relu builtin_tensor_leaky_relu float-producing - - VAL_FLOAT,VAL_INT,eigs_float_arg,eigs_slot_float,make_float,val_is_number +len builtin_len int-preserving - - make_int +list_contains builtin_list_contains other - - values_equal +list_index_of builtin_list_index_of int-preserving - - make_int,values_equal +list_insert_at builtin_list_insert_at int-requiring - - eigs_int_arg_range,val_is_number +list_remove_at builtin_list_remove_at int-requiring - - eigs_int_arg_range,val_is_number +list_slice builtin_list_slice int-requiring - - eigs_int_arg_range,val_is_number +list_truncate builtin_list_truncate int-requiring - - eigs_int_arg_range,val_is_number +log builtin_tensor_log float-producing - - eigs_number_arg,make_float,make_int,num_guard,val_is_number +log_softmax builtin_tensor_log_softmax float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float,make_float,val_is_number +matmul builtin_tensor_matmul float-producing - - SLOT_F64_RAW,VAL_FLOAT,VAL_INT,eigs_slot_float,make_float,num_guard_named +matmul_at builtin_tensor_matmul_at float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float,make_float +matmul_bt builtin_tensor_matmul_bt float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float,make_float +max builtin_max int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +mean builtin_tensor_mean float-producing - - buffer_read_f64,eigs_float_arg,make_float,num_guard,val_is_number +min builtin_min int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +model_load_weights builtin_eigen_model_load other - - eigs_int_arg_range,strtod,val_is_number +monotonic_ms builtin_monotonic_ms float-producing - - make_float +monotonic_ns builtin_monotonic_ns int-preserving LOSS - make_int +multiply builtin_tensor_multiply int-preserving LOSS - eigs_float_arg,eigs_number_arg,make_float,make_int,num_guard,val_is_number +native_train_step_builtin builtin_native_train_step int-requiring - - VAL_FLOAT,VAL_INT,atof,eigs_list_float,eigs_slot_float,eigs_slot_int_range,strtod +nearest_in_range builtin_nearest_in_range float-producing - - eigs_float_arg,eigs_list_float,make_float,make_int,val_is_number +nearest_in_range_all builtin_nearest_in_range_all float-producing - - eigs_float_arg,eigs_list_float,make_float,make_int,val_is_number +negative builtin_tensor_negative int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +net_accept builtin_net_accept int-requiring - - eigs_int_arg,eigs_opt_magnitude,make_int,val_is_number +net_close builtin_net_close int-requiring - - eigs_int_arg,val_is_number +net_dial builtin_net_dial int-requiring - - eigs_list_int_range,eigs_opt_magnitude,make_int +net_listen builtin_net_listen int-requiring - - eigs_int_arg_range,make_int,val_is_number +net_port builtin_net_port int-requiring - - eigs_int_arg,make_int,val_is_number +net_recv builtin_net_recv int-requiring - - eigs_int_arg,eigs_opt_int_range,eigs_opt_magnitude +net_send builtin_net_send int-requiring - - buffer_byte_at,eigs_elem_byte,eigs_int_arg,make_int +norm builtin_tensor_norm float-producing - - buffer_read_f64,eigs_float_arg,make_float,num_guard,val_is_number +num builtin_num other LOSS - make_float,make_int,num_guard_named,strtod,val_is_number +num_copy builtin_num_copy int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +num_type builtin_num_type other - - VAL_FLOAT,VAL_INT +numerical_grad builtin_numerical_grad float-producing - - VAL_FLOAT,VAL_INT,eigs_float_arg,eigs_slot_float,eigs_slot_opt_float,list_get_f64,list_set_f64,make_float,val_is_number +numerical_grad_cols builtin_numerical_grad_cols float-producing - - VAL_FLOAT,VAL_INT,buffer_index_at,eigs_float_arg,eigs_slot_float,eigs_slot_int,eigs_slot_opt_float,list_get_f64,list_set_f64,make_float,val_is_number +numerical_grad_rows builtin_numerical_grad_rows float-producing - - VAL_FLOAT,VAL_INT,buffer_index_at,eigs_float_arg,eigs_slot_float,eigs_slot_int,eigs_slot_opt_float,list_get_f64,list_set_f64,make_float,val_is_number +observe builtin_observe other - - make_float +ord builtin_ord int-preserving - - make_float,make_int +pi builtin_pi float-producing - - make_float +pow builtin_tensor_pow float-producing - - eigs_float_arg,eigs_number_arg,make_float,make_int,num_guard,num_guard_named,val_is_number +ppu_render_frame builtin_ppu_render_frame int-requiring - - buffer_byte_at +print builtin_print other - - eigs_num_text +random builtin_random float-producing - - make_float +random_hex builtin_random_hex int-requiring - - eigs_int_arg_range,val_is_number +random_int builtin_random_int int-requiring LOSS - eigs_int_arg,make_float,make_int,val_is_number +random_normal builtin_random_normal float-producing - - eigs_list_float,eigs_list_int_range,make_float +range builtin_range int-preserving - - eigs_int_arg_range,make_int,val_is_number +read_bytes builtin_read_bytes int-preserving - - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg,make_int,val_is_number +read_bytes_buf builtin_read_bytes_buf int-requiring - - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg,make_int,val_is_number +read_text builtin_read_text other - - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg,make_int,val_is_number +record_history builtin_record_history other - - eigs_float_arg,make_int,val_is_number +recv builtin_recv int-requiring - - eigs_int_arg_range,val_is_number +recv_timeout builtin_recv_timeout int-requiring - magnitude eigs_int_arg_range,eigs_magnitude_arg,val_is_number +relu builtin_tensor_relu float-producing - - VAL_FLOAT,VAL_INT,eigs_float_arg,eigs_slot_float,make_float,val_is_number +reshape builtin_reshape int-requiring - - VAL_FLOAT,VAL_INT,eigs_list_int_range +round builtin_round int-preserving LOSS - eigs_number_arg,make_float,make_int,num_to_i64_exact,val_is_number +sandbox_run builtin_sandbox_run int-requiring - - VAL_FLOAT,VAL_INT,eigs_byte_arg,eigs_int_arg_range,eigs_list_int,make_int,val_is_number +scan_int_tokens builtin_scan_int_tokens int-preserving LOSS - make_int +scan_ints builtin_scan_ints int-preserving LOSS - make_int +scatter_add builtin_tensor_scatter_add float-producing - - VAL_FLOAT,VAL_INT,buffer_index_at,eigs_float_arg,eigs_slot_float,eigs_slot_int,num_guard,val_is_number +seed_random builtin_seed_random int-requiring LOSS - eigs_int_arg,val_is_number +send builtin_send int-requiring - - eigs_int_arg_range,val_is_number +set_at builtin_set_at int-requiring - - eigs_float_arg,eigs_int_arg_range,val_is_number +set_exit_code builtin_set_exit_code int-requiring - - eigs_int_arg_range,val_is_number +sgd_update builtin_sgd_update float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float,eigs_slot_opt_float,list_set_f64 +sgd_update_cols builtin_sgd_update_cols float-producing - - VAL_FLOAT,VAL_INT,buffer_index_at,eigs_slot_float,eigs_slot_int,eigs_slot_opt_float,list_set_f64 +sgd_update_rows builtin_sgd_update_rows float-producing - - VAL_FLOAT,VAL_INT,buffer_index_at,eigs_slot_float,eigs_slot_int,eigs_slot_opt_float,list_set_f64 +shape builtin_tensor_shape int-preserving - - make_int,val_is_number +shared_delete builtin_shared_delete int-preserving - - make_int +shared_incr builtin_shared_incr int-preserving LOSS - eigs_number_arg,make_float,make_int,val_is_number +shared_size builtin_shared_size int-preserving - - make_int +sign_extend builtin_sign_extend int-requiring LOSS - eigs_list_int,make_float,make_int,val_is_number +sin builtin_sin float-producing - - eigs_float_arg,make_float,val_is_number +softmax builtin_tensor_softmax float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float,make_float,val_is_number +sort builtin_sort other LOSS - VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_float_arg,val_is_number +sort_by builtin_sort_by other LOSS - VAL_I64_RAW,VAL_INT,eigs_float_arg,val_is_number +spawn builtin_spawn int-preserving - - make_int +sqrt builtin_tensor_sqrt float-producing - - eigs_number_arg,make_float,make_int,val_is_number +state_at builtin_state_at int-requiring - - eigs_int_arg_range,val_is_number +store_close builtin_store_close int-requiring - - SLOT_F64_RAW,VAL_F64_RAW,VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg_range,eigs_num_text,slot_is_f64,slot_is_smi,slot_smi_val,val_is_number +store_collections builtin_store_collections int-requiring - - eigs_int_arg_range,val_is_number +store_count builtin_store_count int-requiring - - eigs_int_arg_range,make_int,val_is_number +store_delete builtin_store_delete int-requiring LOSS - eigs_int_arg,eigs_int_arg_range,val_is_number +store_drop builtin_store_drop int-requiring - - SLOT_F64_RAW,VAL_F64_RAW,VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg_range,eigs_num_text,slot_is_f64,slot_is_smi,slot_smi_val,val_is_number +store_get builtin_store_get int-requiring LOSS - eigs_float_arg,eigs_int_arg,eigs_int_arg_range,eigs_number_arg,make_float,make_int,num_to_i64_exact,strtod,val_is_number +store_open builtin_store_open int-preserving - - eigs_int_arg_range,make_float,make_int,strtod,val_is_number +store_put builtin_store_put other LOSS - SLOT_F64_RAW,VAL_F64_RAW,VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg_range,eigs_num_text,make_int,slot_is_f64,slot_is_smi,slot_smi_val,val_is_number +store_query builtin_store_query other LOSS - eigs_float_arg,eigs_int_arg_range,eigs_number_arg,make_float,make_int,num_to_i64_exact,strtod,val_is_number +store_update builtin_store_update other LOSS - SLOT_F64_RAW,VAL_F64_RAW,VAL_FLOAT,VAL_I64_RAW,VAL_INT,eigs_int_arg,eigs_int_arg_range,eigs_num_text,make_int,slot_is_f64,slot_is_smi,slot_smi_val,val_is_number +str builtin_str other - - eigs_num_text +str_from_bytes builtin_str_from_bytes int-requiring - - VAL_FLOAT,VAL_INT,buffer_byte_at,eigs_byte_arg,eigs_slot_byte,val_is_number +stream_open builtin_stream_open int-requiring - - eigs_int_arg,val_is_number +stream_write builtin_stream_write float-producing - - eigs_float_arg,val_is_number +substr builtin_substr int-requiring - - eigs_int_arg_range,val_is_number +subtract builtin_tensor_subtract int-preserving LOSS - eigs_float_arg,eigs_number_arg,make_float,make_int,num_guard,val_is_number +sum builtin_tensor_sum int-preserving LOSS - VAL_FLOAT,buffer_read_f64,eigs_number_arg,make_float,make_int,num_guard,val_is_number +tan builtin_tan float-producing - - eigs_float_arg,make_float,val_is_number +task_alive builtin_task_alive int-requiring - - eigs_int_arg,val_is_number +task_detach builtin_task_detach int-requiring - - eigs_int_arg,make_int,val_is_number +task_join builtin_task_join int-requiring - - eigs_int_arg,val_is_number +task_kill builtin_task_kill int-requiring - - eigs_int_arg,val_is_number +task_sched_seed builtin_task_sched_seed int-requiring LOSS - eigs_int_arg,val_is_number +task_self builtin_task_self int-preserving - - make_int +task_send builtin_task_send int-requiring - - eigs_int_arg,val_is_number +task_sleep builtin_task_sleep other - - eigs_float_arg,val_is_number +task_spawn builtin_task_spawn int-preserving - - make_int +tensor_load builtin_tensor_load float-producing - - VAL_INT,make_float,make_int,num_guard_named +tensor_save builtin_tensor_save float-producing - - VAL_FLOAT,VAL_INT,eigs_slot_float +text_builder_append builtin_text_builder_append other - - eigs_num_text +text_builder_append_line builtin_text_builder_append_line other - - eigs_num_text +thread_join builtin_thread_join int-requiring - - eigs_int_arg_range,val_is_number +throw builtin_throw other - - eigs_num_text +token_name builtin_token_name int-requiring - - eigs_int_arg_range,val_is_number +try_recv builtin_try_recv int-requiring - - eigs_int_arg_range,val_is_number +type builtin_type other - - VAL_FLOAT,VAL_INT +u16_checked_add builtin_u16_checked_add int-requiring - - eigs_int_arg_range,make_int +u16_checked_mul builtin_u16_checked_mul int-requiring - - eigs_int_arg_range,make_int +u16_checked_sub builtin_u16_checked_sub int-requiring - - eigs_int_arg_range,make_int +u16_saturating_add builtin_u16_saturating_add int-requiring - - eigs_int_arg_range,make_int +u16_saturating_mul builtin_u16_saturating_mul int-requiring - - eigs_int_arg_range,make_int +u16_saturating_sub builtin_u16_saturating_sub int-requiring - - eigs_int_arg_range,make_int +u16_wrapping_add builtin_u16_wrapping_add int-requiring - - eigs_int_arg_range,make_int +u16_wrapping_mul builtin_u16_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +u16_wrapping_sub builtin_u16_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +u32_checked_add builtin_u32_checked_add int-requiring - - eigs_int_arg_range,make_int +u32_checked_mul builtin_u32_checked_mul int-requiring - - eigs_int_arg_range,make_int +u32_checked_sub builtin_u32_checked_sub int-requiring - - eigs_int_arg_range,make_int +u32_saturating_add builtin_u32_saturating_add int-requiring - - eigs_int_arg_range,make_int +u32_saturating_mul builtin_u32_saturating_mul int-requiring - - eigs_int_arg_range,make_int +u32_saturating_sub builtin_u32_saturating_sub int-requiring - - eigs_int_arg_range,make_int +u32_wrapping_add builtin_u32_wrapping_add int-requiring - - eigs_int_arg_range,make_int +u32_wrapping_mul builtin_u32_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +u32_wrapping_sub builtin_u32_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +u64_checked_add builtin_u64_checked_add int-requiring - - eigs_int_arg_range,make_int +u64_checked_mul builtin_u64_checked_mul int-requiring - - eigs_int_arg_range,make_int +u64_checked_sub builtin_u64_checked_sub int-requiring - - eigs_int_arg_range,make_int +u64_compare builtin_u64_compare int-requiring - - eigs_int_arg_range,make_int +u64_div builtin_u64_div int-requiring - - eigs_int_arg_range,make_int +u64_rem builtin_u64_rem int-requiring - - eigs_int_arg_range,make_int +u64_saturating_add builtin_u64_saturating_add int-requiring - - eigs_int_arg_range,make_int +u64_saturating_mul builtin_u64_saturating_mul int-requiring - - eigs_int_arg_range,make_int +u64_saturating_sub builtin_u64_saturating_sub int-requiring - - eigs_int_arg_range,make_int +u64_to_string builtin_u64_to_string int-requiring - - eigs_int_arg +u64_wrapping_add builtin_u64_wrapping_add int-requiring - - eigs_int_arg_range,make_int +u64_wrapping_mul builtin_u64_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +u64_wrapping_sub builtin_u64_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +u8_checked_add builtin_u8_checked_add int-requiring - - eigs_int_arg_range,make_int +u8_checked_mul builtin_u8_checked_mul int-requiring - - eigs_int_arg_range,make_int +u8_checked_sub builtin_u8_checked_sub int-requiring - - eigs_int_arg_range,make_int +u8_saturating_add builtin_u8_saturating_add int-requiring - - eigs_int_arg_range,make_int +u8_saturating_mul builtin_u8_saturating_mul int-requiring - - eigs_int_arg_range,make_int +u8_saturating_sub builtin_u8_saturating_sub int-requiring - - eigs_int_arg_range,make_int +u8_wrapping_add builtin_u8_wrapping_add int-requiring - - eigs_int_arg_range,make_int +u8_wrapping_mul builtin_u8_wrapping_mul int-requiring - - eigs_int_arg_range,make_int +u8_wrapping_sub builtin_u8_wrapping_sub int-requiring - - eigs_int_arg_range,make_int +usleep builtin_usleep other - magnitude eigs_magnitude_arg,val_is_number +vm_run_bytecode builtin_vm_run_bytecode int-requiring - - VAL_FLOAT,VAL_INT,eigs_byte_arg,eigs_int_arg_range,val_is_number +write builtin_write other - - eigs_num_text +write_bytes builtin_write_bytes int-requiring - - VAL_FLOAT,VAL_INT,buffer_byte_at,eigs_float_arg,eigs_slot_elem_byte,make_int +zeros builtin_tensor_zeros int-requiring - - VAL_FLOAT,VAL_INT,eigs_int_arg,eigs_list_int,make_float,val_is_number +zeros_like builtin_tensor_zeros_like float-producing - - make_float,val_is_number + +## Sites (file:line scope token kind category) +builtins.c:46 builtin_c_int eigs_int_arg_range READ b +builtins.c:172 builtin_usleep val_is_number TEST a +builtins.c:174 builtin_usleep eigs_magnitude_arg READ b +builtins.c:346 bit_int eigs_int_arg READ b +builtins.c:356 bit_pair val_is_number TEST a +builtins.c:356 bit_pair val_is_number TEST a +builtins.c:367 builtin_bit_and make_int PRODUCE c +builtins.c:375 builtin_bit_or make_int PRODUCE c +builtins.c:383 builtin_bit_xor make_int PRODUCE c +builtins.c:387 builtin_bit_not val_is_number TEST a +builtins.c:390 builtin_bit_not make_int PRODUCE c +builtins.c:398 builtin_bit_shift_left make_int PRODUCE c +builtins.c:406 builtin_bit_shift_right make_int PRODUCE c +builtins.c:420 builtin_monotonic_ns make_int PRODUCE c +builtins.c:428 builtin_monotonic_ms make_float PRODUCE c +builtins.c:439 builtin_clock_unix make_float PRODUCE c +builtins.c:444 builtin_len make_int PRODUCE c +builtins.c:446 builtin_len make_int PRODUCE c +builtins.c:449 builtin_len make_int PRODUCE c +builtins.c:452 builtin_len make_int PRODUCE c +builtins.c:454 builtin_len make_int PRODUCE c +builtins.c:464 builtin_len make_int PRODUCE c +builtins.c:502 builtin_number_value make_float PRODUCE c +builtins.c:502 builtin_number_value make_int PRODUCE c +builtins.c:513 builtin_num make_float PRODUCE c +builtins.c:514 builtin_num val_is_number TEST a +builtins.c:519 builtin_num make_float PRODUCE c +builtins.c:538 builtin_num make_int PRODUCE c +builtins.c:556 builtin_num make_int PRODUCE c +builtins.c:562 builtin_num strtod PARSE c +builtins.c:564 builtin_num num_guard_named GUARD c +builtins.c:565 builtin_num make_float PRODUCE c +builtins.c:587 builtin_append VAL_FLOAT YIELD a +builtins.c:611 builtin_get_observer_thresholds make_float PRODUCE i +builtins.c:612 builtin_get_observer_thresholds make_float PRODUCE i +builtins.c:613 builtin_get_observer_thresholds make_float PRODUCE i +builtins.c:640 builtin_exit val_is_number TEST a +builtins.c:644 builtin_exit val_is_number TEST a +builtins.c:659 builtin_set_exit_code val_is_number TEST a +builtins.c:664 builtin_set_exit_code eigs_int_arg_range READ b +builtins.c:799 builtin_observe make_float PRODUCE i +builtins.c:800 builtin_observe make_float PRODUCE i +builtins.c:801 builtin_observe make_float PRODUCE i +builtins.c:883 builtin_type VAL_FLOAT CASE a +builtins.c:884 builtin_type VAL_INT CASE a +builtins.c:902 builtin_num_type VAL_INT TEST a +builtins.c:903 builtin_num_type VAL_FLOAT TEST a +builtins.c:939 eigs_json_encode_value VAL_FLOAT CASE h +builtins.c:946 eigs_json_encode_value eigs_float_arg READ h +builtins.c:946 eigs_json_encode_value eigs_num_text FORMAT h +builtins.c:950 eigs_json_encode_value VAL_INT CASE h +builtins.c:952 eigs_json_encode_value VAL_I64_RAW RAW h +builtins.c:1317 eigs_json_parse_number make_float PRODUCE h +builtins.c:1324 eigs_json_parse_number make_float PRODUCE h +builtins.c:1333 eigs_json_parse_number make_float PRODUCE h +builtins.c:1340 eigs_json_parse_number make_int PRODUCE h +builtins.c:1343 eigs_json_parse_number strtod PARSE h +builtins.c:1353 eigs_json_parse_number strtod PARSE h +builtins.c:1357 eigs_json_parse_number make_float PRODUCE h +builtins.c:1528 builtin_json_build val_is_number TEST h +builtins.c:1530 builtin_json_build VAL_I64_RAW RAW h +builtins.c:1530 builtin_json_build VAL_INT TEST h +builtins.c:1531 builtin_json_build eigs_float_arg READ h +builtins.c:1531 builtin_json_build eigs_num_text FORMAT h +builtins.c:1701 builtin_scan_ints make_int PRODUCE c +builtins.c:1796 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1797 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1798 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1799 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1800 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1801 builtin_scan_int_tokens make_int PRODUCE c +builtins.c:1904 builtin_char_at val_is_number TEST a +builtins.c:1941 builtin_substr val_is_number TEST a +builtins.c:1942 builtin_substr val_is_number TEST a +builtins.c:1976 builtin_index_of make_int PRODUCE c +builtins.c:1983 builtin_index_of make_int PRODUCE c +builtins.c:1988 builtin_index_of make_int PRODUCE c +builtins.c:1989 builtin_index_of make_int PRODUCE c +builtins.c:1997 builtin_sin make_float PRODUCE c +builtins.c:1997 builtin_sin val_is_number TEST a +builtins.c:1998 builtin_sin eigs_float_arg READ b +builtins.c:1998 builtin_sin make_float PRODUCE c +builtins.c:2002 builtin_cos make_float PRODUCE c +builtins.c:2002 builtin_cos val_is_number TEST a +builtins.c:2003 builtin_cos eigs_float_arg READ b +builtins.c:2003 builtin_cos make_float PRODUCE c +builtins.c:2007 builtin_tan make_float PRODUCE c +builtins.c:2007 builtin_tan val_is_number TEST a +builtins.c:2008 builtin_tan eigs_float_arg READ b +builtins.c:2008 builtin_tan make_float PRODUCE c +builtins.c:2012 builtin_asin make_float PRODUCE c +builtins.c:2012 builtin_asin val_is_number TEST a +builtins.c:2013 builtin_asin eigs_float_arg READ b +builtins.c:2018 builtin_asin make_float PRODUCE c +builtins.c:2022 builtin_asin make_float PRODUCE c +builtins.c:2026 builtin_acos make_float PRODUCE c +builtins.c:2026 builtin_acos val_is_number TEST a +builtins.c:2027 builtin_acos eigs_float_arg READ b +builtins.c:2032 builtin_acos make_float PRODUCE c +builtins.c:2036 builtin_acos make_float PRODUCE c +builtins.c:2040 builtin_atan make_float PRODUCE c +builtins.c:2040 builtin_atan val_is_number TEST a +builtins.c:2041 builtin_atan eigs_float_arg READ b +builtins.c:2041 builtin_atan make_float PRODUCE c +builtins.c:2046 builtin_atan2 make_float PRODUCE c +builtins.c:2049 builtin_atan2 make_float PRODUCE c +builtins.c:2049 builtin_atan2 val_is_number TEST a +builtins.c:2049 builtin_atan2 val_is_number TEST a +builtins.c:2050 builtin_atan2 eigs_float_arg READ b +builtins.c:2050 builtin_atan2 eigs_float_arg READ b +builtins.c:2050 builtin_atan2 make_float PRODUCE c +builtins.c:2054 builtin_floor make_float PRODUCE c +builtins.c:2054 builtin_floor val_is_number TEST a +builtins.c:2055 builtin_floor eigs_number_arg READ b +builtins.c:2057 builtin_floor make_int PRODUCE c +builtins.c:2060 builtin_floor make_float PRODUCE c +builtins.c:2060 builtin_floor make_int PRODUCE c +builtins.c:2060 builtin_floor num_to_i64_exact READ b +builtins.c:2064 builtin_ceil make_float PRODUCE c +builtins.c:2064 builtin_ceil val_is_number TEST a +builtins.c:2065 builtin_ceil eigs_number_arg READ b +builtins.c:2067 builtin_ceil make_int PRODUCE c +builtins.c:2070 builtin_ceil make_float PRODUCE c +builtins.c:2070 builtin_ceil make_int PRODUCE c +builtins.c:2070 builtin_ceil num_to_i64_exact READ b +builtins.c:2074 builtin_round make_float PRODUCE c +builtins.c:2074 builtin_round val_is_number TEST a +builtins.c:2075 builtin_round eigs_number_arg READ b +builtins.c:2077 builtin_round make_int PRODUCE c +builtins.c:2080 builtin_round make_float PRODUCE c +builtins.c:2080 builtin_round make_int PRODUCE c +builtins.c:2080 builtin_round num_to_i64_exact READ b +builtins.c:2084 builtin_abs make_float PRODUCE c +builtins.c:2084 builtin_abs val_is_number TEST a +builtins.c:2085 builtin_abs eigs_number_arg READ b +builtins.c:2092 builtin_abs make_int PRODUCE c +builtins.c:2094 builtin_abs make_float PRODUCE c +builtins.c:2104 minmax_reduce val_is_number TEST a +builtins.c:2105 minmax_reduce eigs_number_arg READ b +builtins.c:2110 minmax_reduce make_float PRODUCE c +builtins.c:2112 minmax_reduce make_float PRODUCE c +builtins.c:2125 minmax_reduce make_float PRODUCE c +builtins.c:2125 minmax_reduce val_is_number TEST a +builtins.c:2127 minmax_reduce make_float PRODUCE c +builtins.c:2127 minmax_reduce val_is_number TEST a +builtins.c:2129 minmax_reduce eigs_number_arg READ b +builtins.c:2150 builtin_pi make_float PRODUCE c +builtins.c:2206 builtin_random make_float PRODUCE c +builtins.c:2215 builtin_random_int make_float PRODUCE c +builtins.c:2218 builtin_random_int val_is_number TEST a +builtins.c:2218 builtin_random_int val_is_number TEST a +builtins.c:2219 builtin_random_int make_float PRODUCE c +builtins.c:2220 builtin_random_int eigs_int_arg READ b +builtins.c:2221 builtin_random_int eigs_int_arg READ b +builtins.c:2223 builtin_random_int make_int PRODUCE c +builtins.c:2232 builtin_random_int make_int PRODUCE c +builtins.c:2246 builtin_seed_random val_is_number TEST a +builtins.c:2247 builtin_seed_random eigs_int_arg READ b +builtins.c:2475 builtin_json_path val_is_number TEST h +builtins.c:2477 builtin_json_path VAL_I64_RAW RAW h +builtins.c:2477 builtin_json_path VAL_INT TEST h +builtins.c:2478 builtin_json_path eigs_float_arg READ h +builtins.c:2478 builtin_json_path eigs_num_text FORMAT h +builtins.c:2587 builtin_token_name val_is_number TEST a +builtins.c:2625 builtin_state_at val_is_number TEST i +builtins.c:2674 builtin_chr val_is_number TEST a +builtins.c:2678 builtin_chr eigs_int_arg READ b +builtins.c:2702 builtin_hex val_is_number TEST e +builtins.c:2703 builtin_hex eigs_int_arg READ e +builtins.c:2705 builtin_hex eigs_list_int READ e +builtins.c:2706 builtin_hex eigs_list_int READ e +builtins.c:2733 builtin_ord make_float PRODUCE c +builtins.c:2736 builtin_ord make_int PRODUCE c +builtins.c:2737 builtin_ord make_int PRODUCE c +builtins.c:2854 vm_desc_abi_error val_is_number TEST a +builtins.c:2965 desc_isolate_const VAL_FLOAT CASE a +builtins.c:2966 desc_isolate_const VAL_INT CASE a +builtins.c:3083 vm_build_chunk_desc_body val_is_number TEST a +builtins.c:3088 vm_build_chunk_desc_body eigs_byte_arg READ b +builtins.c:3146 vm_build_chunk_desc_body val_is_number TEST a +builtins.c:3546 builtin_sandbox_run val_is_number TEST a +builtins.c:3554 builtin_sandbox_run val_is_number TEST a +builtins.c:3555 builtin_sandbox_run eigs_list_int READ b +builtins.c:3571 builtin_sandbox_run val_is_number TEST a +builtins.c:3572 builtin_sandbox_run eigs_list_int READ b +builtins.c:3631 builtin_sandbox_run make_int PRODUCE c +builtins.c:3894 builtin_sandbox_run make_int PRODUCE c +builtins.c:3907 builtin_sandbox_run make_int PRODUCE c +builtins.c:3922 builtin_sandbox_run make_int PRODUCE c +builtins.c:3957 builtin_sandbox_run make_int PRODUCE c +builtins.c:3986 builtin_record_history val_is_number TEST i +builtins.c:3992 builtin_record_history eigs_float_arg READ i +builtins.c:3999 builtin_record_history make_int PRODUCE i +builtins.c:4027 builtin_copy_into val_is_number TEST a +builtins.c:4045 builtin_copy_into val_is_number TEST a +builtins.c:4050 builtin_copy_into eigs_float_arg READ b +builtins.c:4085 builtin_list_slice val_is_number TEST a +builtins.c:4085 builtin_list_slice val_is_number TEST a +builtins.c:4112 builtin_num_copy val_is_number TEST a +builtins.c:4113 builtin_num_copy eigs_number_arg READ b +builtins.c:4145 range_bound eigs_int_arg_range READ b +builtins.c:4156 builtin_range val_is_number TEST a +builtins.c:4165 builtin_range val_is_number TEST a +builtins.c:4227 builtin_range make_int PRODUCE c +builtins.c:4265 at_index val_is_number TEST a +builtins.c:4285 buf_at_index val_is_number TEST a +builtins.c:4335 builtin_set_at val_is_number TEST a +builtins.c:4340 builtin_set_at eigs_float_arg READ b +builtins.c:4403 builtin_get_at buffer_read_f64 READ b +builtins.c:4403 builtin_get_at make_float PRODUCE c +builtins.c:4411 builtin_get_at buffer_read_f64 READ b +builtins.c:4411 builtin_get_at make_float PRODUCE c +builtins.c:4796 builtin_spawn make_int PRODUCE c +builtins.c:4802 builtin_spawn make_int PRODUCE c +builtins.c:4803 builtin_spawn make_int PRODUCE c +builtins.c:4828 builtin_thread_join val_is_number TEST a +builtins.c:4834 builtin_thread_join eigs_int_arg_range READ b +builtins.c:4834 builtin_thread_join val_is_number TEST a +builtins.c:4939 get_channel_why val_is_number TEST a +builtins.c:4943 get_channel_why eigs_int_arg_range READ b +builtins.c:4943 get_channel_why val_is_number TEST a +builtins.c:5009 builtin_channel make_int PRODUCE c +builtins.c:5010 builtin_channel make_int PRODUCE c +builtins.c:5109 builtin_recv_timeout val_is_number TEST a +builtins.c:5114 builtin_recv_timeout eigs_magnitude_arg READ b +builtins.c:5332 builtin_task_spawn make_int PRODUCE c +builtins.c:5342 task_handle_resolve val_is_number TEST a +builtins.c:5343 task_handle_resolve eigs_int_arg READ b +builtins.c:5397 builtin_task_join val_is_number TEST a +builtins.c:5398 builtin_task_join eigs_int_arg READ b +builtins.c:5444 builtin_task_send val_is_number TEST a +builtins.c:5448 builtin_task_send eigs_int_arg READ b +builtins.c:5476 builtin_task_kill val_is_number TEST a +builtins.c:5479 builtin_task_kill eigs_int_arg READ b +builtins.c:5489 builtin_task_alive val_is_number TEST a +builtins.c:5511 builtin_task_sleep val_is_number TEST a +builtins.c:5515 builtin_task_sleep eigs_float_arg READ b +builtins.c:5550 builtin_task_self make_int PRODUCE c +builtins.c:5564 builtin_task_detach val_is_number TEST a +builtins.c:5569 builtin_task_detach make_int PRODUCE c +builtins.c:5572 builtin_task_detach make_int PRODUCE c +builtins.c:5583 builtin_task_sched_seed val_is_number TEST a +builtins.c:5587 builtin_task_sched_seed eigs_int_arg READ b +builtins.c:5738 builtin_nearest_in_range eigs_list_float READ b +builtins.c:5739 builtin_nearest_in_range eigs_list_float READ b +builtins.c:5740 builtin_nearest_in_range eigs_list_float READ b +builtins.c:5741 builtin_nearest_in_range eigs_list_float READ b +builtins.c:5742 builtin_nearest_in_range eigs_list_float READ b +builtins.c:5799 builtin_nearest_in_range eigs_float_arg READ b +builtins.c:5799 builtin_nearest_in_range val_is_number TEST a +builtins.c:5823 builtin_nearest_in_range eigs_float_arg READ b +builtins.c:5826 builtin_nearest_in_range val_is_number TEST a +builtins.c:5826 builtin_nearest_in_range val_is_number TEST a +builtins.c:5828 builtin_nearest_in_range eigs_float_arg READ b +builtins.c:5829 builtin_nearest_in_range eigs_float_arg READ b +builtins.c:5844 builtin_nearest_in_range make_int PRODUCE c +builtins.c:5845 builtin_nearest_in_range make_float PRODUCE c +builtins.c:5846 builtin_nearest_in_range make_float PRODUCE c +builtins.c:5847 builtin_nearest_in_range make_float PRODUCE c +builtins.c:5872 builtin_nearest_in_range_all eigs_list_float READ b +builtins.c:5873 builtin_nearest_in_range_all eigs_list_float READ b +builtins.c:5874 builtin_nearest_in_range_all eigs_list_float READ b +builtins.c:5929 builtin_nearest_in_range_all eigs_float_arg READ b +builtins.c:5929 builtin_nearest_in_range_all val_is_number TEST a +builtins.c:5949 builtin_nearest_in_range_all eigs_float_arg READ b +builtins.c:5952 builtin_nearest_in_range_all val_is_number TEST a +builtins.c:5952 builtin_nearest_in_range_all val_is_number TEST a +builtins.c:5956 builtin_nearest_in_range_all eigs_float_arg READ b +builtins.c:5957 builtin_nearest_in_range_all eigs_float_arg READ b +builtins.c:5999 builtin_nearest_in_range_all make_int PRODUCE c +builtins.c:6000 builtin_nearest_in_range_all make_float PRODUCE c +builtins.c:6001 builtin_nearest_in_range_all make_float PRODUCE c +builtins.c:6002 builtin_nearest_in_range_all make_float PRODUCE c +builtins.c:6025 builtin_sign_extend make_float PRODUCE c +builtins.c:6027 builtin_sign_extend make_float PRODUCE c +builtins.c:6027 builtin_sign_extend val_is_number TEST a +builtins.c:6027 builtin_sign_extend val_is_number TEST a +builtins.c:6028 builtin_sign_extend eigs_list_int READ b +builtins.c:6029 builtin_sign_extend eigs_list_int READ b +builtins.c:6031 builtin_sign_extend make_int PRODUCE c +builtins.c:6038 builtin_sign_extend make_int PRODUCE c +builtins.c:6040 builtin_sign_extend make_int PRODUCE c +builtins.c:6057 builtin_list_truncate val_is_number TEST a +builtins.c:6088 builtin_list_remove_at val_is_number TEST a +builtins.c:6111 builtin_list_insert_at val_is_number TEST a +builtins.c:6144 builtin_list_index_of make_int PRODUCE f +builtins.c:6148 builtin_list_index_of make_int PRODUCE f +builtins.c:6154 builtin_list_index_of make_int PRODUCE f +builtins.c:6158 builtin_list_index_of make_int PRODUCE f +builtins.c:6229 builtin_sort_by val_is_number TEST f +builtins.c:6238 builtin_sort_by VAL_INT TEST f +builtins.c:6239 builtin_sort_by VAL_I64_RAW RAW f +builtins.c:6240 builtin_sort_by eigs_float_arg READ f +builtins.c:6262 sort_cmp_num VAL_INT TEST a +builtins.c:6262 sort_cmp_num VAL_INT TEST a +builtins.c:6263 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6263 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6263 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6263 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6264 sort_cmp_num VAL_INT TEST a +builtins.c:6265 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6265 sort_cmp_num eigs_float_arg READ b +builtins.c:6268 sort_cmp_num VAL_INT TEST a +builtins.c:6269 sort_cmp_num VAL_I64_RAW RAW b +builtins.c:6269 sort_cmp_num eigs_float_arg READ b +builtins.c:6272 sort_cmp_num eigs_float_arg READ b +builtins.c:6272 sort_cmp_num eigs_float_arg READ b +builtins.c:6290 builtin_sort VAL_FLOAT TEST f +builtins.c:6290 builtin_sort VAL_INT TEST f +builtins.c:6291 builtin_sort val_is_number TEST f +builtins.c:6298 builtin_sort VAL_FLOAT TEST f +builtins.c:6298 builtin_sort VAL_INT TEST f +builtins.c:6319 builtin_dispatch val_is_number TEST a +builtins.c:6323 builtin_dispatch eigs_number_arg READ b +builtins.c:6327 builtin_dispatch num_to_i64_exact READ b +builtins.c:6358 builtin_dispatch val_is_number TEST a +builtins.c:6440 builtin_dot make_float PRODUCE c +builtins.c:6443 builtin_dot make_float PRODUCE c +builtins.c:6448 builtin_dot buffer_read_f64 READ b +builtins.c:6450 builtin_dot buffer_read_f64 READ b +builtins.c:6452 builtin_dot num_guard GUARD c +builtins.c:6452 builtin_dot num_guard GUARD c +builtins.c:6454 builtin_dot make_float PRODUCE c +builtins_buf.c:35 builtin_buffer VAL_FLOAT TEST a +builtins_buf.c:35 builtin_buffer VAL_INT TEST a +builtins_buf.c:36 builtin_buffer VAL_FLOAT TEST a +builtins_buf.c:36 builtin_buffer VAL_INT TEST a +builtins_buf.c:38 builtin_buffer eigs_list_int_range READ b +builtins_buf.c:39 builtin_buffer eigs_list_int_range READ b +builtins_buf.c:58 builtin_buffer val_is_number TEST a +builtins_buf.c:59 builtin_buffer val_is_number TEST a +builtins_buf.c:61 builtin_buffer eigs_int_arg_range READ b +builtins_buf.c:84 builtin_reshape VAL_FLOAT TEST a +builtins_buf.c:84 builtin_reshape VAL_INT TEST a +builtins_buf.c:85 builtin_reshape VAL_FLOAT TEST a +builtins_buf.c:85 builtin_reshape VAL_INT TEST a +builtins_buf.c:87 builtin_reshape eigs_list_int_range READ b +builtins_buf.c:88 builtin_reshape eigs_list_int_range READ b +builtins_buf.c:113 builtin_buf_get make_float PRODUCE c +builtins_buf.c:120 builtin_buf_get make_float PRODUCE c +builtins_buf.c:122 builtin_buf_get eigs_list_int READ b +builtins_buf.c:123 builtin_buf_get make_float PRODUCE c +builtins_buf.c:128 builtin_buf_get make_float PRODUCE c +builtins_buf.c:130 builtin_buf_get buffer_read_f64 READ b +builtins_buf.c:130 builtin_buf_get make_float PRODUCE c +builtins_buf.c:149 builtin_buf_set VAL_FLOAT TEST a +builtins_buf.c:149 builtin_buf_set VAL_INT TEST a +builtins_buf.c:153 builtin_buf_set VAL_FLOAT TEST a +builtins_buf.c:153 builtin_buf_set VAL_INT TEST a +builtins_buf.c:157 builtin_buf_set eigs_list_int READ b +builtins_buf.c:158 builtin_buf_set eigs_list_float READ b +builtins_buf.c:171 builtin_buf_len make_int PRODUCE c +builtins_buf.c:172 builtin_buf_len make_int PRODUCE c +builtins_buf.c:192 builtin_buf_from_list VAL_FLOAT TEST a +builtins_buf.c:192 builtin_buf_from_list VAL_INT TEST a +builtins_buf.c:193 builtin_buf_from_list eigs_slot_float READ b +builtins_buf.c:215 strict_numeric_byte_list val_is_number TEST a +builtins_buf.c:222 strict_numeric_byte_list val_is_number TEST a +builtins_buf.c:223 strict_numeric_byte_list val_is_number TEST a +builtins_buf.c:224 strict_numeric_byte_list eigs_byte_arg READ b +builtins_buf.c:253 builtin_str_from_bytes buffer_byte_at READ b +builtins_buf.c:254 builtin_str_from_bytes VAL_FLOAT TEST a +builtins_buf.c:254 builtin_str_from_bytes VAL_INT TEST a +builtins_buf.c:255 builtin_str_from_bytes eigs_slot_byte READ b +builtins_buf.c:275 builtin_f64_to_bytes val_is_number TEST a +builtins_buf.c:276 builtin_f64_to_bytes eigs_float_arg READ b +builtins_buf.c:276 builtin_f64_to_bytes val_is_number TEST a +builtins_buf.c:283 builtin_f64_to_bytes make_int PRODUCE c +builtins_buf.c:299 builtin_f64_from_bytes eigs_slot_elem_byte READ b +builtins_buf.c:309 builtin_f64_from_bytes buffer_byte_at READ b +builtins_buf.c:313 builtin_f64_from_bytes make_float PRODUCE c +builtins_buf.c:322 builtin_f64_from_bytes make_float PRODUCE c +builtins_buf.c:322 builtin_f64_from_bytes num_guard_named GUARD c +builtins_buf.c:374 zlib_bytes_arg buffer_byte_at READ b +builtins_buf.c:375 zlib_bytes_arg VAL_FLOAT TEST a +builtins_buf.c:375 zlib_bytes_arg VAL_INT TEST a +builtins_buf.c:376 zlib_bytes_arg eigs_slot_byte READ b +builtins_buf.c:400 zlib_bytes_result make_int PRODUCE c +builtins_buf.c:518 buf_count_arg VAL_FLOAT TEST a +builtins_buf.c:518 buf_count_arg VAL_INT TEST a +builtins_buf.c:522 buf_count_arg eigs_slot_int READ b +builtins_buf.c:533 buf_float_arg READ b +builtins_buf.c:535 buf_float_arg VAL_FLOAT TEST a +builtins_buf.c:535 buf_float_arg VAL_INT TEST a +builtins_buf.c:539 buf_float_arg eigs_slot_float READ b +builtins_buf.c:552 buf_window_arg VAL_FLOAT TEST a +builtins_buf.c:552 buf_window_arg VAL_INT TEST a +builtins_buf.c:556 buf_window_arg eigs_slot_int READ b +builtins_buf.c:617 builtin_buf_mix buf_float_arg READ b +builtins_buf.c:622 builtin_buf_mix num_guard GUARD c +builtins_buf.c:622 builtin_buf_mix num_guard GUARD c +builtins_buf.c:641 builtin_buf_scale_range buf_float_arg READ b +builtins_buf.c:645 builtin_buf_scale_range num_guard GUARD c +builtins_buf.c:664 builtin_buf_fill buf_float_arg READ b +builtins_buf.c:679 builtin_buf_peak make_float PRODUCE c +builtins_buf.c:687 builtin_buf_peak make_float PRODUCE c +builtins_buf.c:690 builtin_buf_peak buffer_read_f64 READ b +builtins_buf.c:695 builtin_buf_peak make_float PRODUCE c +builtins_buf.c:709 builtin_buf_dot make_float PRODUCE c +builtins_buf.c:720 builtin_buf_dot make_float PRODUCE c +builtins_buf.c:723 builtin_buf_dot buffer_read_f64 READ b +builtins_buf.c:725 builtin_buf_dot buffer_read_f64 READ b +builtins_buf.c:727 builtin_buf_dot num_guard GUARD c +builtins_buf.c:727 builtin_buf_dot num_guard GUARD c +builtins_buf.c:729 builtin_buf_dot make_float PRODUCE c +builtins_buf.c:782 builtin_buf_from_pcm16le buffer_byte_at READ b +builtins_buf.c:783 builtin_buf_from_pcm16le buffer_byte_at READ b +builtins_buf.c:786 builtin_buf_from_pcm16le num_guard GUARD c +builtins_buf.c:786 builtin_buf_from_pcm16le num_guard GUARD c +builtins_buf.c:788 builtin_buf_from_pcm16le num_guard GUARD c +builtins_buf.c:789 builtin_buf_from_pcm16le num_guard GUARD c +builtins_buf.c:822 builtin_buf_to_pcm16le buffer_read_f64 READ b +builtins_buf.c:826 builtin_buf_to_pcm16le num_guard GUARD c +builtins_buf.c:827 builtin_buf_to_pcm16le num_guard GUARD c +builtins_buf.c:828 builtin_buf_to_pcm16le num_guard GUARD c +builtins_buf.c:829 builtin_buf_to_pcm16le num_guard GUARD c +builtins_buf.c:829 builtin_buf_to_pcm16le num_guard GUARD c +builtins_buf.c:855 builtin_buf_deinterleave val_is_number TEST a +builtins_buf.c:855 builtin_buf_deinterleave val_is_number TEST a +builtins_buf.c:859 builtin_buf_deinterleave eigs_int_arg READ b +builtins_buf.c:860 builtin_buf_deinterleave eigs_int_arg READ b +builtins_buf.c:938 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:938 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:952 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:953 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:953 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:953 builtin_buf_resample_linear num_guard GUARD c +builtins_buf.c:954 builtin_buf_resample_linear num_guard GUARD c +builtins_host.c:41 builtin_host_c_int eigs_int_arg_range READ b +builtins_host.c:226 builtin_stream_open val_is_number TEST a +builtins_host.c:228 builtin_stream_open eigs_int_arg READ b +builtins_host.c:273 builtin_stream_write val_is_number TEST a +builtins_host.c:278 builtin_stream_write eigs_float_arg READ b +builtins_host.c:442 builtin_build_corpus VAL_FLOAT TEST a +builtins_host.c:442 builtin_build_corpus VAL_INT TEST a +builtins_host.c:445 builtin_build_corpus VAL_FLOAT TEST a +builtins_host.c:445 builtin_build_corpus VAL_INT TEST a +builtins_host.c:447 builtin_build_corpus VAL_FLOAT TEST a +builtins_host.c:447 builtin_build_corpus VAL_INT TEST a +builtins_host.c:466 builtin_build_corpus val_is_number TEST a +builtins_host.c:503 builtin_build_corpus eigs_opt_int_range READ b +builtins_host.c:532 builtin_build_corpus eigs_opt_int_range READ b +builtins_host.c:761 builtin_build_corpus number RAW b +builtins_host.c:761 builtin_build_corpus number RAW b +builtins_host.c:762 builtin_build_corpus number RAW b +builtins_host.c:762 builtin_build_corpus number RAW b +builtins_host.c:763 builtin_build_corpus number RAW b +builtins_host.c:764 builtin_build_corpus number RAW b +builtins_host.c:863 builtin_build_corpus make_int PRODUCE c +builtins_host.c:864 builtin_build_corpus make_int PRODUCE c +builtins_host.c:865 builtin_build_corpus make_int PRODUCE c +builtins_host.c:1226 reader_cap_parse val_is_number TEST a +builtins_host.c:1229 reader_cap_parse eigs_int_arg READ b +builtins_host.c:1251 reader_cap_raise make_int PRODUCE c +builtins_host.c:1303 replay_reader_size VAL_I64_RAW RAW b +builtins_host.c:1303 replay_reader_size VAL_INT TEST a +builtins_host.c:1309 replay_reader_size VAL_I64_RAW RAW b +builtins_host.c:1324 builtin_read_bytes VAL_FLOAT TEST a +builtins_host.c:1324 builtin_read_bytes VAL_INT TEST a +builtins_host.c:1362 builtin_read_bytes make_int PRODUCE c +builtins_host.c:1377 builtin_read_bytes make_int PRODUCE c +builtins_host.c:1415 builtin_read_bytes_buf VAL_FLOAT TEST a +builtins_host.c:1415 builtin_read_bytes_buf VAL_INT TEST a +builtins_host.c:1490 builtin_read_text VAL_FLOAT TEST a +builtins_host.c:1490 builtin_read_text VAL_INT TEST a +builtins_host.c:1620 exec_capture_result make_int PRODUCE c +builtins_host.c:1646 builtin_exec_capture VAL_FLOAT TEST a +builtins_host.c:1646 builtin_exec_capture VAL_INT TEST a +builtins_host.c:1648 builtin_exec_capture eigs_list_float READ b +builtins_host.c:1653 builtin_exec_capture make_float PRODUCE c +builtins_host.c:1654 builtin_exec_capture eigs_magnitude_arg READ b +builtins_host.c:1802 builtin_random_hex val_is_number TEST a +builtins_host.c:1830 builtin_write_bytes make_int PRODUCE c +builtins_host.c:1836 builtin_write_bytes make_int PRODUCE c +builtins_host.c:1844 builtin_write_bytes eigs_float_arg READ b +builtins_host.c:1845 builtin_write_bytes make_int PRODUCE c +builtins_host.c:1858 builtin_write_bytes make_int PRODUCE c +builtins_host.c:1868 builtin_write_bytes VAL_FLOAT TEST a +builtins_host.c:1868 builtin_write_bytes VAL_INT TEST a +builtins_host.c:1875 builtin_write_bytes eigs_slot_elem_byte READ b +builtins_host.c:1877 builtin_write_bytes buffer_byte_at READ b +builtins_host.c:1899 builtin_write_bytes make_int PRODUCE c +builtins_int.c:106 n9_pair eigs_int_arg_range READ b +builtins_int.c:107 n9_pair eigs_int_arg_range READ b +builtins_int.c:162 n9_width make_int PRODUCE c +builtins_int.c:163 n9_width make_int PRODUCE c +builtins_int.c:168 n9_width make_int PRODUCE c +builtins_int.c:169 n9_width make_int PRODUCE c +builtins_int.c:536 builtin_u64_compare make_int PRODUCE c +builtins_int.c:546 n9_unsigned_div make_int PRODUCE c +builtins_int.c:561 builtin_u64_to_string eigs_int_arg READ b +builtins_int.c:572 builtin_fround eigs_float_arg READ b +builtins_int.c:576 builtin_fround make_float PRODUCE c +builtins_tensor.c:229 tensor_dims VAL_FLOAT TEST a +builtins_tensor.c:229 tensor_dims VAL_INT TEST a +builtins_tensor.c:257 #define FLAT_BAD VAL_FLOAT TEST a +builtins_tensor.c:257 #define FLAT_BAD VAL_INT TEST a +builtins_tensor.c:292 tensor_to_flat VAL_FLOAT TEST a +builtins_tensor.c:292 tensor_to_flat VAL_INT TEST a +builtins_tensor.c:292 tensor_to_flat eigs_slot_float READ b +builtins_tensor.c:301 tensor_to_flat VAL_FLOAT TEST a +builtins_tensor.c:301 tensor_to_flat VAL_INT TEST a +builtins_tensor.c:301 tensor_to_flat eigs_slot_float READ b +builtins_tensor.c:315 flat_to_tensor_2d make_float PRODUCE c +builtins_tensor.c:331 flat_to_tensor_1d make_float PRODUCE c +builtins_tensor.c:363 tensor_total val_is_number TEST a +builtins_tensor.c:384 tensor_flatten_recursive eigs_float_arg READ b +builtins_tensor.c:384 tensor_flatten_recursive val_is_number TEST a +builtins_tensor.c:387 tensor_flatten_recursive buffer_read_f64 READ b +builtins_tensor.c:403 op_add num_guard GUARD c +builtins_tensor.c:404 op_sub num_guard GUARD c +builtins_tensor.c:405 op_mul num_guard GUARD c +builtins_tensor.c:412 op_div num_guard GUARD c +builtins_tensor.c:416 op_pow num_guard_named GUARD c +builtins_tensor.c:504 tensor_elementwise make_float PRODUCE c +builtins_tensor.c:507 tensor_elementwise val_is_number TEST a +builtins_tensor.c:507 tensor_elementwise val_is_number TEST a +builtins_tensor.c:510 tensor_elementwise eigs_number_arg READ b +builtins_tensor.c:512 tensor_elementwise eigs_number_arg READ b +builtins_tensor.c:524 tensor_elementwise make_int PRODUCE c +builtins_tensor.c:529 tensor_elementwise make_float PRODUCE c +builtins_tensor.c:538 tensor_elementwise val_is_number TEST a +builtins_tensor.c:539 tensor_elementwise eigs_float_arg READ b +builtins_tensor.c:543 tensor_elementwise val_is_number TEST a +builtins_tensor.c:544 tensor_elementwise eigs_float_arg READ b +builtins_tensor.c:564 tensor_elementwise val_is_number TEST a +builtins_tensor.c:575 tensor_elementwise val_is_number TEST a +builtins_tensor.c:673 tensor_elementwise make_float PRODUCE c +builtins_tensor.c:728 tensor_unary val_is_number TEST a +builtins_tensor.c:729 tensor_unary eigs_number_arg READ b +builtins_tensor.c:736 tensor_unary make_int PRODUCE c +builtins_tensor.c:739 tensor_unary make_float PRODUCE c +builtins_tensor.c:768 tensor_unary make_float PRODUCE c +builtins_tensor.c:781 op_exp num_guard GUARD c +builtins_tensor.c:795 op_log_safe num_guard GUARD c +builtins_tensor.c:797 op_log_safe num_guard GUARD c +builtins_tensor.c:860 builtin_tensor_matmul SLOT_F64_RAW RAW b +builtins_tensor.c:891 builtin_tensor_matmul num_guard_named GUARD c +builtins_tensor.c:1058 builtin_tensor_scatter_add val_is_number TEST a +builtins_tensor.c:1090 builtin_tensor_scatter_add VAL_FLOAT TEST a +builtins_tensor.c:1090 builtin_tensor_scatter_add VAL_INT TEST a +builtins_tensor.c:1094 builtin_tensor_scatter_add eigs_slot_int READ b +builtins_tensor.c:1099 builtin_tensor_scatter_add buffer_index_at READ b +builtins_tensor.c:1104 builtin_tensor_scatter_add VAL_FLOAT TEST a +builtins_tensor.c:1104 builtin_tensor_scatter_add VAL_INT TEST a +builtins_tensor.c:1108 builtin_tensor_scatter_add eigs_slot_float READ b +builtins_tensor.c:1112 builtin_tensor_scatter_add eigs_float_arg READ b +builtins_tensor.c:1137 builtin_tensor_scatter_add num_guard GUARD c +builtins_tensor.c:1140 builtin_tensor_scatter_add num_guard GUARD c +builtins_tensor.c:1151 builtin_tensor_softmax make_float PRODUCE c +builtins_tensor.c:1151 builtin_tensor_softmax val_is_number TEST a +builtins_tensor.c:1185 builtin_tensor_log_softmax VAL_FLOAT TEST a +builtins_tensor.c:1185 builtin_tensor_log_softmax VAL_INT TEST a +builtins_tensor.c:1191 builtin_tensor_log_softmax make_float PRODUCE c +builtins_tensor.c:1191 builtin_tensor_log_softmax val_is_number TEST a +builtins_tensor.c:1225 builtin_tensor_relu val_is_number TEST a +builtins_tensor.c:1226 builtin_tensor_relu eigs_float_arg READ b +builtins_tensor.c:1227 builtin_tensor_relu make_float PRODUCE c +builtins_tensor.c:1246 builtin_tensor_leaky_relu val_is_number TEST a +builtins_tensor.c:1247 builtin_tensor_leaky_relu eigs_float_arg READ b +builtins_tensor.c:1248 builtin_tensor_leaky_relu make_float PRODUCE c +builtins_tensor.c:1279 builtin_tensor_mean make_float PRODUCE c +builtins_tensor.c:1282 builtin_tensor_mean buffer_read_f64 READ b +builtins_tensor.c:1284 builtin_tensor_mean num_guard GUARD c +builtins_tensor.c:1286 builtin_tensor_mean make_float PRODUCE c +builtins_tensor.c:1296 builtin_tensor_mean val_is_number TEST a +builtins_tensor.c:1298 builtin_tensor_mean make_float PRODUCE c +builtins_tensor.c:1303 builtin_tensor_mean make_float PRODUCE c +builtins_tensor.c:1313 builtin_tensor_mean make_float PRODUCE c +builtins_tensor.c:1328 tensor_sum_has_float VAL_FLOAT TEST a +builtins_tensor.c:1340 tensor_sum_into val_is_number TEST a +builtins_tensor.c:1341 tensor_sum_into eigs_number_arg READ b +builtins_tensor.c:1352 tensor_sum_into num_guard GUARD c +builtins_tensor.c:1357 tensor_sum_into buffer_read_f64 READ b +builtins_tensor.c:1359 tensor_sum_into num_guard GUARD c +builtins_tensor.c:1372 builtin_tensor_sum val_is_number TEST a +builtins_tensor.c:1373 builtin_tensor_sum make_float PRODUCE c +builtins_tensor.c:1380 builtin_tensor_sum make_float PRODUCE c +builtins_tensor.c:1380 builtin_tensor_sum make_int PRODUCE c +builtins_tensor.c:1389 builtin_tensor_norm buffer_read_f64 READ b +builtins_tensor.c:1391 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1391 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1393 builtin_tensor_norm make_float PRODUCE c +builtins_tensor.c:1393 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1403 builtin_tensor_norm val_is_number TEST a +builtins_tensor.c:1405 builtin_tensor_norm make_float PRODUCE c +builtins_tensor.c:1409 builtin_tensor_norm make_float PRODUCE c +builtins_tensor.c:1417 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1417 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1419 builtin_tensor_norm make_float PRODUCE c +builtins_tensor.c:1419 builtin_tensor_norm num_guard GUARD c +builtins_tensor.c:1440 builtin_tensor_zeros val_is_number TEST a +builtins_tensor.c:1441 builtin_tensor_zeros eigs_int_arg READ b +builtins_tensor.c:1450 builtin_tensor_zeros VAL_FLOAT TEST a +builtins_tensor.c:1450 builtin_tensor_zeros VAL_INT TEST a +builtins_tensor.c:1451 builtin_tensor_zeros VAL_FLOAT TEST a +builtins_tensor.c:1451 builtin_tensor_zeros VAL_INT TEST a +builtins_tensor.c:1452 builtin_tensor_zeros eigs_list_int READ b +builtins_tensor.c:1453 builtin_tensor_zeros eigs_list_int READ b +builtins_tensor.c:1467 builtin_tensor_zeros make_float PRODUCE c +builtins_tensor.c:1482 builtin_tensor_zeros_like make_float PRODUCE c +builtins_tensor.c:1482 builtin_tensor_zeros_like val_is_number TEST a +builtins_tensor.c:1499 builtin_tensor_zeros_like make_float PRODUCE c +builtins_tensor.c:1515 flat_index_at eigs_slot_int READ b +builtins_tensor.c:1517 flat_index_at buffer_index_at READ b +builtins_tensor.c:1524 flat_index_is_num VAL_FLOAT TEST a +builtins_tensor.c:1524 flat_index_is_num VAL_INT TEST a +builtins_tensor.c:1619 builtin_tensor_gather val_is_number TEST a +builtins_tensor.c:1620 builtin_tensor_gather eigs_int_arg READ b +builtins_tensor.c:1627 builtin_tensor_gather buffer_read_f64 READ b +builtins_tensor.c:1627 builtin_tensor_gather make_float PRODUCE c +builtins_tensor.c:1670 builtin_tensor_gather eigs_float_arg READ b +builtins_tensor.c:1673 builtin_tensor_gather make_float PRODUCE c +builtins_tensor.c:1677 builtin_tensor_gather VAL_FLOAT TEST a +builtins_tensor.c:1677 builtin_tensor_gather VAL_INT TEST a +builtins_tensor.c:1683 builtin_tensor_gather eigs_slot_int READ b +builtins_tensor.c:1694 builtin_tensor_gather eigs_slot_elem_number READ b +builtins_tensor.c:1696 builtin_tensor_gather make_float PRODUCE c +builtins_tensor.c:1696 builtin_tensor_gather make_int PRODUCE c +builtins_tensor.c:1701 builtin_tensor_gather val_is_number TEST a +builtins_tensor.c:1702 builtin_tensor_gather eigs_int_arg READ b +builtins_tensor.c:1709 builtin_tensor_gather eigs_slot_elem_number READ b +builtins_tensor.c:1711 builtin_tensor_gather make_float PRODUCE c +builtins_tensor.c:1711 builtin_tensor_gather make_int PRODUCE c +builtins_tensor.c:1720 builtin_tensor_gather make_float PRODUCE c +builtins_tensor.c:1819 builtin_random_normal eigs_list_int_range READ b +builtins_tensor.c:1821 builtin_random_normal eigs_list_int_range READ b +builtins_tensor.c:1823 builtin_random_normal eigs_list_float READ b +builtins_tensor.c:1837 builtin_random_normal make_float PRODUCE c +builtins_tensor.c:1849 builtin_random_normal make_float PRODUCE c +builtins_tensor.c:1860 builtin_tensor_shape val_is_number TEST a +builtins_tensor.c:1868 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1869 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1873 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1879 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1887 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1888 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1893 builtin_tensor_shape make_int PRODUCE c +builtins_tensor.c:1902 numerical_loss val_is_number TEST a +builtins_tensor.c:1903 numerical_loss eigs_float_arg READ b +builtins_tensor.c:1925 gradient_perturb list_set_f64 PRODUCE c +builtins_tensor.c:1927 gradient_perturb list_get_f64 READ b +builtins_tensor.c:1951 gradient_container_unchanged list_get_f64 READ b +builtins_tensor.c:1952 gradient_container_unchanged list_set_f64 PRODUCE c +builtins_tensor.c:1979 tensor_cells_numeric VAL_FLOAT TEST a +builtins_tensor.c:1979 tensor_cells_numeric VAL_INT TEST a +builtins_tensor.c:1980 tensor_cells_numeric eigs_slot_float READ b +builtins_tensor.c:1983 tensor_cells_numeric VAL_FLOAT TEST a +builtins_tensor.c:1983 tensor_cells_numeric VAL_INT TEST a +builtins_tensor.c:1984 tensor_cells_numeric eigs_slot_float READ b +builtins_tensor.c:2001 builtin_numerical_grad eigs_slot_opt_float READ b +builtins_tensor.c:2037 builtin_numerical_grad eigs_slot_float READ b +builtins_tensor.c:2046 builtin_numerical_grad list_set_f64 PRODUCE c +builtins_tensor.c:2048 builtin_numerical_grad make_float PRODUCE c +builtins_tensor.c:2070 builtin_numerical_grad eigs_slot_float READ b +builtins_tensor.c:2082 builtin_numerical_grad list_set_f64 PRODUCE c +builtins_tensor.c:2084 builtin_numerical_grad make_float PRODUCE c +builtins_tensor.c:2111 builtin_sgd_update eigs_slot_opt_float READ b +builtins_tensor.c:2132 builtin_sgd_update eigs_slot_float READ b +builtins_tensor.c:2133 builtin_sgd_update eigs_slot_float READ b +builtins_tensor.c:2134 builtin_sgd_update list_set_f64 PRODUCE c +builtins_tensor.c:2147 builtin_sgd_update eigs_slot_float READ b +builtins_tensor.c:2148 builtin_sgd_update eigs_slot_float READ b +builtins_tensor.c:2149 builtin_sgd_update list_set_f64 PRODUCE c +builtins_tensor.c:2173 builtin_numerical_grad_rows eigs_slot_opt_float READ b +builtins_tensor.c:2226 builtin_numerical_grad_rows make_float PRODUCE c +builtins_tensor.c:2247 builtin_numerical_grad_rows eigs_slot_float READ b +builtins_tensor.c:2259 builtin_numerical_grad_rows list_set_f64 PRODUCE c +builtins_tensor.c:2262 builtin_numerical_grad_rows list_set_f64 PRODUCE c +builtins_tensor.c:2291 builtin_sgd_update_rows eigs_slot_opt_float READ b +builtins_tensor.c:2335 builtin_sgd_update_rows eigs_slot_float READ b +builtins_tensor.c:2336 builtin_sgd_update_rows eigs_slot_float READ b +builtins_tensor.c:2337 builtin_sgd_update_rows list_set_f64 PRODUCE c +builtins_tensor.c:2361 builtin_numerical_grad_cols eigs_slot_opt_float READ b +builtins_tensor.c:2417 builtin_numerical_grad_cols make_float PRODUCE c +builtins_tensor.c:2434 builtin_numerical_grad_cols eigs_slot_float READ b +builtins_tensor.c:2449 builtin_numerical_grad_cols list_set_f64 PRODUCE c +builtins_tensor.c:2453 builtin_numerical_grad_cols list_set_f64 PRODUCE c +builtins_tensor.c:2482 builtin_sgd_update_cols eigs_slot_opt_float READ b +builtins_tensor.c:2538 builtin_sgd_update_cols eigs_slot_float READ b +builtins_tensor.c:2539 builtin_sgd_update_cols eigs_slot_float READ b +builtins_tensor.c:2540 builtin_sgd_update_cols list_set_f64 PRODUCE c +builtins_tensor.c:2661 tensor_load_record_verdict make_int PRODUCE c +builtins_tensor.c:2662 tensor_load_record_verdict make_int PRODUCE c +builtins_tensor.c:2681 builtin_tensor_load VAL_INT TEST a +builtins_tensor.c:2682 builtin_tensor_load VAL_INT TEST a +builtins_tensor.c:2756 builtin_tensor_load num_guard_named GUARD c +chunk.c:196 const_hash_value VAL_FLOAT TEST f +chunk.c:197 const_hash_value VAL_F64_RAW RAW f +chunk.c:205 const_hash_value VAL_INT TEST f +chunk.c:206 const_hash_value VAL_I64_RAW RAW f +chunk.c:221 const_equal VAL_FLOAT TEST f +chunk.c:221 const_equal VAL_FLOAT TEST f +chunk.c:223 const_equal VAL_F64_RAW RAW f +chunk.c:223 const_equal VAL_F64_RAW RAW f +chunk.c:226 const_equal VAL_INT TEST f +chunk.c:226 const_equal VAL_INT TEST f +chunk.c:227 const_equal VAL_I64_RAW RAW f +chunk.c:227 const_equal VAL_I64_RAW RAW f +chunk.c:247 const_dedup_grow val_is_number TEST a +chunk.c:256 chunk_add_constant_ex val_is_number TEST a +chunk.c:422 chunk_disassemble VAL_FLOAT TEST e +chunk.c:423 chunk_disassemble number RAW e +chunk.c:424 chunk_disassemble VAL_F64_RAW RAW e +chunk.c:424 chunk_disassemble eigs_num_text FORMAT e +chunk.c:424 chunk_disassemble number RAW e +chunk.c:424 chunk_disassemble number RAW e +chunk.c:425 chunk_disassemble number RAW e +chunk.c:427 chunk_disassemble VAL_INT TEST e +chunk.c:428 chunk_disassemble VAL_I64_RAW RAW e +chunk.c:732 chunk_verify_impl VAL_INT TEST a +chunk.c:1038 chunk_scan_leaf_accessor/OP_CONST VAL_FLOAT TEST a +compiler.c:643 add_num_constant make_float PRODUCE c +compiler.c:650 add_int_constant make_int PRODUCE c +compiler.c:659 literal_index_u16 data.number RAW b +compiler.c:659 literal_index_u16 number RAW b +compiler.c:876 collect_referenced_names_skip AST_NUM CASE a +compiler.c:1020 collect_referenced_names AST_NUM CASE a +compiler.c:1159 scan_for_captures AST_NUM CASE a +compiler.c:1266 ast_has_closure AST_NUM CASE a +compiler.c:1344 subtree_overwrite_safe AST_NUM CASE a +compiler.c:1532 scan_for_interrogated AST_NUM CASE a +compiler.c:1635 walk_scope_ast AST_NUM CASE a +compiler.c:1881 scan_dispatch_rebind AST_NUM CASE a +compiler.c:2113 cond_is_observer_based AST_NUM CASE i +compiler.c:2201 compile_node_inner AST_NUM CASE a +compiler.c:2202 compile_node_inner data.number RAW b +compiler.c:2202 compile_node_inner number RAW b +compiler.c:2202 compile_node_inner number RAW b +compiler.c:2203 compile_node_inner number RAW b +compiler.c:2204 compile_node_inner number RAW b +compiler.c:2210 compile_node_inner number RAW b +compiler.c:3123 compile_node_inner AST_NUM TEST a +compiler.c:3178 compile_node_inner AST_NUM TEST a +compiler.c:3222 compile_node_inner AST_NUM TEST a +compiler.c:4063 obs_ast_scan_d AST_NUM CASE i +container.h:27 eigs_container_slot VAL_F64_RAW RAW b +container.h:27 eigs_container_slot VAL_FLOAT TEST a +container.h:27 eigs_container_slot num_guard GUARD c +container.h:27 eigs_container_slot slot_from_f64 PRODUCE c +container.h:30 eigs_container_slot VAL_INT TEST a +container.h:31 eigs_container_slot VAL_I64_RAW RAW b +container.h:32 eigs_container_slot slot_from_int PRODUCE c +container.h:50 eigs_slot_store_owned SLOT_F64_RAW RAW b +container.h:50 eigs_slot_store_owned num_guard GUARD c +container.h:50 eigs_slot_store_owned slot_from_f64 PRODUCE c +container.h:50 eigs_slot_store_owned slot_is_f64 TEST a +container.h:53 eigs_slot_store_owned VAL_FLOAT TEST a +container.h:53 eigs_slot_store_owned VAL_INT TEST a +container.h:59 eigs_slot_store_borrow SLOT_F64_RAW RAW b +container.h:59 eigs_slot_store_borrow num_guard GUARD c +container.h:59 eigs_slot_store_borrow slot_from_f64 PRODUCE c +container.h:59 eigs_slot_store_borrow slot_is_f64 TEST a +container.h:86 slot_type VAL_FLOAT YIELD a +container.h:86 slot_type slot_is_f64 TEST a +container.h:90 slot_type VAL_INT YIELD a +container.h:90 slot_type slot_is_smi TEST a +container.h:116 list_get_ref slot_is_f64 TEST a +container.h:116 list_get_ref slot_is_smi TEST a +container.h:120 dict_value_get_ref slot_is_f64 TEST a +container.h:120 dict_value_get_ref slot_is_smi TEST a +container.h:129 slot_view slot_is_f64 TEST a +container.h:130 slot_view slot_is_smi TEST a +container.h:131 slot_view VAL_INT TAGSET c +container.h:132 slot_view VAL_I64_RAW WRITE c +container.h:132 slot_view slot_smi_val RAW b +container.h:137 slot_view VAL_FLOAT TEST a +container.h:139 slot_view VAL_FLOAT TAGSET c +container.h:142 slot_view VAL_F64_RAW RAW b +container.h:142 slot_view slot_f64_value READ b +container.h:155 eigs_view_check VAL_FLOAT TEST a +container.h:155 eigs_view_check VAL_INT TEST a +container.h:174 eigs_slot_number eigs_slot_number READ b +container.h:175 eigs_slot_number eigs_number_arg READ b +container.h:178 eigs_slot_elem_number eigs_slot_elem_number READ b +container.h:179 eigs_slot_elem_number eigs_elem_number READ b +container.h:182 eigs_slot_opt_number eigs_slot_opt_number READ b +container.h:183 eigs_slot_opt_number eigs_opt_number READ b +container.h:186 eigs_slot_float eigs_slot_float READ b +container.h:187 eigs_slot_float eigs_float_arg READ b +container.h:190 eigs_slot_elem_float eigs_slot_elem_float READ b +container.h:191 eigs_slot_elem_float eigs_elem_float READ b +container.h:194 eigs_slot_opt_float eigs_slot_opt_float READ b +container.h:195 eigs_slot_opt_float eigs_opt_float READ b +container.h:198 eigs_slot_int eigs_slot_int READ b +container.h:199 eigs_slot_int eigs_int_arg READ b +container.h:202 eigs_slot_elem_int eigs_slot_elem_int READ b +container.h:203 eigs_slot_elem_int eigs_elem_int READ b +container.h:206 eigs_slot_opt_int eigs_slot_opt_int READ b +container.h:207 eigs_slot_opt_int eigs_opt_int READ b +container.h:210 eigs_slot_byte eigs_slot_byte READ b +container.h:211 eigs_slot_byte eigs_byte_arg READ b +container.h:214 eigs_slot_elem_byte eigs_slot_elem_byte READ b +container.h:215 eigs_slot_elem_byte eigs_elem_byte READ b +container.h:218 eigs_slot_opt_byte eigs_slot_opt_byte READ b +container.h:219 eigs_slot_opt_byte eigs_opt_byte READ b +container.h:222 eigs_slot_int_range eigs_slot_int_range READ b +container.h:223 eigs_slot_int_range eigs_int_arg_range READ b +container.h:226 eigs_slot_elem_int_range eigs_slot_elem_int_range READ b +container.h:227 eigs_slot_elem_int_range eigs_elem_int_range READ b +container.h:230 eigs_slot_opt_int_range eigs_slot_opt_int_range READ b +container.h:231 eigs_slot_opt_int_range eigs_opt_int_range READ b +container.h:234 eigs_slot_bounded eigs_slot_bounded READ b +container.h:235 eigs_slot_bounded eigs_bounded_arg READ b +container.h:238 eigs_slot_elem_bounded eigs_slot_elem_bounded READ b +container.h:239 eigs_slot_elem_bounded eigs_elem_bounded READ b +container.h:242 eigs_slot_opt_bounded eigs_slot_opt_bounded READ b +container.h:243 eigs_slot_opt_bounded eigs_opt_bounded READ b +container.h:246 eigs_slot_magnitude eigs_slot_magnitude READ b +container.h:247 eigs_slot_magnitude eigs_magnitude_arg READ b +container.h:250 eigs_slot_elem_magnitude eigs_slot_elem_magnitude READ b +container.h:251 eigs_slot_elem_magnitude eigs_elem_magnitude READ b +container.h:254 eigs_slot_opt_magnitude eigs_slot_opt_magnitude READ b +container.h:255 eigs_slot_opt_magnitude eigs_opt_magnitude READ b +container.h:268 list_get_return slot_is_smi TEST a +container.h:269 list_get_return SLOT_F64_RAW RAW b +container.h:269 list_get_return make_float PRODUCE c +container.h:269 list_get_return slot_is_f64 TEST a +container.h:277 dict_value_get_return slot_is_smi TEST a +container.h:278 dict_value_get_return SLOT_F64_RAW RAW b +container.h:278 dict_value_get_return make_float PRODUCE c +container.h:278 dict_value_get_return slot_is_f64 TEST a +container.h:308 eigs_slot_box SLOT_F64_RAW RAW b +container.h:308 eigs_slot_box make_float PRODUCE c +container.h:308 eigs_slot_box num_guard GUARD c +container.h:308 eigs_slot_box slot_is_f64 TEST a +container.h:309 eigs_slot_box slot_is_smi TEST a +container.h:334 list_rewrite_f64 list_rewrite_f64 PRODUCE c +container.h:336 list_rewrite_f64 slot_from_f64 PRODUCE c +container.h:340 list_set_f64 list_set_f64 PRODUCE c +container.h:341 list_set_f64 list_rewrite_f64 PRODUCE c +container.h:341 list_set_f64 num_guard GUARD c +container.h:344 list_get_f64 list_get_f64 READ b +container.h:345 list_get_f64 slot_f64_value READ b +container.h:348 dict_value_rewrite_f64 dict_value_rewrite_f64 PRODUCE c +container.h:350 dict_value_rewrite_f64 slot_from_f64 PRODUCE c +container.h:354 dict_value_set_f64 dict_value_set_f64 PRODUCE c +container.h:355 dict_value_set_f64 dict_value_rewrite_f64 PRODUCE c +container.h:355 dict_value_set_f64 num_guard GUARD c +container.h:358 dict_value_get_f64 dict_value_get_f64 READ b +container.h:359 dict_value_get_f64 slot_f64_value READ b +container.h:362 list_append_f64 list_append_f64 PRODUCE c +container.h:363 list_append_f64 num_guard GUARD c +container.h:365 list_append_f64 slot_from_f64 PRODUCE c +container.h:368 list_rewrite_int list_rewrite_int PRODUCE c +container.h:370 list_rewrite_int slot_from_int PRODUCE c +container.h:374 list_set_int list_set_int PRODUCE c +container.h:375 list_set_int list_rewrite_int PRODUCE c +container.h:385 list_get_int list_get_int READ b +container.h:386 list_get_int slot_int_value READ b +container.h:389 dict_value_rewrite_int dict_value_rewrite_int PRODUCE c +container.h:391 dict_value_rewrite_int slot_from_int PRODUCE c +container.h:395 dict_value_set_int dict_value_set_int PRODUCE c +container.h:396 dict_value_set_int dict_value_rewrite_int PRODUCE c +container.h:399 dict_value_get_int dict_value_get_int READ b +container.h:400 dict_value_get_int slot_int_value READ b +container.h:403 list_append_int list_append_int PRODUCE c +container.h:406 list_append_int slot_from_int PRODUCE c +eigenlsp.c:500 walk_ast_symbols AST_NUM CASE a +eigenlsp.c:640 collect_references AST_NUM CASE a +eigenlsp.c:1162 handle_hover number RAW b +eigenscript.c:330 val_type_name VAL_FLOAT CASE a +eigenscript.c:331 val_type_name VAL_INT CASE a +eigenscript.c:450 ent_child_slot SLOT_F64_RAW RAW i +eigenscript.c:450 ent_child_slot slot_is_f64 TEST i +eigenscript.c:452 ent_child_slot slot_is_smi TEST i +eigenscript.c:452 ent_child_slot slot_smi_val RAW i +eigenscript.c:460 compute_entropy_impl VAL_F64_RAW RAW i +eigenscript.c:460 compute_entropy_impl VAL_FLOAT CASE i +eigenscript.c:463 compute_entropy_impl VAL_I64_RAW RAW i +eigenscript.c:463 compute_entropy_impl VAL_INT CASE i +eigenscript.c:624 values_equal_for_observation VAL_FLOAT TEST a +eigenscript.c:624 values_equal_for_observation VAL_INT TEST a +eigenscript.c:625 values_equal_for_observation VAL_F64_RAW RAW b +eigenscript.c:625 values_equal_for_observation VAL_I64_RAW RAW b +eigenscript.c:626 values_equal_for_observation VAL_FLOAT TEST a +eigenscript.c:626 values_equal_for_observation VAL_INT TEST a +eigenscript.c:627 values_equal_for_observation VAL_F64_RAW RAW b +eigenscript.c:627 values_equal_for_observation VAL_I64_RAW RAW b +eigenscript.c:631 values_equal_for_observation VAL_F64_RAW RAW b +eigenscript.c:631 values_equal_for_observation VAL_F64_RAW RAW b +eigenscript.c:631 values_equal_for_observation VAL_FLOAT CASE a +eigenscript.c:632 values_equal_for_observation VAL_I64_RAW RAW b +eigenscript.c:632 values_equal_for_observation VAL_I64_RAW RAW b +eigenscript.c:632 values_equal_for_observation VAL_INT CASE a +eigenscript.c:1027 observer_slot_update_locked val_is_number TEST i +eigenscript.c:1029 observer_slot_update_locked VAL_I64_RAW RAW i +eigenscript.c:1029 observer_slot_update_locked VAL_INT TEST i +eigenscript.c:1030 observer_slot_update_locked VAL_F64_RAW RAW i +eigenscript.c:1090 observer_slot_update_value_num_shared VAL_I64_RAW RAW i +eigenscript.c:1090 observer_slot_update_value_num_shared VAL_INT TEST i +eigenscript.c:1091 observer_slot_update_value_num_shared VAL_F64_RAW RAW i +eigenscript.c:1164 observer_slot_update val_is_number TEST i +eigenscript.c:1233 observer_slot_sample_gated val_is_number TEST i +eigenscript.c:1234 observer_slot_sample_gated VAL_I64_RAW RAW i +eigenscript.c:1234 observer_slot_sample_gated VAL_INT TEST i +eigenscript.c:1235 observer_slot_sample_gated VAL_F64_RAW RAW i +eigenscript.c:1330 observer_slot_saturated EIGS_NUM_MAX GUARD i +eigenscript.c:1818 observer_entropy_now slot_is_f64 TEST i +eigenscript.c:1820 observer_entropy_now SLOT_F64_RAW RAW i +eigenscript.c:1821 observer_entropy_now slot_is_smi TEST i +eigenscript.c:1823 observer_entropy_now slot_smi_val RAW i +eigenscript.c:1868 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1869 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1872 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1876 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1877 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1878 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1879 observer_slot_trajectory make_float PRODUCE i +eigenscript.c:1885 observer_slot_trajectory make_int PRODUCE i +eigenscript.c:1918 observer_slot_from_trajectory eigs_float_arg READ i +eigenscript.c:1918 observer_slot_from_trajectory val_is_number TEST i +eigenscript.c:1919 observer_slot_from_trajectory val_is_number TEST i +eigenscript.c:1937 observer_slot_from_trajectory VAL_FLOAT TEST i +eigenscript.c:1937 observer_slot_from_trajectory VAL_FLOAT TEST i +eigenscript.c:1937 observer_slot_from_trajectory VAL_INT TEST i +eigenscript.c:1937 observer_slot_from_trajectory VAL_INT TEST i +eigenscript.c:1942 observer_slot_from_trajectory eigs_slot_float READ i +eigenscript.c:1943 observer_slot_from_trajectory eigs_slot_float READ i +eigenscript.c:1953 observer_slot_from_trajectory VAL_FLOAT TEST i +eigenscript.c:1953 observer_slot_from_trajectory VAL_INT TEST i +eigenscript.c:1958 observer_slot_from_trajectory eigs_slot_float READ i +eigenscript.c:1971 observer_slot_from_trajectory val_is_number TEST i +eigenscript.c:1976 observer_slot_from_trajectory eigs_float_arg READ i +eigenscript.c:2079 free_value VAL_FLOAT TEST a +eigenscript.c:2160 free_value VAL_FLOAT CASE a +eigenscript.c:2161 free_value VAL_INT CASE a +eigenscript.c:2172 make_float_pooled num_guard GUARD c +eigenscript.c:2188 make_float_pooled VAL_FLOAT TAGSET c +eigenscript.c:2189 make_float_pooled VAL_F64_RAW WRITE c +eigenscript.c:2195 make_float make_float PRODUCE c +eigenscript.c:2199 make_int make_int PRODUCE c +eigenscript.c:2201 make_int VAL_INT TAGSET c +eigenscript.c:2202 make_int VAL_I64_RAW WRITE c +eigenscript.c:2208 recycle_intermediate VAL_FLOAT TEST a +eigenscript.c:2230 make_float_permanent make_float_permanent PRODUCE c +eigenscript.c:2231 make_float_permanent num_guard GUARD c +eigenscript.c:2233 make_float_permanent VAL_FLOAT TAGSET c +eigenscript.c:2234 make_float_permanent VAL_F64_RAW WRITE c +eigenscript.c:2267 eigs_number_read VAL_INT TEST a +eigenscript.c:2268 eigs_number_read VAL_I64_RAW RAW b +eigenscript.c:2270 eigs_number_read VAL_FLOAT TEST a +eigenscript.c:2271 eigs_number_read VAL_F64_RAW RAW b +eigenscript.c:2279 eigs_number_arg eigs_number_arg READ b +eigenscript.c:2290 eigs_float_arg eigs_float_arg READ b +eigenscript.c:2291 eigs_float_arg eigs_number_arg READ b +eigenscript.c:2295 eigs_int_arg eigs_int_arg READ b +eigenscript.c:2300 eigs_int_arg num_to_i64_exact READ b +eigenscript.c:2306 eigs_int_arg_range READ b +eigenscript.c:2308 eigs_int_arg_range eigs_int_arg READ b +eigenscript.c:2319 eigs_byte_arg eigs_byte_arg READ b +eigenscript.c:2321 eigs_byte_arg eigs_int_arg_range READ b +eigenscript.c:2485 eigs_geom_shape READ b +eigenscript.c:2492 eigs_geom_shape eigs_list_float READ b +eigenscript.c:2532 eigs_bounded_arg eigs_bounded_arg READ b +eigenscript.c:2533 eigs_bounded_arg eigs_float_arg READ b +eigenscript.c:2549 eigs_magnitude_arg READ b +eigenscript.c:2551 eigs_magnitude_arg eigs_number_arg READ b +eigenscript.c:2555 eigs_magnitude_arg num_to_i64_exact READ b +eigenscript.c:2579 eigs_reader_elem_default val_is_number TEST a +eigenscript.c:2582 eigs_list_number eigs_list_number READ b +eigenscript.c:2585 eigs_list_number eigs_number_arg READ b +eigenscript.c:2587 eigs_elem_number eigs_elem_number READ b +eigenscript.c:2588 eigs_elem_number eigs_number_arg READ b +eigenscript.c:2590 eigs_opt_number eigs_opt_number READ b +eigenscript.c:2592 eigs_opt_number eigs_number_arg READ b +eigenscript.c:2597 eigs_list_float eigs_list_float READ b +eigenscript.c:2600 eigs_list_float eigs_float_arg READ b +eigenscript.c:2602 eigs_elem_float eigs_elem_float READ b +eigenscript.c:2603 eigs_elem_float eigs_float_arg READ b +eigenscript.c:2605 eigs_opt_float eigs_opt_float READ b +eigenscript.c:2607 eigs_opt_float eigs_float_arg READ b +eigenscript.c:2612 eigs_list_int eigs_list_int READ b +eigenscript.c:2615 eigs_list_int eigs_int_arg READ b +eigenscript.c:2617 eigs_elem_int eigs_elem_int READ b +eigenscript.c:2618 eigs_elem_int eigs_int_arg READ b +eigenscript.c:2620 eigs_opt_int eigs_opt_int READ b +eigenscript.c:2622 eigs_opt_int eigs_int_arg READ b +eigenscript.c:2627 eigs_list_byte eigs_list_byte READ b +eigenscript.c:2630 eigs_list_byte eigs_byte_arg READ b +eigenscript.c:2632 eigs_elem_byte eigs_elem_byte READ b +eigenscript.c:2633 eigs_elem_byte eigs_byte_arg READ b +eigenscript.c:2635 eigs_opt_byte eigs_opt_byte READ b +eigenscript.c:2637 eigs_opt_byte eigs_byte_arg READ b +eigenscript.c:2642 eigs_list_int_range eigs_list_int_range READ b +eigenscript.c:2645 eigs_list_int_range eigs_int_arg_range READ b +eigenscript.c:2647 eigs_elem_int_range eigs_elem_int_range READ b +eigenscript.c:2648 eigs_elem_int_range VAL_INT TAGSET c +eigenscript.c:2649 eigs_elem_int_range eigs_int_arg_range READ b +eigenscript.c:2651 eigs_opt_int_range eigs_opt_int_range READ b +eigenscript.c:2653 eigs_opt_int_range eigs_int_arg_range READ b +eigenscript.c:2656 eigs_list_bounded eigs_list_bounded READ b +eigenscript.c:2659 eigs_list_bounded eigs_bounded_arg READ b +eigenscript.c:2661 eigs_elem_bounded eigs_elem_bounded READ b +eigenscript.c:2662 eigs_elem_bounded VAL_INT TAGSET c +eigenscript.c:2663 eigs_elem_bounded eigs_bounded_arg READ b +eigenscript.c:2665 eigs_opt_bounded eigs_opt_bounded READ b +eigenscript.c:2667 eigs_opt_bounded eigs_bounded_arg READ b +eigenscript.c:2670 eigs_list_magnitude eigs_list_magnitude READ b +eigenscript.c:2673 eigs_list_magnitude eigs_magnitude_arg READ b +eigenscript.c:2675 eigs_elem_magnitude eigs_elem_magnitude READ b +eigenscript.c:2676 eigs_elem_magnitude VAL_INT TAGSET c +eigenscript.c:2677 eigs_elem_magnitude eigs_magnitude_arg READ b +eigenscript.c:2679 eigs_opt_magnitude eigs_opt_magnitude READ b +eigenscript.c:2681 eigs_opt_magnitude eigs_magnitude_arg READ b +eigenscript.c:2686 buffer_read_f64 buffer_read_f64 READ b +eigenscript.c:2692 buffer_read_f64 num_guard GUARD c +eigenscript.c:2698 buffer_read_f64 num_guard GUARD c +eigenscript.c:2700 buffer_index_at buffer_index_at READ b +eigenscript.c:2708 buffer_index_at buffer_read_f64 READ b +eigenscript.c:2710 buffer_index_at VAL_FLOAT TAGSET c +eigenscript.c:2711 buffer_index_at VAL_F64_RAW WRITE c +eigenscript.c:2712 buffer_index_at eigs_int_arg READ b +eigenscript.c:2716 buffer_byte_at buffer_byte_at READ b +eigenscript.c:2718 buffer_byte_at buffer_index_at READ b +eigenscript.c:2758 slot_from_value VAL_FLOAT TEST a +eigenscript.c:2762 slot_from_value VAL_F64_RAW RAW b +eigenscript.c:2764 slot_from_value slot_from_f64 PRODUCE c +eigenscript.c:2768 slot_from_value VAL_INT TEST a +eigenscript.c:2769 slot_from_value VAL_I64_RAW RAW b +eigenscript.c:2772 slot_from_value slot_from_int PRODUCE c +eigenscript.c:2792 slot_to_value SLOT_F64_RAW RAW b +eigenscript.c:2792 slot_to_value make_float PRODUCE c +eigenscript.c:2792 slot_to_value slot_is_f64 TEST a +eigenscript.c:2800 slot_to_value make_int PRODUCE c +eigenscript.c:2800 slot_to_value slot_is_smi TEST a +eigenscript.c:2800 slot_to_value slot_smi_val RAW b +eigenscript.c:3294 module_ns_project slot_is_f64 TEST a +eigenscript.c:3294 module_ns_project slot_is_f64 TEST a +eigenscript.c:3299 module_ns_project SLOT_F64_RAW RAW b +eigenscript.c:3299 module_ns_project dict_value_rewrite_f64 PRODUCE c +eigenscript.c:3559 chan_clone_rec VAL_FLOAT TEST a +eigenscript.c:3559 chan_clone_rec VAL_INT TEST a +eigenscript.c:3562 chan_clone_rec VAL_F64_RAW RAW b +eigenscript.c:3562 chan_clone_rec VAL_FLOAT CASE a +eigenscript.c:3562 chan_clone_rec make_float PRODUCE c +eigenscript.c:3563 chan_clone_rec VAL_I64_RAW RAW b +eigenscript.c:3563 chan_clone_rec VAL_INT CASE a +eigenscript.c:3563 chan_clone_rec make_int PRODUCE c +eigenscript.c:3766 is_truthy VAL_F64_RAW RAW b +eigenscript.c:3766 is_truthy VAL_FLOAT CASE a +eigenscript.c:3767 is_truthy VAL_I64_RAW RAW b +eigenscript.c:3767 is_truthy VAL_INT CASE a +eigenscript.c:3887 values_equal_impl VAL_FLOAT TEST f +eigenscript.c:3887 values_equal_impl VAL_INT TEST f +eigenscript.c:3888 values_equal_impl VAL_F64_RAW RAW f +eigenscript.c:3888 values_equal_impl VAL_I64_RAW RAW f +eigenscript.c:3889 values_equal_impl VAL_FLOAT TEST f +eigenscript.c:3889 values_equal_impl VAL_INT TEST f +eigenscript.c:3890 values_equal_impl VAL_F64_RAW RAW f +eigenscript.c:3890 values_equal_impl VAL_I64_RAW RAW f +eigenscript.c:3891 values_equal_impl val_is_number TEST f +eigenscript.c:3892 values_equal_impl val_is_number TEST f +eigenscript.c:3906 values_equal_impl VAL_F64_RAW RAW f +eigenscript.c:3906 values_equal_impl VAL_F64_RAW RAW f +eigenscript.c:3906 values_equal_impl VAL_FLOAT CASE f +eigenscript.c:3907 values_equal_impl VAL_I64_RAW RAW f +eigenscript.c:3907 values_equal_impl VAL_I64_RAW RAW f +eigenscript.c:3907 values_equal_impl VAL_INT CASE f +eigenscript.c:3977 eigs_num_text eigs_num_text FORMAT e +eigenscript.c:4039 eigs_num_text strtod PARSE e +eigenscript.c:4094 value_to_string VAL_FLOAT CASE e +eigenscript.c:4095 value_to_string VAL_F64_RAW RAW e +eigenscript.c:4095 value_to_string eigs_num_text FORMAT e +eigenscript.c:4098 value_to_string VAL_INT CASE e +eigenscript.c:4099 value_to_string VAL_I64_RAW RAW e +eigenscript.c:5473 gc_value_is_node VAL_FLOAT CASE a +eigenscript.c:5474 gc_value_is_node VAL_INT CASE a +eigenscript.h:196 number RAW b +eigenscript.h:226 AST_NUM DATA a +eigenscript.h:252 number RAW b +eigenscript.h:293 VAL_FLOAT DATA a +eigenscript.h:302 VAL_INT DATA a +eigenscript.h:310 #define val_is_number VAL_FLOAT TEST a +eigenscript.h:310 #define val_is_number VAL_INT TEST a +eigenscript.h:310 #define val_is_number val_is_number TEST a +eigenscript.h:331 #define VAL_F64_RAW VAL_F64_RAW RAW b +eigenscript.h:331 #define VAL_F64_RAW f64_ RAW b +eigenscript.h:332 #define VAL_I64_RAW VAL_I64_RAW RAW b +eigenscript.h:332 #define VAL_I64_RAW i64_ RAW b +eigenscript.h:333 #define VAL_F64_OFFSET VAL_F64_OFFSET RAW b +eigenscript.h:333 #define VAL_F64_OFFSET f64_ RAW b +eigenscript.h:1440 eigs_number_arg READ b +eigenscript.h:1441 eigs_list_number READ b +eigenscript.h:1442 eigs_elem_number READ b +eigenscript.h:1443 eigs_opt_number READ b +eigenscript.h:1444 eigs_float_arg READ b +eigenscript.h:1445 eigs_list_float READ b +eigenscript.h:1446 eigs_elem_float READ b +eigenscript.h:1447 eigs_opt_float READ b +eigenscript.h:1448 eigs_int_arg READ b +eigenscript.h:1449 eigs_list_int READ b +eigenscript.h:1450 eigs_elem_int READ b +eigenscript.h:1451 eigs_opt_int READ b +eigenscript.h:1452 eigs_byte_arg READ b +eigenscript.h:1453 eigs_list_byte READ b +eigenscript.h:1454 eigs_elem_byte READ b +eigenscript.h:1455 eigs_opt_byte READ b +eigenscript.h:1456 eigs_int_arg_range READ b +eigenscript.h:1457 eigs_list_int_range READ b +eigenscript.h:1458 eigs_elem_int_range READ b +eigenscript.h:1459 eigs_opt_int_range READ b +eigenscript.h:1469 eigs_geom_shape READ b +eigenscript.h:1471 eigs_bounded_arg READ b +eigenscript.h:1472 eigs_list_bounded READ b +eigenscript.h:1473 eigs_elem_bounded READ b +eigenscript.h:1474 eigs_opt_bounded READ b +eigenscript.h:1475 eigs_magnitude_arg READ b +eigenscript.h:1476 eigs_list_magnitude READ b +eigenscript.h:1477 eigs_elem_magnitude READ b +eigenscript.h:1478 eigs_opt_magnitude READ b +eigenscript.h:1853 make_float PRODUCE c +eigenscript.h:1854 make_float_permanent PRODUCE c +eigenscript.h:1855 make_int PRODUCE c +eigenscript.h:1918 #define EIGS_NUM_MAX EIGS_NUM_MAX GUARD c +eigenscript.h:1967 num_guard_value EIGS_NUM_MAX GUARD c +eigenscript.h:1967 num_guard_value EIGS_NUM_MAX GUARD c +eigenscript.h:1968 num_guard_value EIGS_NUM_MAX GUARD c +eigenscript.h:1968 num_guard_value EIGS_NUM_MAX GUARD c +eigenscript.h:1972 num_guard num_guard GUARD c +eigenscript.h:1980 num_guard EIGS_NUM_MAX GUARD c +eigenscript.h:1980 num_guard EIGS_NUM_MAX GUARD c +eigenscript.h:1981 num_guard EIGS_NUM_MAX GUARD c +eigenscript.h:1981 num_guard EIGS_NUM_MAX GUARD c +eigenscript.h:1990 num_to_i64_exact num_to_i64_exact READ b +eigenscript.h:2009 buffer_read_f64 READ b +eigenscript.h:2010 buffer_byte_at READ b +eigenscript.h:2011 buffer_index_at READ b +eigenscript.h:2023 num_guard_named num_guard_named GUARD c +eigenscript.h:2029 num_guard_named num_guard GUARD c +eigenscript.h:2340 eigs_num_text FORMAT e +eigs_embed.c:372 eigs_value_new_num make_float PRODUCE j +eigs_embed.c:373 eigs_value_new_int make_int PRODUCE j +eigs_embed.c:386 eigs_value_type VAL_FLOAT CASE j +eigs_embed.c:387 eigs_value_type VAL_INT CASE j +eigs_embed.c:431 eigs_value_as_num eigs_value_as_num READ j +eigs_embed.c:436 eigs_value_as_num VAL_I64_RAW RAW j +eigs_embed.c:436 eigs_value_as_num VAL_INT TEST j +eigs_embed.c:437 eigs_value_as_num VAL_F64_RAW RAW j +eigs_embed.c:437 eigs_value_as_num VAL_FLOAT TEST j +eigs_embed.c:444 eigs_value_as_int VAL_INT TEST j +eigs_embed.c:446 eigs_value_as_int VAL_I64_RAW RAW j +eigs_embed.c:448 eigs_value_as_int VAL_FLOAT TEST j +eigs_embed.c:454 eigs_value_as_int VAL_F64_RAW RAW j +eigs_embed.c:454 eigs_value_as_int num_to_i64_exact READ j +eigs_embed.c:464 eigs_value_is_int VAL_INT TEST j +eigs_embed.c:666 eigs_trace_declare_kind VAL_FLOAT DATA j +eigs_embed.c:666 eigs_trace_declare_kind VAL_INT DATA j +eigs_embed.h:173 eigs_value_as_num READ j +embed_concurrent.c:106 global_worker eigs_value_as_num READ j +embed_concurrent.c:768 take_drain eigs_value_as_num READ j +embed_concurrent.c:1268 test_sink_only_buffer_bounded eigs_value_as_num READ j +embed_concurrent.c:1763 fu_worker eigs_value_as_num READ j +embed_concurrent.c:1830 test_register_while_workers_read eigs_value_as_num READ j +embed_concurrent.c:1834 test_register_while_workers_read eigs_value_as_num READ j +embed_smoke.c:61 test_embed_int eigs_value_as_num READ j +embed_smoke.c:100 test_embed_int VAL_F64_RAW WRITE j +embed_smoke.c:494 test_worker_exit_lifecycle eigs_value_as_num READ j +embed_smoke.c:502 test_worker_exit_lifecycle eigs_slot_float READ j +embed_smoke.c:503 test_worker_exit_lifecycle eigs_slot_float READ j +embed_smoke.c:518 test_worker_exit_lifecycle eigs_value_as_num READ j +embed_smoke.c:604 host_add eigs_value_as_num READ j +embed_smoke.c:604 host_add eigs_value_as_num READ j +embed_smoke.c:640 unattached_buffer_probe eigs_value_as_num READ j +embed_smoke.c:673 main SLOT_F64_RAW WRITE j +embed_smoke.c:675 main SLOT_F64_RAW WRITE j +embed_smoke.c:677 main SLOT_F64_RAW RAW j +embed_smoke.c:680 main SLOT_F64_RAW RAW j +embed_smoke.c:682 main SLOT_F64_RAW RAW j +embed_smoke.c:685 main slot_is_smi TEST j +embed_smoke.c:685 main slot_smi_val RAW j +embed_smoke.c:718 main eigs_value_as_num READ j +embed_smoke.c:726 main eigs_value_as_num READ j +embed_smoke.c:736 main eigs_value_as_num READ j +embed_smoke.c:761 main eigs_value_as_num READ j +embed_smoke.c:769 main eigs_value_as_num READ j +embed_smoke.c:794 main eigs_value_as_num READ j +embed_smoke.c:820 main eigs_value_as_num READ j +embed_smoke.c:831 main eigs_value_as_num READ j +embed_smoke.c:836 main eigs_value_as_num READ j +embed_smoke.c:844 main eigs_value_as_num READ j +embed_smoke.c:913 main eigs_value_as_num READ j +embed_smoke.c:931 main eigs_value_as_num READ j +embed_smoke.c:940 main eigs_value_as_num READ j +embed_smoke.c:1003 main eigs_value_as_num READ j +embed_smoke.c:1289 main eigs_value_as_num READ j +embed_smoke.c:1332 main eigs_value_as_num READ j +embed_smoke.c:1338 main eigs_value_as_num READ j +embed_smoke.c:1342 main eigs_value_as_num READ j +embed_smoke.c:1376 main eigs_value_as_num READ j +embed_smoke.c:1454 main eigs_value_as_num READ j +embed_smoke.c:1465 main eigs_value_as_num READ j +embed_smoke.c:1495 main SLOT_F64_RAW WRITE j +embed_smoke.c:1526 main eigs_value_as_num READ j +embed_smoke.c:1536 main eigs_value_as_num READ j +embed_smoke.c:1548 main eigs_value_as_num READ j +embed_smoke.c:1548 main eigs_value_as_num READ j +embed_smoke.c:1560 main eigs_value_as_num READ j +embed_smoke.c:1569 main eigs_value_as_num READ j +embed_smoke.c:1587 main eigs_value_as_num READ j +embed_smoke.c:1647 main eigs_value_as_num READ j +embed_smoke.c:1659 main eigs_value_as_num READ j +embed_smoke.c:1663 main eigs_value_as_num READ j +embed_smoke.c:1668 main eigs_value_as_num READ j +embed_smoke.c:1710 main eigs_value_as_num READ j +embed_smoke.c:1715 main eigs_value_as_num READ j +embed_smoke.c:1724 main eigs_value_as_num READ j +embed_smoke.c:1743 main eigs_value_as_num READ j +embed_smoke.c:1747 main eigs_value_as_num READ j +embed_smoke.c:1756 main eigs_value_as_num READ j +embed_smoke.c:1760 main eigs_value_as_num READ j +embed_smoke.c:1767 main eigs_value_as_num READ j +embed_smoke.c:1823 main eigs_value_as_num READ j +embed_smoke.c:1847 main eigs_value_as_num READ j +embed_smoke.c:1874 main eigs_value_as_num READ j +ext_db.c:59 db_build_query VAL_INT TEST h +ext_db.c:60 db_build_query VAL_I64_RAW RAW h +ext_db.c:62 db_build_query VAL_FLOAT TEST h +ext_db.c:64 db_build_query eigs_float_arg READ h +ext_db.c:66 db_build_query eigs_num_text FORMAT h +ext_db.c:286 builtin_db_query_value make_int PRODUCE h +ext_db.c:298 builtin_db_query_value make_float PRODUCE h +ext_db.c:298 builtin_db_query_value strtod PARSE h +ext_gfx.c:512 gfx_nums VAL_FLOAT TEST a +ext_gfx.c:512 gfx_nums VAL_INT TEST a +ext_gfx.c:525 gfx_list_all_num VAL_FLOAT TEST a +ext_gfx.c:525 gfx_list_all_num VAL_INT TEST a +ext_gfx.c:560 builtin_gfx_open VAL_FLOAT TEST a +ext_gfx.c:560 builtin_gfx_open VAL_INT TEST a +ext_gfx.c:561 builtin_gfx_open VAL_FLOAT TEST a +ext_gfx.c:561 builtin_gfx_open VAL_INT TEST a +ext_gfx.c:564 builtin_gfx_open eigs_list_magnitude READ b +ext_gfx.c:565 builtin_gfx_open eigs_list_magnitude READ b +ext_gfx.c:638 gfx_colour eigs_list_bounded READ b +ext_gfx.c:640 gfx_colour eigs_list_bounded READ b +ext_gfx.c:656 gfx_edges_ld slot_from_f64 PRODUCE c +ext_gfx.c:656 gfx_edges_ld slot_from_f64 PRODUCE c +ext_gfx.c:657 gfx_edges_ld slot_from_f64 PRODUCE c +ext_gfx.c:657 gfx_edges_ld slot_from_f64 PRODUCE c +ext_gfx.c:659 gfx_edges_ld eigs_geom_shape READ b +ext_gfx.c:718 builtin_gfx_rect eigs_geom_shape READ b +ext_gfx.c:736 builtin_gfx_line eigs_geom_shape READ b +ext_gfx.c:753 builtin_gfx_point eigs_geom_shape READ b +ext_gfx.c:771 builtin_gfx_circle eigs_geom_shape READ b +ext_gfx.c:804 builtin_gfx_rrect eigs_geom_shape READ b +ext_gfx.c:835 builtin_gfx_clip eigs_geom_shape READ b +ext_gfx.c:866 builtin_gfx_read eigs_list_float READ b +ext_gfx.c:868 builtin_gfx_read eigs_list_float READ b +ext_gfx.c:874 builtin_gfx_read eigs_geom_shape READ b +ext_gfx.c:881 builtin_gfx_read make_int PRODUCE c +ext_gfx.c:882 builtin_gfx_read make_int PRODUCE c +ext_gfx.c:883 builtin_gfx_read make_int PRODUCE c +ext_gfx.c:926 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:932 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:937 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:938 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:943 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:944 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:945 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:950 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:951 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:952 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:957 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:958 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:968 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:969 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:977 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:978 builtin_gfx_poll make_int PRODUCE c +ext_gfx.c:1000 builtin_gfx_ticks make_int PRODUCE c +ext_gfx.c:1005 builtin_gfx_delay val_is_number TEST a +ext_gfx.c:1008 builtin_gfx_delay eigs_magnitude_arg READ b +ext_gfx.c:1157 builtin_gfx_text eigs_list_float READ b +ext_gfx.c:1159 builtin_gfx_text eigs_list_float READ b +ext_gfx.c:1161 builtin_gfx_text eigs_list_float READ b +ext_gfx.c:1242 builtin_gfx_text_width VAL_FLOAT TEST a +ext_gfx.c:1242 builtin_gfx_text_width VAL_INT TEST a +ext_gfx.c:1244 builtin_gfx_text_width eigs_list_magnitude READ b +ext_gfx.c:1254 builtin_gfx_text_width make_int PRODUCE c +ext_gfx.c:1262 builtin_gfx_text_width make_int PRODUCE c +ext_gfx.c:1269 builtin_gfx_text_width make_int PRODUCE c +ext_gfx.c:1276 builtin_gfx_text_width make_int PRODUCE c +ext_gfx.c:1289 builtin_gfx_text_height val_is_number TEST a +ext_gfx.c:1291 builtin_gfx_text_height VAL_FLOAT TEST a +ext_gfx.c:1291 builtin_gfx_text_height VAL_INT TEST a +ext_gfx.c:1293 builtin_gfx_text_height val_is_number TEST a +ext_gfx.c:1295 builtin_gfx_text_height eigs_magnitude_arg READ b +ext_gfx.c:1299 builtin_gfx_text_height VAL_FLOAT TEST a +ext_gfx.c:1299 builtin_gfx_text_height VAL_INT TEST a +ext_gfx.c:1301 builtin_gfx_text_height eigs_list_magnitude READ b +ext_gfx.c:1308 builtin_gfx_text_height make_int PRODUCE c +ext_gfx.c:1313 builtin_gfx_text_height make_int PRODUCE c +ext_gfx.c:1315 builtin_gfx_text_height make_int PRODUCE c +ext_gfx.c:1383 audio_convert_samples buffer_read_f64 READ b +ext_gfx.c:1386 audio_convert_samples VAL_FLOAT TEST a +ext_gfx.c:1386 audio_convert_samples VAL_INT TEST a +ext_gfx.c:1390 audio_convert_samples eigs_slot_float READ b +ext_gfx.c:1398 audio_convert_samples eigs_slot_bounded READ b +ext_gfx.c:1398 audio_convert_samples slot_from_f64 PRODUCE c +ext_gfx.c:1483 builtin_audio_open VAL_FLOAT TEST a +ext_gfx.c:1483 builtin_audio_open VAL_INT TEST a +ext_gfx.c:1484 builtin_audio_open VAL_FLOAT TEST a +ext_gfx.c:1484 builtin_audio_open VAL_INT TEST a +ext_gfx.c:1485 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1491 builtin_audio_open eigs_list_int_range READ b +ext_gfx.c:1492 builtin_audio_open eigs_list_int_range READ b +ext_gfx.c:1493 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1503 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1506 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1521 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1525 builtin_audio_open make_int PRODUCE c +ext_gfx.c:1544 builtin_audio_pause val_is_number TEST a +ext_gfx.c:1547 builtin_audio_pause val_is_number TEST a +ext_gfx.c:1548 builtin_audio_pause eigs_int_arg_range READ b +ext_gfx.c:1639 builtin_audio_capture_open VAL_FLOAT TEST a +ext_gfx.c:1639 builtin_audio_capture_open VAL_INT TEST a +ext_gfx.c:1640 builtin_audio_capture_open VAL_FLOAT TEST a +ext_gfx.c:1640 builtin_audio_capture_open VAL_INT TEST a +ext_gfx.c:1641 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1647 builtin_audio_capture_open eigs_list_int_range READ b +ext_gfx.c:1648 builtin_audio_capture_open eigs_list_int_range READ b +ext_gfx.c:1649 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1653 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1657 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1678 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1681 builtin_audio_capture_open make_int PRODUCE c +ext_gfx.c:1773 builtin_audio_stream_open VAL_FLOAT TEST a +ext_gfx.c:1773 builtin_audio_stream_open VAL_INT TEST a +ext_gfx.c:1774 builtin_audio_stream_open VAL_FLOAT TEST a +ext_gfx.c:1774 builtin_audio_stream_open VAL_INT TEST a +ext_gfx.c:1775 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1781 builtin_audio_stream_open eigs_list_int_range READ b +ext_gfx.c:1782 builtin_audio_stream_open eigs_list_int_range READ b +ext_gfx.c:1783 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1785 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1788 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1807 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1811 builtin_audio_stream_open make_int PRODUCE c +ext_gfx.c:1855 builtin_audio_stream_queued make_int PRODUCE c +ext_gfx.c:1858 builtin_audio_stream_queued make_int PRODUCE c +ext_gfx.c:1894 builtin_audio_music_play VAL_FLOAT TEST a +ext_gfx.c:1894 builtin_audio_music_play VAL_INT TEST a +ext_gfx.c:1898 builtin_audio_music_play VAL_FLOAT TEST a +ext_gfx.c:1898 builtin_audio_music_play VAL_INT TEST a +ext_gfx.c:1899 builtin_audio_music_play eigs_list_int_range READ b +ext_gfx.c:1941 builtin_audio_music_volume val_is_number TEST a +ext_gfx.c:1943 builtin_audio_music_volume VAL_FLOAT TEST a +ext_gfx.c:1943 builtin_audio_music_volume VAL_INT TEST a +ext_gfx.c:1946 builtin_audio_music_volume val_is_number TEST a +ext_gfx.c:1947 builtin_audio_music_volume eigs_bounded_arg READ b +ext_gfx.c:1949 builtin_audio_music_volume VAL_FLOAT TEST a +ext_gfx.c:1949 builtin_audio_music_volume VAL_INT TEST a +ext_gfx.c:1950 builtin_audio_music_volume eigs_list_bounded READ b +ext_gfx.c:1973 builtin_audio_play make_int PRODUCE c +ext_gfx.c:1978 builtin_audio_play make_int PRODUCE c +ext_gfx.c:1980 builtin_audio_play make_int PRODUCE c +ext_gfx.c:1982 builtin_audio_play make_int PRODUCE c +ext_gfx.c:1996 builtin_audio_play_loop VAL_FLOAT TEST a +ext_gfx.c:1996 builtin_audio_play_loop VAL_INT TEST a +ext_gfx.c:1997 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:1999 builtin_audio_play_loop eigs_list_int_range READ b +ext_gfx.c:1999 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2002 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2013 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2018 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2020 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2022 builtin_audio_play_loop make_int PRODUCE c +ext_gfx.c:2031 builtin_audio_volume VAL_FLOAT TEST a +ext_gfx.c:2031 builtin_audio_volume VAL_INT TEST a +ext_gfx.c:2032 builtin_audio_volume VAL_FLOAT TEST a +ext_gfx.c:2032 builtin_audio_volume VAL_INT TEST a +ext_gfx.c:2033 builtin_audio_volume make_int PRODUCE c +ext_gfx.c:2035 builtin_audio_volume eigs_list_int_range READ b +ext_gfx.c:2035 builtin_audio_volume make_int PRODUCE c +ext_gfx.c:2037 builtin_audio_volume eigs_list_bounded READ b +ext_gfx.c:2037 builtin_audio_volume make_int PRODUCE c +ext_gfx.c:2038 builtin_audio_volume make_int PRODUCE c +ext_gfx.c:2044 builtin_audio_volume make_int PRODUCE c +ext_gfx.c:2051 builtin_audio_stop val_is_number TEST a +ext_gfx.c:2052 builtin_audio_stop make_int PRODUCE c +ext_gfx.c:2054 builtin_audio_stop eigs_int_arg_range READ b +ext_gfx.c:2055 builtin_audio_stop make_int PRODUCE c +ext_gfx.c:2056 builtin_audio_stop make_int PRODUCE c +ext_gfx.c:2057 builtin_audio_stop make_int PRODUCE c +ext_gfx.c:2063 builtin_audio_stop make_int PRODUCE c +ext_gfx.c:2069 builtin_audio_queue_size make_int PRODUCE c +ext_gfx.c:2083 builtin_audio_queue_size make_int PRODUCE c +ext_gfx.c:2104 audio_generator_sample_count eigs_slot_magnitude READ b +ext_gfx.c:2104 audio_generator_sample_count slot_from_f64 PRODUCE c +ext_gfx.c:2139 builtin_audio_sine VAL_FLOAT TEST a +ext_gfx.c:2139 builtin_audio_sine VAL_INT TEST a +ext_gfx.c:2140 builtin_audio_sine VAL_FLOAT TEST a +ext_gfx.c:2140 builtin_audio_sine VAL_INT TEST a +ext_gfx.c:2141 builtin_audio_sine VAL_FLOAT TEST a +ext_gfx.c:2141 builtin_audio_sine VAL_INT TEST a +ext_gfx.c:2143 builtin_audio_sine eigs_list_float READ b +ext_gfx.c:2144 builtin_audio_sine eigs_list_float READ b +ext_gfx.c:2145 builtin_audio_sine eigs_list_float READ b +ext_gfx.c:2156 builtin_audio_sine make_float PRODUCE c +ext_gfx.c:2176 builtin_audio_saw VAL_FLOAT TEST a +ext_gfx.c:2176 builtin_audio_saw VAL_INT TEST a +ext_gfx.c:2177 builtin_audio_saw VAL_FLOAT TEST a +ext_gfx.c:2177 builtin_audio_saw VAL_INT TEST a +ext_gfx.c:2178 builtin_audio_saw VAL_FLOAT TEST a +ext_gfx.c:2178 builtin_audio_saw VAL_INT TEST a +ext_gfx.c:2180 builtin_audio_saw eigs_list_float READ b +ext_gfx.c:2181 builtin_audio_saw eigs_list_float READ b +ext_gfx.c:2182 builtin_audio_saw eigs_list_float READ b +ext_gfx.c:2193 builtin_audio_saw make_float PRODUCE c +ext_gfx.c:2213 builtin_audio_square VAL_FLOAT TEST a +ext_gfx.c:2213 builtin_audio_square VAL_INT TEST a +ext_gfx.c:2214 builtin_audio_square VAL_FLOAT TEST a +ext_gfx.c:2214 builtin_audio_square VAL_INT TEST a +ext_gfx.c:2215 builtin_audio_square VAL_FLOAT TEST a +ext_gfx.c:2215 builtin_audio_square VAL_INT TEST a +ext_gfx.c:2217 builtin_audio_square eigs_list_float READ b +ext_gfx.c:2218 builtin_audio_square eigs_list_float READ b +ext_gfx.c:2219 builtin_audio_square eigs_list_float READ b +ext_gfx.c:2230 builtin_audio_square make_float PRODUCE c +ext_gfx.c:2251 builtin_audio_sweep VAL_FLOAT TEST a +ext_gfx.c:2251 builtin_audio_sweep VAL_INT TEST a +ext_gfx.c:2252 builtin_audio_sweep VAL_FLOAT TEST a +ext_gfx.c:2252 builtin_audio_sweep VAL_INT TEST a +ext_gfx.c:2253 builtin_audio_sweep VAL_FLOAT TEST a +ext_gfx.c:2253 builtin_audio_sweep VAL_INT TEST a +ext_gfx.c:2254 builtin_audio_sweep VAL_FLOAT TEST a +ext_gfx.c:2254 builtin_audio_sweep VAL_INT TEST a +ext_gfx.c:2255 builtin_audio_sweep VAL_FLOAT TEST a +ext_gfx.c:2255 builtin_audio_sweep VAL_INT TEST a +ext_gfx.c:2257 builtin_audio_sweep eigs_list_float READ b +ext_gfx.c:2258 builtin_audio_sweep eigs_list_float READ b +ext_gfx.c:2259 builtin_audio_sweep eigs_list_float READ b +ext_gfx.c:2260 builtin_audio_sweep eigs_list_float READ b +ext_gfx.c:2261 builtin_audio_sweep eigs_list_int READ b +ext_gfx.c:2280 builtin_audio_sweep make_float PRODUCE c +ext_gfx.c:2364 builtin_audio_noise VAL_FLOAT TEST a +ext_gfx.c:2364 builtin_audio_noise VAL_INT TEST a +ext_gfx.c:2365 builtin_audio_noise VAL_FLOAT TEST a +ext_gfx.c:2365 builtin_audio_noise VAL_INT TEST a +ext_gfx.c:2367 builtin_audio_noise eigs_list_float READ b +ext_gfx.c:2368 builtin_audio_noise eigs_list_float READ b +ext_gfx.c:2400 builtin_audio_noise make_float PRODUCE c +ext_gfx.c:2432 builtin_audio_mix VAL_FLOAT TEST a +ext_gfx.c:2432 builtin_audio_mix VAL_INT TEST a +ext_gfx.c:2432 builtin_audio_mix eigs_list_float READ b +ext_gfx.c:2433 builtin_audio_mix VAL_FLOAT TEST a +ext_gfx.c:2433 builtin_audio_mix VAL_INT TEST a +ext_gfx.c:2433 builtin_audio_mix eigs_list_float READ b +ext_gfx.c:2438 builtin_audio_mix make_float PRODUCE c +ext_gfx.c:2464 builtin_audio_gain VAL_FLOAT TEST a +ext_gfx.c:2464 builtin_audio_gain VAL_INT TEST a +ext_gfx.c:2466 builtin_audio_gain eigs_list_float READ b +ext_gfx.c:2472 builtin_audio_gain VAL_FLOAT TEST a +ext_gfx.c:2472 builtin_audio_gain VAL_INT TEST a +ext_gfx.c:2472 builtin_audio_gain eigs_list_float READ b +ext_gfx.c:2477 builtin_audio_gain make_float PRODUCE c +ext_gfx.c:2497 builtin_audio_envelope VAL_FLOAT TEST a +ext_gfx.c:2497 builtin_audio_envelope VAL_INT TEST a +ext_gfx.c:2498 builtin_audio_envelope VAL_FLOAT TEST a +ext_gfx.c:2498 builtin_audio_envelope VAL_INT TEST a +ext_gfx.c:2499 builtin_audio_envelope VAL_FLOAT TEST a +ext_gfx.c:2499 builtin_audio_envelope VAL_INT TEST a +ext_gfx.c:2500 builtin_audio_envelope VAL_FLOAT TEST a +ext_gfx.c:2500 builtin_audio_envelope VAL_INT TEST a +ext_gfx.c:2511 builtin_audio_envelope eigs_list_float READ b +ext_gfx.c:2512 builtin_audio_envelope eigs_list_float READ b +ext_gfx.c:2513 builtin_audio_envelope eigs_list_float READ b +ext_gfx.c:2514 builtin_audio_envelope eigs_list_float READ b +ext_gfx.c:2520 builtin_audio_envelope eigs_slot_magnitude READ b +ext_gfx.c:2520 builtin_audio_envelope slot_from_f64 PRODUCE c +ext_gfx.c:2522 builtin_audio_envelope eigs_slot_magnitude READ b +ext_gfx.c:2522 builtin_audio_envelope slot_from_f64 PRODUCE c +ext_gfx.c:2524 builtin_audio_envelope eigs_slot_magnitude READ b +ext_gfx.c:2524 builtin_audio_envelope slot_from_f64 PRODUCE c +ext_gfx.c:2550 builtin_audio_envelope VAL_FLOAT TEST a +ext_gfx.c:2550 builtin_audio_envelope VAL_INT TEST a +ext_gfx.c:2550 builtin_audio_envelope eigs_list_float READ b +ext_gfx.c:2553 builtin_audio_envelope make_float PRODUCE c +ext_gfx.c:2562 gfx_byte_at buffer_byte_at READ b +ext_gfx.c:2584 builtin_gfx_fb eigs_list_int_range READ b +ext_gfx.c:2585 builtin_gfx_fb eigs_list_int_range READ b +ext_gfx.c:2586 builtin_gfx_fb eigs_list_float READ b +ext_gfx.c:2588 builtin_gfx_fb eigs_list_float READ b +ext_gfx.c:2590 builtin_gfx_fb eigs_list_float READ b +ext_http.c:589 builtin_http_early_bind val_is_number TEST a +ext_http.c:595 builtin_http_early_bind eigs_int_arg_range READ b +ext_http.c:668 builtin_http_serve eigs_int_arg_range READ b +ext_http.c:987 builtin_shared_incr val_is_number TEST a +ext_http.c:991 builtin_shared_incr eigs_number_arg READ b +ext_http.c:1004 builtin_shared_incr val_is_number TEST a +ext_http.c:1005 builtin_shared_incr eigs_number_arg READ b +ext_http.c:1020 builtin_shared_incr make_int PRODUCE c +ext_http.c:1024 builtin_shared_incr make_float PRODUCE c +ext_http.c:1096 builtin_shared_delete make_int PRODUCE c +ext_http.c:1098 builtin_shared_delete make_int PRODUCE c +ext_http.c:1114 builtin_shared_delete make_int PRODUCE c +ext_http.c:1133 builtin_shared_size make_int PRODUCE c +ext_http.c:1137 builtin_shared_size make_int PRODUCE c +ext_net.c:145 net_count_at eigs_opt_int_range READ b +ext_net.c:153 net_timeout_at eigs_opt_magnitude READ b +ext_net.c:165 builtin_net_listen val_is_number TEST a +ext_net.c:170 builtin_net_listen eigs_int_arg_range READ b +ext_net.c:187 builtin_net_listen make_int PRODUCE c +ext_net.c:194 builtin_net_port val_is_number TEST a +ext_net.c:198 builtin_net_port eigs_int_arg READ b +ext_net.c:207 builtin_net_port make_int PRODUCE c +ext_net.c:223 builtin_net_accept val_is_number TEST a +ext_net.c:227 builtin_net_accept eigs_int_arg READ b +ext_net.c:239 builtin_net_accept make_int PRODUCE c +ext_net.c:255 builtin_net_dial eigs_list_int_range READ b +ext_net.c:292 builtin_net_dial make_int PRODUCE c +ext_net.c:304 builtin_net_recv eigs_int_arg READ b +ext_net.c:339 net_send_bytes make_int PRODUCE c +ext_net.c:344 net_send_bytes make_int PRODUCE c +ext_net.c:347 net_send_bytes make_int PRODUCE c +ext_net.c:359 builtin_net_send eigs_int_arg READ b +ext_net.c:375 builtin_net_send buffer_byte_at READ b +ext_net.c:380 builtin_net_send eigs_elem_byte READ b +ext_net.c:397 builtin_net_close val_is_number TEST a +ext_net.c:403 builtin_net_close eigs_int_arg READ b +ext_store.c:164 store_json_encode_buf_elem eigs_num_text FORMAT h +ext_store.c:175 store_json_encode_slot slot_is_f64 TEST h +ext_store.c:178 store_json_encode_slot SLOT_F64_RAW RAW h +ext_store.c:180 store_json_encode_slot eigs_num_text FORMAT h +ext_store.c:184 store_json_encode_slot slot_is_smi TEST h +ext_store.c:187 store_json_encode_slot slot_smi_val RAW h +ext_store.c:200 store_json_encode VAL_FLOAT CASE h +ext_store.c:203 store_json_encode VAL_F64_RAW RAW h +ext_store.c:205 store_json_encode eigs_num_text FORMAT h +ext_store.c:209 store_json_encode VAL_INT CASE h +ext_store.c:211 store_json_encode VAL_I64_RAW RAW h +ext_store.c:334 store_json_parse_number strtod PARSE h +ext_store.c:347 store_json_parse_number make_int PRODUCE h +ext_store.c:348 store_json_parse_number make_float PRODUCE h +ext_store.c:384 store_buffer_from_tag val_is_number TEST h +ext_store.c:384 store_buffer_from_tag val_is_number TEST h +ext_store.c:389 store_buffer_from_tag eigs_number_arg READ h +ext_store.c:390 store_buffer_from_tag eigs_number_arg READ h +ext_store.c:394 store_buffer_from_tag num_to_i64_exact READ h +ext_store.c:396 store_buffer_from_tag num_to_i64_exact READ h +ext_store.c:414 store_buffer_from_tag val_is_number TEST h +ext_store.c:429 store_buffer_from_tag eigs_float_arg READ h +ext_store.c:429 store_buffer_from_tag val_is_number TEST h +ext_store.c:767 store_load_catalog val_is_number TEST h +ext_store.c:767 store_load_catalog val_is_number TEST h +ext_store.c:773 store_load_catalog eigs_int_arg_range READ h +ext_store.c:846 get_store_why val_is_number TEST h +ext_store.c:848 get_store_why eigs_int_arg_range READ h +ext_store.c:851 get_store_why val_is_number TEST h +ext_store.c:852 get_store_why eigs_int_arg_range READ h +ext_store.c:991 builtin_store_open make_int PRODUCE h +ext_store.c:992 builtin_store_open make_int PRODUCE h +ext_store.c:1006 builtin_store_close eigs_int_arg_range READ h +ext_store.c:1009 builtin_store_close eigs_int_arg_range READ h +ext_store.c:1055 builtin_store_put make_int PRODUCE h +ext_store.c:1056 builtin_store_put make_int PRODUCE h +ext_store.c:1071 builtin_store_put eigs_int_arg_range READ h +ext_store.c:1073 builtin_store_put eigs_int_arg_range READ h +ext_store.c:1088 builtin_store_put make_int PRODUCE h +ext_store.c:1212 builtin_store_get val_is_number TEST h +ext_store.c:1213 builtin_store_get eigs_int_arg READ h +ext_store.c:1225 builtin_store_get eigs_int_arg_range READ h +ext_store.c:1285 store_locate eigs_int_arg_range READ h +ext_store.c:1382 builtin_store_delete val_is_number TEST h +ext_store.c:1383 builtin_store_delete eigs_int_arg READ h +ext_store.c:1432 builtin_store_query eigs_int_arg_range READ h +ext_store.c:1468 builtin_store_count make_int PRODUCE h +ext_store.c:1476 builtin_store_count make_int PRODUCE h +ext_store.c:1477 builtin_store_count make_int PRODUCE h +ext_store.c:1480 builtin_store_count make_int PRODUCE h +ext_store.c:1486 builtin_store_count make_int PRODUCE h +ext_store.c:1490 builtin_store_count make_int PRODUCE h +ext_store.c:1492 builtin_store_count eigs_int_arg_range READ h +ext_store.c:1504 builtin_store_count make_int PRODUCE h +ext_store.c:1510 builtin_store_count make_int PRODUCE h +ext_store.c:1544 builtin_store_update val_is_number TEST h +ext_store.c:1545 builtin_store_update eigs_int_arg READ h +ext_store.c:1689 builtin_store_drop eigs_int_arg_range READ h +jit.c:1303 emit_conditional_incref_rax TAG-IMM 0xFFFB EMIT d +jit.c:1427 emit_addsd_xmm0_xmm1 emit_addsd_xmm0_xmm1 EMIT d +jit.c:1431 emit_subsd_xmm0_xmm1 emit_subsd_xmm0_xmm1 EMIT d +jit.c:1435 emit_mulsd_xmm0_xmm1 emit_mulsd_xmm0_xmm1 EMIT d +jit.c:1439 emit_divsd_xmm0_xmm1 emit_divsd_xmm0_xmm1 EMIT d +jit.c:1445 emit_cvttsd2si_xmm0_rax emit_cvttsd2si_xmm0_rax EMIT d +jit.c:1449 emit_cvttsd2si_xmm1_rdx emit_cvttsd2si_xmm1_rdx EMIT d +jit.c:1457 emit_cvtsi2sd_rax_xmm2 emit_cvtsi2sd_rax_xmm2 EMIT d +jit.c:1461 emit_cvtsi2sd_rdx_xmm2 emit_cvtsi2sd_rdx_xmm2 EMIT d +jit.c:1465 emit_ucomisd_xmm0_xmm2 emit_ucomisd_xmm0_xmm2 EMIT d +jit.c:1469 emit_ucomisd_xmm1_xmm2 emit_ucomisd_xmm1_xmm2 EMIT d +jit.c:1473 emit_cvtsi2sd_rdx_xmm0 emit_cvtsi2sd_rdx_xmm0 EMIT d +jit.c:1520 emit_btr_63_rax emit_btr_63_rax EMIT d +jit.c:1538 emit_cvtsi2sd_rax_xmm0 emit_cvtsi2sd_rax_xmm0 EMIT d +jit.c:1552 emit_ucomisd_xmm0_xmm1 emit_ucomisd_xmm0_xmm1 EMIT d +jit.c:1614 emit_btr_63_rdx emit_btr_63_rdx EMIT d +jit.c:1619 emit_btr_63_rsi emit_btr_63_rsi EMIT d +jit.c:1646 emit_immediate_num_check_rax emit_immediate_num_check_rax EMIT d +jit.c:1649 emit_immediate_num_check_rax TAG-IMM 0xFFF8 EMIT d +jit.c:1652 emit_immediate_num_check_rdx emit_immediate_num_check_rdx EMIT d +jit.c:1655 emit_immediate_num_check_rdx TAG-IMM 0xFFF8 EMIT d +jit.c:1661 emit_overflow_check_rax EMIT d +jit.c:1664 emit_overflow_check_rax emit_btr_63_rdx EMIT d +jit.c:1725 emit_cvttsd2si_xmm0_r8d emit_cvttsd2si_xmm0_r8d EMIT d +jit.c:1730 emit_cvtsi2sd_r8d_xmm1 emit_cvtsi2sd_r8d_xmm1 EMIT d +jit.c:1923 emit_jump_unless_bool_rax TAG-IMM 0xFFF9 EMIT d +jit.c:2041 emit_conditional_decref_rsi_hook TAG-IMM 0xFFFB EMIT d +jit.c:2187 emit_dict_cache_probe TAG-IMM 0xFFFB EMIT d +jit.c:2352 emit_load_numeric_operand EMIT d +jit.c:2357 emit_load_numeric_operand TAG-IMM 0xFFF8 EMIT d +jit.c:2362 emit_load_numeric_operand TAG-IMM 0xFFFB EMIT d +jit.c:2365 emit_load_numeric_operand TAG-IMM 0xFFFD EMIT d +jit.c:2372 emit_load_numeric_operand VAL_FLOAT EMIT d +jit.c:2376 emit_load_numeric_operand VAL_F64_OFFSET RAW d +jit.c:2377 emit_load_numeric_operand VAL_F64_OFFSET RAW d +jit.c:2406 emit_decref_binop_operands TAG-IMM 0xFFFB EMIT d +jit.c:2434 emit_commit_binop TAG-IMM 0xFFFB EMIT d +jit.c:2642 jit_compile_to_thunk max_normal_bits EMIT d +jit.c:2643 jit_compile_to_thunk EIGS_NUM_MAX GUARD d +jit.c:2643 jit_compile_to_thunk max_normal_bits EMIT d +jit.c:2810 jit_compile_to_thunk/OP_NULL TAG-IMM 0xFFF8 EMIT d +jit.c:2831 jit_compile_to_thunk/OP_CONST VAL_FLOAT TEST d +jit.c:2834 jit_compile_to_thunk/OP_CONST VAL_F64_RAW RAW d +jit.c:2837 jit_compile_to_thunk/OP_CONST TAG-IMM 0xFFFB EMIT d +jit.c:3033 jit_compile_to_thunk/OP_SET_NAME TAG-IMM 0xFFFB EMIT d +jit.c:3154 jit_compile_to_thunk/OP_LOCAL_DOT_GET emit_immediate_num_check_rax EMIT d +jit.c:3280 jit_compile_to_thunk/OP_LOCAL_DOT_SET emit_immediate_num_check_rax EMIT d +jit.c:3283 jit_compile_to_thunk/OP_LOCAL_DOT_SET emit_immediate_num_check_rdx EMIT d +jit.c:3461 jit_compile_to_thunk/OP_SET_LOCAL TAG-IMM 0xFFF8 EMIT d +jit.c:3466 jit_compile_to_thunk/OP_SET_LOCAL TAG-IMM 0xFFFB EMIT d +jit.c:3468 jit_compile_to_thunk/OP_SET_LOCAL TAG-IMM 0xFFFD EMIT d +jit.c:3474 jit_compile_to_thunk/OP_SET_LOCAL VAL_FLOAT EMIT d +jit.c:3480 jit_compile_to_thunk/OP_SET_LOCAL VAL_F64_OFFSET RAW d +jit.c:3539 jit_compile_to_thunk/OP_MOD emit_load_numeric_operand EMIT d +jit.c:3543 jit_compile_to_thunk/OP_MOD emit_load_numeric_operand EMIT d +jit.c:3553 jit_compile_to_thunk/OP_MOD emit_btr_63_rsi EMIT d +jit.c:3567 jit_compile_to_thunk/OP_MOD fmod EMIT d +jit.c:3573 jit_compile_to_thunk/OP_MOD emit_overflow_check_rax EMIT d +jit.c:3573 jit_compile_to_thunk/OP_MOD max_normal_bits EMIT d +jit.c:3621 jit_compile_to_thunk/OP_ADD emit_load_numeric_operand EMIT d +jit.c:3625 jit_compile_to_thunk/OP_ADD emit_load_numeric_operand EMIT d +jit.c:3633 jit_compile_to_thunk/OP_ADD emit_addsd_xmm0_xmm1 EMIT d +jit.c:3634 jit_compile_to_thunk/OP_ADD emit_subsd_xmm0_xmm1 EMIT d +jit.c:3635 jit_compile_to_thunk/OP_ADD emit_mulsd_xmm0_xmm1 EMIT d +jit.c:3636 jit_compile_to_thunk/OP_ADD emit_divsd_xmm0_xmm1 EMIT d +jit.c:3643 jit_compile_to_thunk/OP_ADD emit_overflow_check_rax EMIT d +jit.c:3643 jit_compile_to_thunk/OP_ADD max_normal_bits EMIT d +jit.c:3682 jit_compile_to_thunk/OP_EQ emit_load_numeric_operand EMIT d +jit.c:3686 jit_compile_to_thunk/OP_EQ emit_load_numeric_operand EMIT d +jit.c:3699 jit_compile_to_thunk/OP_EQ emit_ucomisd_xmm0_xmm1 EMIT d +jit.c:3739 jit_compile_to_thunk/OP_BAND emit_immediate_num_check_rax EMIT d +jit.c:3741 jit_compile_to_thunk/OP_BAND emit_immediate_num_check_rdx EMIT d +jit.c:3747 jit_compile_to_thunk/OP_BAND emit_cvttsd2si_xmm0_rax EMIT d +jit.c:3748 jit_compile_to_thunk/OP_BAND emit_cvtsi2sd_rax_xmm2 EMIT d +jit.c:3749 jit_compile_to_thunk/OP_BAND emit_ucomisd_xmm0_xmm2 EMIT d +jit.c:3752 jit_compile_to_thunk/OP_BAND emit_cvttsd2si_xmm1_rdx EMIT d +jit.c:3753 jit_compile_to_thunk/OP_BAND emit_cvtsi2sd_rdx_xmm2 EMIT d +jit.c:3754 jit_compile_to_thunk/OP_BAND emit_ucomisd_xmm1_xmm2 EMIT d +jit.c:3774 jit_compile_to_thunk/OP_BAND emit_cvtsi2sd_rdx_xmm0 EMIT d +jit.c:3804 jit_compile_to_thunk/OP_NOT emit_immediate_num_check_rax EMIT d +jit.c:3817 jit_compile_to_thunk/OP_NEG emit_btr_63_rax EMIT d +jit.c:3832 jit_compile_to_thunk/OP_NEG emit_cvttsd2si_xmm0_rax EMIT d +jit.c:3833 jit_compile_to_thunk/OP_NEG emit_cvtsi2sd_rax_xmm2 EMIT d +jit.c:3834 jit_compile_to_thunk/OP_NEG emit_ucomisd_xmm0_xmm2 EMIT d +jit.c:3838 jit_compile_to_thunk/OP_NEG emit_cvtsi2sd_rax_xmm0 EMIT d +jit.c:3957 jit_compile_to_thunk/OP_JUMP_IF_FALSE emit_immediate_num_check_rax EMIT d +jit.c:3960 jit_compile_to_thunk/OP_JUMP_IF_FALSE emit_btr_63_rax EMIT d +jit.c:4097 jit_compile_to_thunk/OP_INDEX_SET emit_immediate_num_check_rdx EMIT d +jit.c:4102 jit_compile_to_thunk/OP_INDEX_SET TAG-IMM 0xFFFB EMIT d +jit.c:4116 jit_compile_to_thunk/OP_INDEX_SET emit_cvttsd2si_xmm0_r8d EMIT d +jit.c:4117 jit_compile_to_thunk/OP_INDEX_SET emit_cvtsi2sd_r8d_xmm1 EMIT d +jit.c:4118 jit_compile_to_thunk/OP_INDEX_SET emit_ucomisd_xmm0_xmm1 EMIT d +jit.c:4126 jit_compile_to_thunk/OP_INDEX_SET emit_immediate_num_check_rax EMIT d +jit.c:4445 jit_compile_to_thunk/OP_BINARY_LINE VAL_FLOAT TEST d +jit_smoke.c:103 jit_helper_local_idx_get slot_from_f64 PRODUCE d +jit_smoke.c:138 jit_helper_iter_next SLOT_F64_RAW RAW d +jit_smoke.c:142 jit_helper_iter_next slot_from_f64 PRODUCE d +jit_smoke.c:149 jit_helper_index_get SLOT_F64_RAW RAW d +jit_smoke.c:149 jit_helper_index_get SLOT_F64_RAW RAW d +jit_smoke.c:153 jit_helper_index_get slot_from_f64 PRODUCE d +jit_smoke.c:174 jit_helper_set_name SLOT_F64_RAW RAW d +jit_smoke.c:174 jit_helper_set_name SLOT_F64_RAW RAW d +jit_smoke.c:175 jit_helper_set_name SLOT_F64_RAW RAW d +jit_smoke.c:178 jit_helper_set_name SLOT_F64_RAW RAW d +jit_smoke.c:331 run_store_cases slot_from_f64 PRODUCE d +jit_smoke.c:345 run_store_cases slot_from_f64 PRODUCE d +jit_smoke.c:362 run_store_cases SLOT_F64_RAW RAW d +jit_smoke.c:362 run_store_cases SLOT_F64_RAW RAW d +jit_smoke.c:363 run_store_cases SLOT_F64_RAW RAW d +jit_smoke.c:370 run_store_cases SLOT_F64_RAW RAW d +jit_smoke.c:446 run_index_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:446 run_index_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:538 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:539 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:540 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:540 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:541 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:542 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:543 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:543 run_reader_bail_cases SLOT_F64_RAW RAW d +jit_smoke.c:603 run_exit_cases VAL_FLOAT TAGSET d +jit_smoke.c:604 run_exit_cases VAL_F64_RAW WRITE d +jit_smoke.c:630 run_exit_cases slot_from_f64 PRODUCE d +jit_smoke.c:639 run_exit_cases SLOT_F64_RAW RAW d +jit_smoke.c:644 run_exit_cases SLOT_F64_RAW RAW d +lexer.c:35 tok_add number RAW b +lexer.c:42 tok_add number RAW b +lexer.c:42 tok_add number RAW b +lexer.c:701 tokenize_at_line number RAW b +lexer.c:792 tokenize_at_line number RAW b +lexer.c:793 tokenize_at_line number RAW b +lexer.c:806 tokenize_at_line strtod PARSE c +lexer.c:807 tokenize_at_line number RAW b +lexer.c:823 tokenize_at_line number RAW b +lexer.c:824 tokenize_at_line number RAW b +lexer.c:828 tokenize_at_line number RAW b +lint.c:247 collect_refs AST_NUM CASE a +lint.c:311 collect_assigns AST_NUM CASE a +lint.c:369 cond_has_bare_predicate AST_NUM CASE a +lint.c:431 collect_loop_assign_names AST_NUM CASE a +lint.c:531 check_empty_blocks AST_NUM CASE a +lint.c:640 check_dup_keys AST_NUM CASE a +lint.c:740 check_builtin_shadow AST_NUM CASE a +lint.c:870 check_disc_interrog AST_NUM CASE a +lint.c:947 check_func_unreachable AST_NUM CASE a +lint.c:1016 check_is_conditions AST_NUM CASE a +lint.c:1118 check_unused_params AST_NUM CASE a +lint.c:1229 w015_collect_locals AST_NUM CASE a +lint.c:1297 w015_flag AST_NUM CASE a +lint.c:1507 w023_walk AST_NUM CASE a +lint.c:1566 w023_collect_binders AST_NUM CASE a +lint.c:1727 w023_scan_sequence AST_NUM CASE a +lint.c:1891 w016_scan AST_NUM CASE a +lint.c:2050 w017_scan AST_NUM CASE a +lint.c:2077 #define LINT_FOR_EACH_CHILD AST_NUM CASE a +lint.c:2293 w022_collect_bindings AST_NUM CASE a +lint.c:2419 w020_facts AST_NUM CASE a +lint.c:2479 w020_facts AST_NUM CASE a +lint.c:2553 w020_scan AST_NUM CASE a +lint.c:2815 w018_scan AST_NUM CASE a +lint.c:3096 w024_collect AST_NUM CASE a +lint.c:3121 w024_index_varies AST_NUM CASE a +lint.c:3151 w024_elem_walks AST_NUM CASE a +lint.c:3187 w024_operand_inert AST_NUM TEST a +lint.c:3538 w024_walk AST_NUM CASE a +lint_host.c:545 w021_collect_imports AST_NUM CASE a +lint_host.c:634 w021_walk AST_NUM CASE a +lint_host.c:1100 e003_walk AST_NUM CASE a +main.c:282 main number RAW b +main.c:283 main number RAW b +main.c:285 main number RAW b +model_infer.c:550 builtin_eigen_generate eigs_slot_opt_float READ b +model_infer.c:551 builtin_eigen_generate eigs_slot_opt_int_range READ b +model_infer.c:552 builtin_eigen_generate eigs_slot_opt_float READ b +model_infer.c:586 builtin_eigen_generate eigs_slot_int_range READ b +model_infer.c:660 builtin_eigen_generate make_int PRODUCE c +model_infer.c:691 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:697 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:699 builtin_eigen_eval_loss VAL_FLOAT TEST a +model_infer.c:699 builtin_eigen_eval_loss VAL_INT TEST a +model_infer.c:701 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:704 builtin_eigen_eval_loss eigs_list_int_range READ b +model_infer.c:711 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:724 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:736 builtin_eigen_eval_loss eigs_slot_int_range READ b +model_infer.c:765 builtin_eigen_eval_loss make_float PRODUCE c +model_infer.c:766 builtin_eigen_eval_loss make_float PRODUCE c +model_io.c:67 replay_model_load_banner val_is_number TEST a +model_io.c:67 replay_model_load_banner val_is_number TEST a +model_io.c:68 replay_model_load_banner val_is_number TEST a +model_io.c:68 replay_model_load_banner val_is_number TEST a +model_io.c:73 replay_model_load_banner eigs_int_arg_range READ b +model_io.c:75 replay_model_load_banner eigs_int_arg_range READ b +model_io.c:77 replay_model_load_banner eigs_int_arg_range READ b +model_io.c:79 replay_model_load_banner eigs_int_arg_range READ b +model_io.c:85 replay_model_load_banner val_is_number TEST a +model_io.c:86 replay_model_load_banner eigs_int_arg_range READ b +model_io.c:129 json_parse_number strtod PARSE h +model_train.c:712 obs_drift_eps atof PARSE i +model_train.c:1463 builtin_native_train_step eigs_slot_float READ b +model_train.c:1464 builtin_native_train_step VAL_FLOAT TEST a +model_train.c:1464 builtin_native_train_step VAL_INT TEST a +model_train.c:1464 builtin_native_train_step eigs_list_float READ b +model_train.c:1465 builtin_native_train_step strtod PARSE c +model_train.c:1482 builtin_native_train_step eigs_slot_int_range READ b +model_train.c:1496 builtin_native_train_step eigs_slot_int_range READ b +parser.c:549 free_ast AST_NUM CASE a +parser.c:832 parse_primary AST_NUM DATA a +parser.c:833 parse_primary data.number WRITE c +parser.c:833 parse_primary number RAW b +parser.c:833 parse_primary number RAW b +tape_read.c:368 tape_parse strtod PARSE g +tape_read.c:387 tape_parse strtod PARSE g +tape_read.c:388 tape_parse strtod PARSE g +tape_read.c:389 tape_parse strtod PARSE g +tape_read.c:391 tape_parse strtod PARSE g +task.c:314 task_sched_trace_read make_int PRODUCE c +task.c:315 task_sched_trace_read make_float PRODUCE c +task.c:316 task_sched_trace_read make_int PRODUCE c +task.c:584 task_do_kill make_int PRODUCE c +task.c:689 task_error_summary eigs_float_arg READ b +task.c:689 task_error_summary val_is_number TEST a +trace.c:740 trace_query_at slot_from_int PRODUCE g +trace.c:755 trace_query_at slot_from_f64 PRODUCE g +trace.c:804 trace_query_when slot_from_int PRODUCE g +trace.c:825 trace_query_when slot_from_f64 PRODUCE g +trace.c:1587 obs_cfg_sync number RAW g +trace.c:1588 obs_cfg_sync eigs_num_text FORMAT g +trace.c:1588 obs_cfg_sync number RAW g +trace.c:1588 obs_cfg_sync number RAW g +trace.c:1589 obs_cfg_sync number RAW g +trace.c:1590 obs_cfg_sync eigs_num_text FORMAT g +trace.c:1590 obs_cfg_sync number RAW g +trace.c:1590 obs_cfg_sync number RAW g +trace.c:1591 obs_cfg_sync number RAW g +trace.c:1592 obs_cfg_sync eigs_num_text FORMAT g +trace.c:1592 obs_cfg_sync number RAW g +trace.c:1592 obs_cfg_sync number RAW g +trace.c:1593 obs_cfg_sync number RAW g +trace.c:1595 obs_cfg_sync eigs_num_text FORMAT g +trace.c:1595 obs_cfg_sync number RAW g +trace.c:1595 obs_cfg_sync number RAW g +trace.c:1596 obs_cfg_sync number RAW g +trace.c:1939 #define TK_NUM_ VAL_FLOAT DATA g +trace.c:1939 #define TK_NUM_ VAL_INT DATA g +trace.c:2052 kinds_text VAL_FLOAT DATA g +trace.c:2052 kinds_text VAL_INT DATA g +trace.c:2053 kinds_text VAL_FLOAT DATA g +trace.c:2362 parse_value_p strtod PARSE g +trace.c:2468 parse_value_p make_int PRODUCE g +trace.c:2471 parse_value_p strtod PARSE g +trace.c:2481 parse_value_p make_float PRODUCE g +trace.c:2827 trace_source_record make_int PRODUCE g +trace.c:2836 trace_source_record make_int PRODUCE g +trace.c:2860 trace_source_replay VAL_FLOAT TEST g +trace.c:2860 trace_source_replay VAL_INT TEST g +trace.c:2869 trace_source_replay VAL_INT TEST g +trace.c:2871 trace_source_replay slot_f64_value READ g +trace.c:2878 trace_source_replay slot_int_value READ g +trace.c:3047 tape_float_text eigs_num_text FORMAT g +trace.c:3067 write_value_ptr VAL_F64_RAW RAW g +trace.c:3067 write_value_ptr VAL_FLOAT CASE g +trace.c:3068 write_value_ptr VAL_I64_RAW RAW g +trace.c:3068 write_value_ptr VAL_INT CASE g +trace.c:3084 write_slot SLOT_F64_RAW RAW g +trace.c:3084 write_slot slot_is_f64 TEST g +trace.c:3088 write_slot slot_is_smi TEST g +trace.c:3088 write_slot slot_smi_val RAW g +trace.c:3211 write_value_ptr_full VAL_FLOAT CASE g +trace.c:3212 write_value_ptr_full number RAW g +trace.c:3213 write_value_ptr_full VAL_F64_RAW RAW g +trace.c:3213 write_value_ptr_full number RAW g +trace.c:3213 write_value_ptr_full number RAW g +trace.c:3214 write_value_ptr_full number RAW g +trace.c:3217 write_value_ptr_full VAL_INT CASE g +trace.c:3218 write_value_ptr_full number RAW g +trace.c:3219 write_value_ptr_full VAL_I64_RAW RAW g +trace.c:3219 write_value_ptr_full number RAW g +trace.c:3219 write_value_ptr_full number RAW g +trace.c:3220 write_value_ptr_full number RAW g +trace.c:3260 write_value_ptr_full number RAW g +trace.c:3261 write_value_ptr_full eigs_num_text FORMAT g +trace.c:3261 write_value_ptr_full number RAW g +trace.c:3261 write_value_ptr_full number RAW g +trace.c:3262 write_value_ptr_full number RAW g +value_slot.h:84 slot_is_f64 slot_is_f64 TEST a +value_slot.h:90 slot_is_smi slot_is_smi TEST a +value_slot.h:101 #define SLOT_F64_RAW SLOT_F64_RAW RAW b +value_slot.h:101 #define SLOT_F64_RAW d_ RAW b +value_slot.h:113 slot_smi_val slot_smi_val RAW b +value_slot.h:121 slot_from_f64 SLOT_F64_RAW WRITE c +value_slot.h:121 slot_from_f64 slot_from_f64 PRODUCE c +value_slot.h:125 slot_from_int slot_from_int PRODUCE c +value_slot.h:127 slot_from_int make_int PRODUCE c +value_slot.h:202 slot_truthy_immediate SLOT_F64_RAW RAW b +value_slot.h:202 slot_truthy_immediate slot_is_f64 TEST a +value_slot.h:205 slot_truthy_immediate slot_is_smi TEST a +value_slot.h:205 slot_truthy_immediate slot_smi_val RAW b +value_slot.h:219 slot_f64_value slot_f64_value READ b +value_slot.h:220 slot_f64_value SLOT_F64_RAW RAW b +value_slot.h:220 slot_f64_value slot_is_f64 TEST a +value_slot.h:223 slot_f64_value VAL_F64_RAW RAW b +value_slot.h:223 slot_f64_value VAL_FLOAT TEST a +value_slot.h:229 slot_int_value slot_int_value READ b +value_slot.h:230 slot_int_value slot_is_smi TEST a +value_slot.h:230 slot_int_value slot_smi_val RAW b +value_slot.h:233 slot_int_value VAL_I64_RAW RAW b +value_slot.h:233 slot_int_value VAL_INT TEST a +vm.c:117 obs_dump_num eigs_num_text FORMAT i +vm.c:128 obs_dump_value SLOT_F64_RAW RAW i +vm.c:128 obs_dump_value slot_is_f64 TEST i +vm.c:129 obs_dump_value slot_is_smi TEST i +vm.c:129 obs_dump_value slot_smi_val RAW i +vm.c:135 obs_dump_value VAL_F64_RAW RAW i +vm.c:135 obs_dump_value VAL_FLOAT CASE i +vm.c:136 obs_dump_value VAL_I64_RAW RAW i +vm.c:136 obs_dump_value VAL_INT CASE i +vm.c:224 obs_dump_scope slot_is_f64 TEST i +vm.c:225 obs_dump_scope SLOT_F64_RAW RAW i +vm.c:226 obs_dump_scope slot_is_smi TEST i +vm.c:227 obs_dump_scope slot_smi_val RAW i +vm.c:731 dict_set_cached_immediate dict_value_rewrite_f64 PRODUCE c +vm.c:811 slot_bridge_unwrap slot_is_f64 TEST a +vm.c:813 slot_bridge_unwrap SLOT_F64_RAW RAW b +vm.c:813 slot_bridge_unwrap make_float PRODUCE c +vm.c:822 slot_bridge_unwrap slot_is_smi TEST a +vm.c:858 vm_slot_lift SLOT_F64_RAW RAW b +vm.c:858 vm_slot_lift make_float PRODUCE c +vm.c:858 vm_slot_lift slot_is_f64 TEST a +vm.c:863 vm_slot_lift slot_is_smi TEST a +vm.c:895 slot_truthy SLOT_F64_RAW RAW b +vm.c:895 slot_truthy slot_is_f64 TEST a +vm.c:899 slot_truthy slot_is_smi TEST a +vm.c:899 slot_truthy slot_smi_val RAW b +vm.c:915 vm_int_arith slot_from_int PRODUCE c +vm.c:923 vm_bit_operand slot_int_value READ b +vm.c:924 vm_bit_operand num_to_i64_exact READ b +vm.c:924 vm_bit_operand slot_f64_value READ b +vm.c:932 vm_number_compare slot_int_value READ b +vm.c:932 vm_number_compare slot_int_value READ b +vm.c:934 vm_number_compare slot_f64_value READ b +vm.c:935 vm_number_compare slot_f64_value READ b +vm.c:947 vm_iter_index slot_int_value READ b +vm.c:953 vm_iter_index slot_f64_value READ b +vm.c:964 vm_iter_limit slot_int_value READ b +vm.c:1012 make_iter_state make_int PRODUCE c +vm.c:1025 make_iter_state make_int PRODUCE c +vm.c:1250 vm_store_local_slot slot_is_f64 TEST a +vm.c:1254 vm_store_local_slot VAL_FLOAT TEST a +vm.c:1256 vm_store_local_slot SLOT_F64_RAW RAW b +vm.c:1256 vm_store_local_slot VAL_F64_RAW WRITE c +vm.c:1443 jit_helper_local_idx_get buffer_read_f64 READ b +vm.c:1443 jit_helper_local_idx_get slot_from_f64 PRODUCE c +vm.c:1604 jit_helper_dot_set slot_is_f64 TEST a +vm.c:1607 jit_helper_dot_set SLOT_F64_RAW RAW b +vm.c:1674 jit_helper_local_dot_set slot_is_f64 TEST a +vm.c:1675 jit_helper_local_dot_set SLOT_F64_RAW RAW b +vm.c:1725 obs_slot_observable slot_is_f64 TEST i +vm.c:1732 obs_slot_num SLOT_F64_RAW RAW i +vm.c:1732 obs_slot_num slot_is_f64 TEST i +vm.c:1733 obs_slot_num slot_is_smi TEST i +vm.c:1733 obs_slot_num slot_smi_val RAW i +vm.c:1734 obs_slot_num VAL_INT TEST i +vm.c:1735 obs_slot_num VAL_I64_RAW RAW i +vm.c:1904 jit_helper_iter_next buffer_read_f64 READ b +vm.c:1904 jit_helper_iter_next num_guard GUARD c +vm.c:1904 jit_helper_iter_next slot_from_f64 PRODUCE c +vm.c:1912 jit_helper_iter_next slot_is_smi TEST a +vm.c:1915 jit_helper_iter_next list_set_int PRODUCE c +vm.c:1970 vm_leaf_accessor_exec/OP_CONST VAL_F64_RAW RAW b +vm.c:1970 vm_leaf_accessor_exec/OP_CONST slot_from_f64 PRODUCE c +vm.c:1974 vm_leaf_accessor_exec/OP_NUM_ZERO slot_from_f64 PRODUCE c +vm.c:1977 vm_leaf_accessor_exec/OP_NUM_ONE slot_from_f64 PRODUCE c +vm.c:2016 vm_leaf_accessor_exec/OP_INDEX_GET slot_f64_value READ b +vm.c:2031 vm_leaf_accessor_exec/OP_INDEX_GET buffer_read_f64 READ b +vm.c:2031 vm_leaf_accessor_exec/OP_INDEX_GET slot_from_f64 PRODUCE c +vm.c:2042 vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL slot_f64_value READ b +vm.c:2043 vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL slot_f64_value READ b +vm.c:2061 vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL num_guard GUARD c +vm.c:2061 vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL slot_from_f64 PRODUCE c +vm.c:2143 loop_iter_store make_int PRODUCE c +vm.c:2150 loop_iter_store slot_is_smi TEST a +vm.c:2152 loop_iter_store slot_from_int PRODUCE c +vm.c:2157 loop_iter_store make_int PRODUCE c +vm.c:2185 loop_counter_load slot_int_value READ b +vm.c:2193 loop_counter_store slot_is_smi TEST a +vm.c:2194 loop_counter_store slot_from_int PRODUCE c +vm.c:2198 loop_counter_store slot_from_int PRODUCE c +vm.c:2208 eigs_loop_enter_named slot_from_int PRODUCE c +vm.c:2353 vm_value_index_is_int VAL_INT TEST a +vm.c:2354 vm_value_index_is_int VAL_I64_RAW RAW b +vm.c:2359 vm_value_index_is_int VAL_F64_RAW RAW b +vm.c:2367 vm_subscript_index_is_int VAL_INT TEST a +vm.c:2368 vm_subscript_index_is_int VAL_I64_RAW RAW b +vm.c:2374 vm_subscript_index_is_int VAL_F64_RAW RAW b +vm.c:2387 jit_helper_index_set slot_is_f64 TEST a +vm.c:2392 jit_helper_index_set SLOT_F64_RAW RAW b +vm.c:2393 jit_helper_index_set slot_is_f64 TEST a +vm.c:2395 jit_helper_index_set SLOT_F64_RAW RAW b +vm.c:2397 jit_helper_index_set SLOT_F64_RAW RAW b +vm.c:2407 jit_helper_index_set slot_is_f64 TEST a +vm.c:2409 jit_helper_index_set SLOT_F64_RAW RAW b +vm.c:2410 jit_helper_index_set list_rewrite_f64 PRODUCE c +vm.c:2412 jit_helper_index_set SLOT_F64_RAW RAW b +vm.c:2424 jit_helper_index_set val_is_number TEST a +vm.c:2427 jit_helper_index_set eigs_float_arg READ b +vm.c:2434 jit_helper_index_set val_is_number TEST a +vm.c:2437 jit_helper_index_set eigs_float_arg READ b +vm.c:2440 jit_helper_index_set val_is_number TEST a +vm.c:2441 jit_helper_index_set eigs_float_arg READ b +vm.c:2461 jit_helper_index_get slot_is_f64 TEST a +vm.c:2463 jit_helper_index_get SLOT_F64_RAW RAW b +vm.c:2470 jit_helper_index_get SLOT_F64_RAW RAW b +vm.c:2481 jit_helper_index_get buffer_read_f64 READ b +vm.c:2483 jit_helper_index_get slot_from_f64 PRODUCE c +vm.c:2485 jit_helper_index_get SLOT_F64_RAW RAW b +vm.c:2501 jit_helper_index_get val_is_number TEST a +vm.c:2504 jit_helper_index_get eigs_float_arg READ b +vm.c:2516 jit_helper_index_get val_is_number TEST a +vm.c:2519 jit_helper_index_get eigs_float_arg READ b +vm.c:2528 jit_helper_index_get val_is_number TEST a +vm.c:2531 jit_helper_index_get eigs_float_arg READ b +vm.c:2533 jit_helper_index_get buffer_read_f64 READ b +vm.c:2533 jit_helper_index_get make_float PRODUCE c +vm.c:3099 vm_take_error_value make_int PRODUCE c +vm.c:3225 slot_type_name slot_is_f64 TEST e +vm.c:3226 slot_type_name slot_is_smi TEST e +vm.c:3488 vm_run_ex/CASE(CONST) VAL_FLOAT TEST a +vm.c:3489 vm_run_ex/CASE(CONST) VAL_F64_RAW RAW b +vm.c:3489 vm_run_ex/CASE(CONST) slot_from_f64 PRODUCE c +vm.c:3498 vm_run_ex/CASE(INT_ZERO) slot_from_int PRODUCE c +vm.c:3502 vm_run_ex/CASE(INT_ONE) slot_from_int PRODUCE c +vm.c:3508 vm_run_ex/CASE(INT_CONST) VAL_I64_RAW RAW b +vm.c:3509 vm_run_ex/CASE(INT_CONST) slot_from_int PRODUCE c +vm.c:3520 vm_run_ex/CASE(NUM_ZERO) slot_from_f64 PRODUCE c +vm.c:3525 vm_run_ex/CASE(NUM_ONE) slot_from_f64 PRODUCE c +vm.c:3545 #define NUM_REUSE NUM_REUSE TEST a +vm.c:3545 #define NUM_REUSE VAL_FLOAT TEST a +vm.c:3556 #define ARITH_FAST slot_f64_value READ b +vm.c:3556 #define ARITH_FAST slot_f64_value READ b +vm.c:3557 #define ARITH_FAST num_guard GUARD c +vm.c:3562 #define ARITH_FAST NUM_REUSE TEST a +vm.c:3563 #define ARITH_FAST VAL_F64_RAW WRITE c +vm.c:3571 #define ARITH_FAST NUM_REUSE TEST a +vm.c:3572 #define ARITH_FAST VAL_F64_RAW WRITE c +vm.c:3580 #define ARITH_FAST slot_from_f64 PRODUCE c +vm.c:3592 #define INT_ARITH slot_int_value READ b +vm.c:3592 #define INT_ARITH slot_int_value READ b +vm.c:3606 vm_run_ex/CASE(ADD) VAL_FLOAT TEST a +vm.c:3606 vm_run_ex/CASE(ADD) VAL_FLOAT TEST a +vm.c:3607 vm_run_ex/CASE(ADD) VAL_F64_RAW RAW b +vm.c:3607 vm_run_ex/CASE(ADD) VAL_F64_RAW RAW b +vm.c:3607 vm_run_ex/CASE(ADD) num_guard GUARD c +vm.c:3608 vm_run_ex/CASE(ADD) NUM_REUSE TEST a +vm.c:3608 vm_run_ex/CASE(ADD) VAL_F64_RAW WRITE c +vm.c:3609 vm_run_ex/CASE(ADD) NUM_REUSE TEST a +vm.c:3609 vm_run_ex/CASE(ADD) VAL_F64_RAW WRITE c +vm.c:3610 vm_run_ex/CASE(ADD) make_float PRODUCE c +vm.c:3611 vm_run_ex/CASE(ADD) val_is_number TEST a +vm.c:3611 vm_run_ex/CASE(ADD) val_is_number TEST a +vm.c:3612 vm_run_ex/CASE(ADD) eigs_float_arg READ b +vm.c:3612 vm_run_ex/CASE(ADD) eigs_float_arg READ b +vm.c:3612 vm_run_ex/CASE(ADD) make_float PRODUCE c +vm.c:3612 vm_run_ex/CASE(ADD) num_guard GUARD c +vm.c:3662 #define NUM_BINOP VAL_FLOAT TEST a +vm.c:3662 #define NUM_BINOP VAL_FLOAT TEST a +vm.c:3663 #define NUM_BINOP VAL_F64_RAW RAW b +vm.c:3663 #define NUM_BINOP VAL_F64_RAW RAW b +vm.c:3663 #define NUM_BINOP num_guard GUARD c +vm.c:3664 #define NUM_BINOP VAL_F64_RAW RAW b +vm.c:3664 #define NUM_BINOP VAL_F64_RAW RAW b +vm.c:3666 #define NUM_BINOP NUM_REUSE TEST a +vm.c:3666 #define NUM_BINOP VAL_F64_RAW WRITE c +vm.c:3667 #define NUM_BINOP NUM_REUSE TEST a +vm.c:3667 #define NUM_BINOP VAL_F64_RAW WRITE c +vm.c:3668 #define NUM_BINOP make_float PRODUCE c +vm.c:3669 #define NUM_BINOP val_is_number TEST a +vm.c:3669 #define NUM_BINOP val_is_number TEST a +vm.c:3670 #define NUM_BINOP eigs_float_arg READ b +vm.c:3670 #define NUM_BINOP eigs_float_arg READ b +vm.c:3671 #define NUM_BINOP num_guard GUARD c +vm.c:3674 #define NUM_BINOP make_float PRODUCE c +vm.c:3678 #define NUM_BINOP make_float PRODUCE c +vm.c:3696 vm_run_ex/CASE(DIV) val_is_number TEST a +vm.c:3696 vm_run_ex/CASE(DIV) val_is_number TEST a +vm.c:3697 vm_run_ex/CASE(DIV) eigs_float_arg READ b +vm.c:3699 vm_run_ex/CASE(DIV) make_float PRODUCE c +vm.c:3701 vm_run_ex/CASE(DIV) eigs_float_arg READ b +vm.c:3701 vm_run_ex/CASE(DIV) eigs_float_arg READ b +vm.c:3701 vm_run_ex/CASE(DIV) num_guard GUARD c +vm.c:3704 vm_run_ex/CASE(DIV) eigs_float_arg READ b +vm.c:3706 vm_run_ex/CASE(DIV) NUM_REUSE TEST a +vm.c:3706 vm_run_ex/CASE(DIV) VAL_F64_RAW WRITE c +vm.c:3707 vm_run_ex/CASE(DIV) NUM_REUSE TEST a +vm.c:3707 vm_run_ex/CASE(DIV) VAL_F64_RAW WRITE c +vm.c:3708 vm_run_ex/CASE(DIV) make_float PRODUCE c +vm.c:3713 vm_run_ex/CASE(DIV) make_float PRODUCE c +vm.c:3721 vm_run_ex/CASE(IDIV) val_is_number TEST a +vm.c:3721 vm_run_ex/CASE(IDIV) val_is_number TEST a +vm.c:3722 vm_run_ex/CASE(IDIV) VAL_INT TEST a +vm.c:3722 vm_run_ex/CASE(IDIV) VAL_INT TEST a +vm.c:3723 vm_run_ex/CASE(IDIV) VAL_I64_RAW RAW b +vm.c:3723 vm_run_ex/CASE(IDIV) VAL_I64_RAW RAW b +vm.c:3726 vm_run_ex/CASE(IDIV) make_int PRODUCE c +vm.c:3729 vm_run_ex/CASE(IDIV) make_int PRODUCE c +vm.c:3735 vm_run_ex/CASE(IDIV) make_int PRODUCE c +vm.c:3738 vm_run_ex/CASE(IDIV) eigs_float_arg READ b +vm.c:3743 vm_run_ex/CASE(IDIV) make_float PRODUCE c +vm.c:3748 vm_run_ex/CASE(IDIV) eigs_float_arg READ b +vm.c:3760 vm_run_ex/CASE(IDIV) make_float PRODUCE c +vm.c:3760 vm_run_ex/CASE(IDIV) num_guard GUARD c +vm.c:3766 vm_run_ex/CASE(IDIV) make_int PRODUCE c +vm.c:3776 vm_run_ex/CASE(MOD) val_is_number TEST a +vm.c:3776 vm_run_ex/CASE(MOD) val_is_number TEST a +vm.c:3777 vm_run_ex/CASE(MOD) VAL_INT TEST a +vm.c:3777 vm_run_ex/CASE(MOD) VAL_INT TEST a +vm.c:3778 vm_run_ex/CASE(MOD) VAL_I64_RAW RAW b +vm.c:3778 vm_run_ex/CASE(MOD) VAL_I64_RAW RAW b +vm.c:3781 vm_run_ex/CASE(MOD) make_int PRODUCE c +vm.c:3787 vm_run_ex/CASE(MOD) make_int PRODUCE c +vm.c:3790 vm_run_ex/CASE(MOD) eigs_float_arg READ b +vm.c:3793 vm_run_ex/CASE(MOD) make_float PRODUCE c +vm.c:3795 vm_run_ex/CASE(MOD) eigs_float_arg READ b +vm.c:3795 vm_run_ex/CASE(MOD) make_float PRODUCE c +vm.c:3795 vm_run_ex/CASE(MOD) num_guard GUARD c +vm.c:3801 vm_run_ex/CASE(MOD) make_float PRODUCE c +vm.c:3817 #define INT_BINOP_R slot_from_int PRODUCE c +vm.c:3823 #define INT_BINOP_R slot_f64_value READ b +vm.c:3843 vm_run_ex/CASE(NEG) slot_f64_value READ b +vm.c:3846 vm_run_ex/CASE(NEG) NUM_REUSE TEST a +vm.c:3846 vm_run_ex/CASE(NEG) VAL_F64_RAW WRITE c +vm.c:3849 vm_run_ex/CASE(NEG) slot_from_f64 PRODUCE c +vm.c:3853 vm_run_ex/CASE(NEG) slot_int_value READ b +vm.c:3858 vm_run_ex/CASE(NEG) slot_from_int PRODUCE c +vm.c:3863 vm_run_ex/CASE(NEG) slot_from_f64 PRODUCE c +vm.c:3880 vm_run_ex/CASE(BNOT) slot_from_int PRODUCE c +vm.c:3885 vm_run_ex/CASE(BNOT) slot_f64_value READ b +vm.c:3898 vm_run_ex/CASE(EQ) slot_is_smi TEST f +vm.c:3898 vm_run_ex/CASE(EQ) slot_is_smi TEST f +vm.c:3904 vm_run_ex/CASE(EQ) slot_f64_value READ f +vm.c:3904 vm_run_ex/CASE(EQ) slot_f64_value READ f +vm.c:3924 vm_run_ex/CASE(NE) slot_is_smi TEST f +vm.c:3924 vm_run_ex/CASE(NE) slot_is_smi TEST f +vm.c:3930 vm_run_ex/CASE(NE) slot_f64_value READ f +vm.c:3930 vm_run_ex/CASE(NE) slot_f64_value READ f +vm.c:3952 #define NUM_CMP slot_f64_value READ f +vm.c:3952 #define NUM_CMP slot_f64_value READ f +vm.c:4903 vm_run_ex/CASE(INDEX_GET) slot_is_f64 TEST a +vm.c:4905 vm_run_ex/CASE(INDEX_GET) SLOT_F64_RAW RAW b +vm.c:4912 vm_run_ex/CASE(INDEX_GET) SLOT_F64_RAW RAW b +vm.c:4922 vm_run_ex/CASE(INDEX_GET) buffer_read_f64 READ b +vm.c:4924 vm_run_ex/CASE(INDEX_GET) slot_from_f64 PRODUCE c +vm.c:4926 vm_run_ex/CASE(INDEX_GET) SLOT_F64_RAW RAW b +vm.c:4948 vm_run_ex/CASE(INDEX_SET) slot_is_f64 TEST a +vm.c:4950 vm_run_ex/CASE(INDEX_SET) SLOT_F64_RAW RAW b +vm.c:4951 vm_run_ex/CASE(INDEX_SET) slot_is_f64 TEST a +vm.c:4953 vm_run_ex/CASE(INDEX_SET) SLOT_F64_RAW RAW b +vm.c:4955 vm_run_ex/CASE(INDEX_SET) SLOT_F64_RAW RAW b +vm.c:4965 vm_run_ex/CASE(INDEX_SET) slot_is_f64 TEST a +vm.c:4967 vm_run_ex/CASE(INDEX_SET) SLOT_F64_RAW RAW b +vm.c:4967 vm_run_ex/CASE(INDEX_SET) list_set_f64 PRODUCE c +vm.c:4969 vm_run_ex/CASE(INDEX_SET) SLOT_F64_RAW RAW b +vm.c:4981 vm_run_ex/CASE(INDEX_SET) val_is_number TEST a +vm.c:4984 vm_run_ex/CASE(INDEX_SET) eigs_float_arg READ b +vm.c:4991 vm_run_ex/CASE(INDEX_SET) val_is_number TEST a +vm.c:4994 vm_run_ex/CASE(INDEX_SET) eigs_float_arg READ b +vm.c:4997 vm_run_ex/CASE(INDEX_SET) val_is_number TEST a +vm.c:4998 vm_run_ex/CASE(INDEX_SET) eigs_float_arg READ b +vm.c:5055 vm_run_ex/CASE(DOT_SET) slot_is_f64 TEST a +vm.c:5058 vm_run_ex/CASE(DOT_SET) SLOT_F64_RAW RAW b +vm.c:5119 vm_run_ex/CASE(LOCAL_DOT_SET) slot_is_f64 TEST a +vm.c:5120 vm_run_ex/CASE(LOCAL_DOT_SET) SLOT_F64_RAW RAW b +vm.c:5146 vm_run_ex/CASE(LOCAL_IDX_GET) buffer_read_f64 READ b +vm.c:5146 vm_run_ex/CASE(LOCAL_IDX_GET) slot_from_f64 PRODUCE c +vm.c:5252 vm_run_ex/CASE(LOCAL_IDX_DOT_SET) slot_is_f64 TEST a +vm.c:5253 vm_run_ex/CASE(LOCAL_IDX_DOT_SET) SLOT_F64_RAW RAW b +vm.c:5325 vm_run_ex/CASE(ITER_NEXT) slot_is_smi TEST a +vm.c:5328 vm_run_ex/CASE(ITER_NEXT) list_set_int PRODUCE c +vm.c:5332 vm_run_ex/CASE(ITER_NEXT) buffer_read_f64 READ b +vm.c:5332 vm_run_ex/CASE(ITER_NEXT) slot_from_f64 PRODUCE c +vm.c:5672 vm_run_ex/CASE(OBSERVE_VALUE_SLOT) make_float PRODUCE i +vm.c:5673 vm_run_ex/CASE(OBSERVE_VALUE_SLOT) make_float PRODUCE i +vm.c:5674 vm_run_ex/CASE(OBSERVE_VALUE_SLOT) make_float PRODUCE i +vm.c:5708 vm_run_ex/CASE(OBSERVE_VALUE_NAME) make_float PRODUCE i +vm.c:5709 vm_run_ex/CASE(OBSERVE_VALUE_NAME) make_float PRODUCE i +vm.c:5710 vm_run_ex/CASE(OBSERVE_VALUE_NAME) make_float PRODUCE i +vm.c:5774 vm_run_ex/CASE(INTERROGATE) VAL_INT TEST i +vm.c:5775 vm_run_ex/CASE(INTERROGATE) VAL_F64_RAW RAW i +vm.c:5775 vm_run_ex/CASE(INTERROGATE) VAL_FLOAT TEST i +vm.c:5775 vm_run_ex/CASE(INTERROGATE) make_float PRODUCE i +vm.c:5779 vm_run_ex/CASE(INTERROGATE) make_int PRODUCE i +vm.c:5780 vm_run_ex/CASE(INTERROGATE) make_int PRODUCE i +vm.c:5781 vm_run_ex/CASE(INTERROGATE) make_int PRODUCE i +vm.c:5782 vm_run_ex/CASE(INTERROGATE) make_float PRODUCE i +vm.c:5786 vm_run_ex/CASE(INTERROGATE) val_is_number TEST i +vm.c:5800 vm_run_ex/CASE(INTERROGATE) make_int PRODUCE i +vm.c:5801 vm_run_ex/CASE(INTERROGATE) make_float PRODUCE i +vm.c:5802 vm_run_ex/CASE(INTERROGATE) make_float PRODUCE i +vm.c:5803 vm_run_ex/CASE(INTERROGATE) make_float PRODUCE i +vm.c:5823 vm_run_ex/CASE(INTERROGATE_NAMED) make_int PRODUCE i +vm.c:5849 vm_run_ex/CASE(INTERROGATE_NAMED) make_float PRODUCE i +vm.c:5852 vm_run_ex/CASE(INTERROGATE_NAMED) make_float PRODUCE i +vm.c:5853 vm_run_ex/CASE(INTERROGATE_NAMED) make_float PRODUCE i +vm.c:5856 vm_run_ex/CASE(INTERROGATE_NAMED) make_float PRODUCE i +vm.c:5872 vm_run_ex/CASE(INTERROGATE_NAMED_AT) val_is_number TEST i +vm.c:5880 vm_run_ex/CASE(INTERROGATE_NAMED_AT) VAL_INT TEST i +vm.c:5883 vm_run_ex/CASE(INTERROGATE_NAMED_AT) VAL_I64_RAW RAW i +vm.c:5888 vm_run_ex/CASE(INTERROGATE_NAMED_AT) VAL_F64_RAW RAW i +vm.c:5908 vm_run_ex/CASE(INTERROGATE_NAMED_WHEN) val_is_number TEST i +vm.c:5909 vm_run_ex/CASE(INTERROGATE_NAMED_WHEN) VAL_INT TEST i +vm.c:5910 vm_run_ex/CASE(INTERROGATE_NAMED_WHEN) VAL_I64_RAW RAW i +vm.c:5911 vm_run_ex/CASE(INTERROGATE_NAMED_WHEN) VAL_F64_RAW RAW i +vm.c:6041 vm_run_ex/CASE(SLICE_GET) VAL_FLOAT CASE a +vm.c:6041 vm_run_ex/CASE(SLICE_GET) VAL_INT CASE a +vm.c:6058 #define READ_SLICE_BOUND slot_is_f64 TEST a +vm.c:6059 #define READ_SLICE_BOUND SLOT_F64_RAW RAW b +vm.c:6061 #define READ_SLICE_BOUND SLOT_F64_RAW RAW b +vm.c:6068 #define READ_SLICE_BOUND VAL_INT TEST a +vm.c:6072 #define READ_SLICE_BOUND VAL_I64_RAW RAW b +vm.c:6075 #define READ_SLICE_BOUND VAL_FLOAT TEST a +vm.c:6076 #define READ_SLICE_BOUND VAL_F64_RAW RAW b +vm.c:6079 #define READ_SLICE_BOUND VAL_F64_RAW RAW b +vm.c:6090 #define READ_SLICE_BOUND slot_is_smi TEST a +vm.c:6093 #define READ_SLICE_BOUND slot_smi_val RAW b +vm.c:6790 vm_run_ex/CASE(DISPATCH) VAL_FLOAT TEST a +vm.c:6790 vm_run_ex/CASE(DISPATCH) VAL_INT TEST a +vm.c:6796 vm_run_ex/CASE(DISPATCH) eigs_slot_number READ b +vm.c:6801 vm_run_ex/CASE(DISPATCH) num_to_i64_exact READ b +vm.h:841 vm_index_is_int num_to_i64_exact READ b diff --git a/docs/design/phase2-numbers.md b/docs/design/phase2-numbers.md new file mode 100644 index 000000000..6d70f75cf --- /dev/null +++ b/docs/design/phase2-numbers.md @@ -0,0 +1,3265 @@ +# Phase 2 numbers (#1758): design (round 0, revised in design rounds 2 and 3) + +Status: design round 3, with R1.1/R1.2 implementation notes and R1.2 +fix-round verification in §12. The first program-visible int producer +remains R1.3. Inventory and prototype measurements below describe the +original design tree. Spec: issue #1758 (Revision 1), its owner decisions (A), (B), (C), the +Q1-Q6 decisions +([comment](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6171744498)) +and the Q7-Q8 decisions +([comment](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6174302316)). +Evidence base: issue #1746 and the `exp-ieee-p1` branch (canonical NaN, the +single slot writer, the JIT bailing on NaN, refusal inside writers). + +Tree measured: `phase2-numbers` at `0740af03` (main, v0.46.0-8). The tag the +Done-when means is `v0.46.0` = `4a3a5c19`. + +Companion artifacts: +- `tools/phase2_inventory.py`: the site inventory, the executor matrix, the + slot-classifier derivation, and the builtin-table check. It writes + `docs/design/phase2-inventory.txt`. +- `tests/phase2/MAP.md`: every N, O and T item mapped to planned rows, with + row shapes and tier witnesses. + +**What design round 2 changed** (after the blind critic's FAIL on round 0): +- The third numeric evaluator, `vm_leaf_accessor_exec` (vm.c:1856, scanner + chunk.c:1072, #366), is in the inventory (§1), the JIT/leaf section (§4.5) + and the map (§7), with every arm, and it gets its own gate (§9.2). +- Every meaning change now coincides with a token change (§2.7). Int + literals get new opcodes; `OP_NUM_ZERO`/`OP_NUM_ONE` keep their float + meaning; `slot_as_double` is deleted in R1.1. +- The compile-forced count is re-measured with clang's `unavailable` + attribute under three configurations (§2.3a), and every site that does + not stop compiling is classified. +- The slot classifiers are derived, not listed (§2.5), and raw union-member + reads are in the vocabulary. +- The canonical-form invariant is corrected (§2.1): the lifts box on purpose. +- The builtin table follows the decided Q1 (§6), with the LOSS rows #1762 + found; §11 records Q1-Q6 as decided. +- The test plan has row shapes and tier witnesses (§9). + +**What design round 3 changed** (after the critic's FAIL on round 2): +- The `osr` witness proves the loop BODY ran natively: every OSR row is a + `loop while true:` whose only exit is a `return` right after the claimed + ops, and the witness requires the OSR record to reach the return sentinel + (§9.1). Every witness kind now has a negative plant, calibrated on main + (§9.2); the old form passed a plant with 0 native `INDEX_SET` calls. +- R1.2 is behaviour-preserving and stays so: the builtin readers and + producers are a named BRIDGE that R1.6 deletes, so every builtin behaviour + change coincides with a token change, and a per-builtin behaviour table + gates R1.6 (§2.7, §8, §9.4). The first int value appears at R1.3, and + §8 lists every consumer converted in that commit. +- `phase2_check.sh --through Rk` checks a round against exactly the rows that + have landed; R1.1 and R1.2 are mechanical rounds with named gates (§9.2). +- The observer's boxed-int path is mapped and routed (§7, MAP + `O1.int-observed-numeric`). +- `buffer_read_num` is renamed in R1.6, so the index reads (`gather`, + `scatter_add`) and the byte reads (`buf_from_pcm16le` and the byte + consumers) are re-decided there; the compile count is re-measured under the + Makefile's exact flags and attributed per commit (§2.3a). +- Q7 and Q8 are decided and folded in: a validated `q7` column in the builtin + table, a geometry reader that clips the whole shape, and IEEE `totalOrder` + for sorting with NaN propagating through `min`/`max` (§2.6, §6, §11). + +Contents: +1. The inventory +2. Representation +3. Measured prototype +4. The JIT and the leaf evaluator +5. N12 and SMI share the NaN space +6. Builtin classification +7. Semantics-to-sites map +8. Round plan +9. Test plan +10. Lua differential tool +11. Decided questions (Q1-Q8) +12. R1.2 verification notes + +--- + +## 1. The inventory + +`python3 tools/phase2_inventory.py` prints `examined=1798 classified=1798 +unclassified=0` on this tree and writes every site to +`docs/design/phase2-inventory.txt` (file:line, enclosing function, token, +syntactic kind, category). + +**Population.** A site is one occurrence, outside comments and string +literals, of a token of the value model's numeric vocabulary in `src/*.c` and +`src/*.h`. The vocabulary is: +- the type tag `VAL_NUM` and the slot predicates; +- the raw and checked payload accessors and the constructors; +- the raw union members themselves: `.d_` / `->d_` (the slot's double) and + `.num_` / `->num_` (the Value's double). Inside their three accessor + macros (`SLOT_NUM_RAW`, `VAL_NUM_RAW`, `VAL_NUM_OFFSET`) they are RAW + sites; anywhere else they are a `BYPASS`, which the script leaves + unclassified, so the run fails. This closes the blind spot the critic's + plant `e_blind_member` (`static double p2_plant_blind(EigsSlot s) { + return s.d_; }` appended to vm.c) showed: the round-0 oracle stayed green + on it. The repo's own `tools/num_read_check.sh` (#1637) already refuses + such a bypass in CI; the inventory now sees it too; +- the clamp (`num_guard`) and the number formatter; +- the double parsers (`strtod`, `atof`); +- the AST literal fields (`AST_NUM`, `data.num`, `num_val`); +- in the JIT files only, the tag-range immediates (`0xFFF8`..`0xFFFF`) and the + emitters that assume a double (`emit_load_numeric_operand`, the `*sd` + arithmetic, `cvt*`, `fmod`, ...). The JIT's guards compare raw tag bits and + name no vocabulary token, so without these the JIT category would hold 40 + sites instead of 127. + +**Attribution.** Each site carries its enclosing function. Inside the five +opcode dispatchers it also carries its arm: +- `vm_run_ex` (the interpreter): `CASE(X)`. A macro whose body spells + `CASE()` generates an arm at each invocation, closed + over chains (`INT_BINOP` -> `INT_BINOP_R` -> `CASE(NAME)`), so + `NUM_BINOP(SUB, ...)` and `NUM_CMP(LT, ...)` are interpreter arms too; +- `jit_supported_prefix` and `jit_compile_to_thunk` (the JIT scanner and + emitter): the `if (op == OP_X || ...)` chain, named by its first opcode; +- `chunk_scan_leaf_accessor` and `vm_leaf_accessor_exec` (the leaf scanner + and evaluator): the `case` labels at the dispatch switch's own depth, + grouped labels joined (`OP_ADD|OP_SUB|OP_MUL`). + +**The key and its blind spot.** The key is the token, so code that handles a +number without naming one is invisible. Four derived checks bound that, and +each can fail the run: +1. Every function that returns or writes out a `double` and whose body + performs a payload read is a numeric accessor. It must be in the + vocabulary (its call sites then count as sites) or in a reviewed + `WRAPPERS` list with a reason. 17 such functions exist. 11 are in the + vocabulary and 6 are wrappers, for example `tensor_to_flat`, whose + per-element read is the site. +2. Every `VAL_NUM`/`AST_NUM` must sit in a recognized syntactic position: + test, case, tag store, type yield, data, or emitter immediate. An + unrecognized position (for example `1u << VAL_NUM`) is unclassified. +3. **The executor matrix** (`## Executors` in the inventory file). For every + opcode, the roles that implement it: `interp`, `jit_scan`, `jit_emit`, + `leaf_scan`, `leaf_exec`. The leaf scanner and the leaf evaluator must + admit exactly the same opcodes, in both directions (a scanner arm with no + evaluator arm, or the reverse, fails the run). Today both hold the same + 11: `LINE`, `CONST`, `NUM_ZERO`, `NUM_ONE`, `GET_LOCAL`, `LOCAL_DOT_GET`, + `INDEX_GET`, `ADD`, `SUB`, `MUL`, `RETURN`. So `OP_NUM_ZERO` and + `OP_NUM_ONE` have three executors (interpreter vm.c:3257, JIT emitter + jit.c:2797, leaf evaluator vm.c:1872) and two scanners; the round-0 draft + named two executors. +4. **The slot classifiers** (`## Slot classifiers`). Every function, or + dispatcher arm, that tests, dereferences or types an `EigsSlot`, by the + tokens it names: + - D1: it names a number test (`slot_is_num`, `slot_is_numeric`, + `slot_as_double`). All three are renamed or deleted, so the compiler + forces every D1 entry (58 today); + - D2: it dereferences (`slot_as_ptr`, `slot_as_heap`, `eigs_slot_ref`) + without a number test (20); + - D3: it tests other tags or raw tag bits (23); + - T: it only reads a slot's `ValType` (`slot_type`, `list_elem_type`, + `dict_value_type`), 67. A comparison with `VAL_NUM` is compile-forced; + a comparison with another enumerator is unaffected by ints; a switch is + forced by `-Werror=switch`. + + Every D2 and D3 entry needs a reviewed row in §2.5's table, with a + disposition from a fixed set. A missing row and a stale row each fail the + run. The three classifiers the critic found outside round 0's rule + (`eigs_slot_ref`, `obs_slot_observable`, `slot_type_name`) are D2/D3 + entries of this derivation. + +Also outside the population: +- `src/freestanding/` (`mini_strtod.c`, `mini_fmt.c`), which N1, N6 and N10 + must also reach for the EigenOS profile; the round plan names it; +- code that computes on a double after a counted read extracted it. + +**Categories** (one per site; the first matching rule wins). Role rules, +keyed by file or function, come first; otherwise the token's own kind +decides (a)/(b)/(c)/(e). + +| | category | sites | kinds | +|---|---|---:|---| +| a | type tests | 384 | TEST 327, CASE 52, DATA 3, YIELD 2 | +| b | payload reads | 530 | READ 414, RAW 116 | +| c | producers | 472 | PRODUCE 373, GUARD 71, WRITE 22, PARSE 3, TAGSET 3 | +| d | JIT emitters and guards | 127 | EMIT 89, RAW 27, PRODUCE 6, TEST 2, GUARD 1, TAGSET 1, WRITE 1 | +| e | number formatting | 14 | | +| f | equality, ordering and hashing | 27 | | +| g | tape encode and decode | 20 | | +| h | JSON, store and db encoders | 70 | | +| i | observer numeric paths | 90 | | +| j | embed API | 64 | | +| | **total** | **1798** | | + +`WRITE` is `VAL_NUM_RAW(v) = x`, the NUM_REUSE in-place writes the brief +names as producers. Design round 2 added 25 sites: the raw members inside +their three accessor macros (`RAW`, 3) and `buffer_read_byte`, the byte +reader Q1 changes (`READ`, 22). + +Largest files: builtins.c 319, vm.c 304, ext_gfx.c 250, builtins_tensor.c +184, builtins_buf.c 97, jit.c 96. The per-file and per-token tables are in +the inventory file. + +**The brief's headline figures, re-measured.** The brief says ~420 `VAL_NUM`, +366 `make_num` and 274 argument reads. Outside comments and literals this +tree has: +- `VAL_NUM` 329, plus `VAL_NUM_RAW` 92; +- `make_num` 368; +- argument and element reads 357 (`eigs_num_arg` 145, `eigs_list_num` 130, + `eigs_slot_num` 52, the optional and element forms 30). + +**Builtins.** The registration table is derived from the +`env_set_local_owned(env, "name", make_builtin(fn))` lines plus the +`ext_names.h` X-lists. It holds 388 names after S10-D2's `proc_read_buf` +retirement (389 after R1.6g, 312 after sync #5). The initial 311-name population was +calibrated against the runtime's own `eigenscript --api --json` +(223 core + 88 extension = 311; equal in both directions). A builtin is numeric when a site is contained in +its function, in a same-file function it calls or names as a value (a +comparator, an op-table entry; closure depth 4), or in the shared formatter +or equality it calls. The current population is 309 numeric builtins (R1.6g N9, minus S10-D2's explicit `proc_read_buf` retirement), and §6 +classifies every one. (The closure does not follow a call into another +file, so a header helper that reads a number must itself be in the +vocabulary for its callers to count: `buffer_read_byte` entered the +vocabulary this round, which made `ppu_render_frame` numeric. A one-off scan +of every header inline and every exported function that reads a number and +returns something other than a double found no other such reader: the rest +return a status (`jit_helper_*`, the observer), a slot (`slot_from_value`, +the container writers) or a truth value (`is_truthy`, +`slot_truthy_immediate`).) The script fails if a numeric builtin has no +row, a row names no numeric builtin, a class or loss mark is not one of the +allowed tokens, or the historical `LOSS` scope differs in either direction. +`make_int` is a producer site too: an int-only builtin remains in the numeric +population after its double bridge is removed. + +R1.6b aligns the `LOSS` check with §6's historical definition. The fixed set +is generated from the approved pre-migration table at +`0a73adb0612f8612d75393891ae2ba2e1c803663` (document blob +`b2f583afd653e845887045795077823077388442`), and is kept in the inventory +script so archive-based checks need no live git repository. R1.6b-D3 +(#1758 comment 6320170738) corrects the omitted historical `http_post` +header loss, adding it to that approved scope. Both adding a +mark outside that scope and removing one inside it fail. A successful +migration therefore need not retain a lossy double path merely to satisfy +the inventory; current builtin membership and classification checks remain. + +**Calibration: every check turns red on its plant.** Each plant runs +against a scratch copy of `src/` and of this document (`--src`, `--doc`, +`--no-write`), never the worktree; the unplanted copy passes first. The +recorded round-0 calibration killed all 20 plants below, each by its own +FAIL line. R1.6b replaces G's then-current double-path predicate with the +historical-scope predicate described above; its one-time calibration removes +the `make_int` vocabulary entry and adds/removes historical `LOSS` marks. + +| plant | mutation | the red line | +|---|---|---| +| A | `1u << VAL_NUM` appended to vm.c | `UNCLASSIFIED: ... VAL_NUM (VAL_NUM/AST_NUM in an unrecognized position)` | +| B | a new function returning `VAL_NUM_RAW(v)` | `UNCLASSIFIED: ... p2_plant_read (reads a payload and returns/writes a double; not in VOCAB or WRAPPERS)` | +| C | a registered numeric builtin with no row | ``numeric builtin `p2plant` ... has no classification row`` | +| D | class `integer` | `` `abs` has unknown class 'integer' `` | +| E | loss mark `maybe` | `` `type` has unknown loss mark 'maybe' `` | +| F | a row for a non-numeric builtin | ``classification row `keys` names no numeric registered builtin`` | +| G | `LOSS` on a builtin with no double path | `` `type` is marked LOSS but its closure has no double-path token `` | +| H | a row without backticks | `unparsed table line: \| type \| other ...` | +| I | the JIT files removed | `role rule 0 (d ...) matched no site` | +| J | an empty source tree | `examined=0 classified=0` (fails) | +| K | a VOCAB token that never occurs | `VOCAB token never occurs: p2_never_token` | +| L | the `abs` row deleted | ``numeric builtin `abs` ... has no classification row`` | +| M | the critic's `e_blind_member`: `static double p2_plant_blind(EigsSlot s) { return s.d_; }` | `UNCLASSIFIED: vm.c:6733 p2_plant_blind d_ (raw union member outside its accessor macro)` | +| N | the leaf scanner admits `OP_DIV` | `leaf scanner admits OP_DIV but the leaf evaluator has no arm` | +| O | a new function returning `slot_as_ptr(s)` | ``slot classifier `vm.c:p2_plant_deref` has no disposition row`` | +| P | a disposition row for a function that does not exist | ``disposition row `vm.c:p2_gone` names no D2/D3 slot classifier`` | +| Q | disposition `fine` | `` `vm.c:slot_type_name` has unknown disposition 'fine' `` | +| R | `vm_leaf_accessor_exec` renamed | `dispatcher vm_leaf_accessor_exec (leaf_exec) attributed no opcode arm` | +| S | `gfx_clear`'s `q7` column set to `size` | `` `gfx_clear` has a bad Q7 column 'size' (want `-` or an ordered subset of geometry,bounded,magnitude) `` | +| T | `gfx_clear`'s `q7` column set to `-` (its note says continuous) | `` `gfx_clear`: Q7 column '-' disagrees with its note (a continuous parameter must be named in both) `` | + +M is the critic's plant verbatim; on the round-0 oracle it stayed green. (On +its first run the plant driver reported T as SURVIVED although the gate +exited 1 with the right FAIL line: the driver's expected-message pattern +did not allow for the quotes around `-`. The pattern was fixed and T +re-run; the gate was never green on it.) + +--- + +## 2. Representation + +### 2.1 What every alternative shares: the slot + +`EigsSlot` stays a NaN-boxed 64-bit word (`src/value_slot.h`). + +| bits (high 16) | meaning | +|---|---| +| `< 0xFFF8` | a double (`slot_is_num` today) | +| `0xFFF8` | null | +| `0xFFF9` | bool | +| `0xFFFA` | **SMI: an int in [-2^47, 2^47), 48-bit two's complement payload** (reserved today, unused) | +| `0xFFFB` | heap pointer; **an int outside the SMI range is a heap-boxed int64 here** | +| `0xFFFC`-`0xFFFE` | reserved | +| `0xFFFF` | sentinel | + +- SMI encode: `s.u = 0xFFFA000000000000 | ((uint64_t)i & 0x0000FFFFFFFFFFFF)`. +- SMI decode, sign-extending bit 47 portably: + `(int64_t)((s.u & PAYLOAD) ^ 0x800000000000) - 0x800000000000`. + The `(int64_t)(u << 16) >> 16` form relies on implementation-defined right + shifts. +- Adding or subtracting two SMIs cannot overflow int64 (|a ± b| < 2^48), so + round 1 computes those in int64 and range-checks the result into SMI or + boxed. Multiplication can overflow int64 (|a·b| < 2^94), so it always uses + `__builtin_mul_overflow`, as do boxed operands for every operator. An int64 + overflow raises `value` ((A)). + +**Canonical form (all alternatives), corrected in design round 2.** The +round-0 draft said an int in [-2^47, 2^47) is never boxed in any slot, with +`slot_from_heap` normalizing a small box. That cannot hold: the VM boxes +numbers on purpose today, and must keep doing it for ints. +- `vm_slot_lift` (vm.c:826, the store at 834) and `vm_local_lift` (vm.c:717, + store at 723) materialize an immediate into a heap Value and **overwrite + the slot with it**, because the caller keeps using that pointer. + `env_get_hashed` and `env_get_local_hashed` (eigenscript.c:4986-5026) do + the same through `slot_to_value` plus `slot_from_heap`. +- `vm_push` goes through `slot_bridge_wrap` (vm.c:768), which by contract + "NEVER emits an immediate": a builtin's boxed result stays a heap slot. +- So a boxed number on the stack or in an env slot is a legal state today + (the JIT's operand loader already accepts a heap `VAL_NUM` with refcount + >= 2, jit.c:2343). A boxed small int is the same state. + +The invariant that does hold, and that round 1 keeps: +- **Container storage is canonical.** A list item or dict value is an + immediate number, null, bool, or a heap pointer to a Value that is not a + number (container.h:4-16). After round 1: a small int is stored as an + SMI, and a `VAL_INT` box only for an int outside [-2^47, 2^47). It is + enforced inside the four container writers that already unbox a + `VAL_NUM` (`eigs_container_slot`, `eigs_slot_adopt`, + `eigs_slot_store_owned`, `eigs_slot_store_borrow`), not beside their + callers (the #1746 lesson). +- **The int writers produce the SMI.** `slot_from_int(int64_t)` (new, the + only int constructor) and `slot_from_value` (an owned Value*, which + already releases a `VAL_NUM` box into an immediate, eigenscript.c:1862) + produce an SMI for a small int and a box outside the range. +- **Everything that compares compares by value whenever a box is + involved.** The only bit-level fast path is "both operands SMI" (equal + bits iff equal ints). An SMI against a box, or two boxes, compares the + int64 values; an int against a float uses N2's exact rule. + `values_equal_impl`, the EQ/NE/compare arms, the sort comparators and + every hash of a number follow this. + +Consequences: +- A writer that skips the small-int rule produces a box where an SMI was + due. No program can see that: equality, ordering, printing and the tape + all go by value. The only witness is the C slot test + (`tests/phase2/slot_layer.c`, §8 R1.1), which drives each writer above + with every boundary value (±2^47, ±(2^47 - 1), 0, INT64_MIN, INT64_MAX) + and asserts the resulting tag. That is plant 1's named check (§9.3); the + round-0 claim that a program row (`N1.canon-eq`) would see it was wrong. +- The JIT `OP_CONST` emitter never meets an int: int literals use new + opcodes (§2.7, §4.2). + +### 2.2 The four alternatives + +**Alt 1: `VAL_INT` beside `VAL_NUM`.** +- Value: a new `ValType VAL_INT` with an `int64_t` payload, appended after + `VAL_BOOL`. `VAL_NUM` keeps meaning a double. +- Slot: SMI plus boxed `VAL_INT`. +- `type of` still answers `"num"` for both ((B)): `val_type_name(VAL_INT)` + returns `"num"`. `num_type of x` reads the tag. + +**Alt 1R (chosen): Alt 1, and the double vocabulary renamed, the ambiguous +accessors deleted.** +- `VAL_NUM` becomes `VAL_FLOAT`, `num_` becomes `f64_`, `VAL_NUM_RAW` and + `SLOT_NUM_RAW` become `*_F64_RAW`, `slot_is_num` becomes `slot_is_f64`, + `make_num` becomes `make_float`, `slot_from_num` becomes `slot_from_f64`. +- `eigs_num_arg`, `eigs_list_num`, `eigs_opt_num` and the slot forms are + deleted. Replacements state intent: + - `eigs_float_arg` converts an int to a double. It is legal only for + float-producing builtins. + - `eigs_int_arg` applies N5's exact rule and raises otherwise. + - `eigs_number_arg` returns `EigsNum { kind, i, f }` for int-preserving + builtins. + - `make_int`. +- Design round 2 extends the list (measured in §2.3a): the AST and token + literal payloads, `slot_as_double` (deleted), the container f64 helpers, + `make_num_permanent`, `buf_num_arg`, `finite_num_to_byte` and + `buffer_read_byte`. +- This is the repo's own precedent: #1637 round 4 renamed the union member to + `num_` "so no unchecked read compiles" (eigenscript.h:281). + +**Alt 2: one `VAL_NUM` with an int/float subtype (Lua 5.4's `TValue` +model).** +- Value: `VAL_NUM` plus a `numkind` field. The payload union holds `num_` + (double) or `i64_`. +- Slot: SMI plus boxed `VAL_NUM`/INT. +- (B) falls out of the type layer for free: `type of`, `who is` and every + `== VAL_NUM` test already say "number". + +**Alt 3: double-plus-int shadow.** +- As Alt 2, but an int Value also keeps `num_ = (double)i64_`, so every + existing double read keeps working. +- Slots still need the SMI tag, because a slot has no room for a shadow, so + the slot work equals Alt 1's. + +### 2.3 How many sites change, and what a missed site does + +The counts come from the inventory (§1); the token-by-token split was +computed from the site list. + +| | sites that must be revisited | how the revisit is forced | what a MISSED site does | +|---|---|---|---| +| **Alt 1** | the 382 TEST and 63 CASE sites (all categories) that should admit ints; reads after them; every producer that should yield an int; JIT producers | CASE only: 62 of the 63 `case VAL_NUM:`/`case AST_NUM:` switches have no `default:`, so `-Werror=switch` names them. TEST sites are not forced. | The int fails `== VAL_NUM` and takes the non-number branch. That raises a type error where the branch raises (loud, acceptable), but returns a **soft default** where the branch is soft. Examples: `what is 5` answers 0 and `who is 5` answers "unknown" (vm.c:5465-5484); `json_encode of 5` writes `null` (builtins.c:998, the one switch with a `default:`). Silently wrong, but never a lossy double. | +| **Alt 1R** | the same sites | **Measured (§2.3a), under the Makefile's exact flags: 1,488 of the 1,798 sites stop compiling (1,450 non-JIT, 38 in the JIT files)**, each attributed to the commit that first forces it (R1.1 223, R1.2 464, R1.3 11, R1.6 743, R2.2 47), plus 53 inside a deleted definition. The other 257 keep a token on purpose, each class with its reason in §2.3a: the AST kind (52), the decided embed accessor (48), the libc parsers (15), the exact-conversion and formatter helpers (9), definitions (44), 86 JIT emitter immediates and helpers reviewed per guard (§4), and 3 `offsetof` uses that a real member rename does reach (measured). | **A compile error.** It cannot exist in code that names the vocabulary. The meaning changes that carry no token are listed with the gate that catches a miss (§2.7); the last `default:` that could absorb `VAL_INT` (builtins.c:998) is removed first in R1.2a. | +| **Alt 2** | payload reads and producers (enforced only if `num_` is renamed); the type tests keep compiling and now admit ints | none for the 382+63 type tests and the 484 checked reads | **A silent wrong number.** After `== VAL_NUM`, a `VAL_NUM_RAW` read of an int reads its int64 bits as a double (5 reads as 2.5e-323). An accessor that converts loses precision above 2^53 silently. In the JIT, three emitters misread with no compiler to catch them: `emit_load_numeric_operand`'s heap arm (jit.c:2343) loads `data.num_` of a boxed int; the `OP_SET_LOCAL` in-place branch (jit.c:3410) writes double bits into an int Value; the `OP_CONST` emitter (jit.c:2816) pushes `VAL_NUM_RAW` bits of an int constant. Constant dedup `const_equal` (chunk.c:212) merges `3` and `3.0`. | +| **Alt 3** | only the sites that need exactness (N2, N3, N6, N8, N14, tape) | none: every existing read compiles and works below 2^53 | **A silently lossy double**, exact below 2^53, so ordinary tests cannot see it. This is precisely the class the brief says to design out. | + +### 2.3a The compile measurement (design rounds 2 and 3) + +Round 0 claimed 1,449 non-JIT sites stop compiling; that was a token count, +and the critic's compile measured 1,331. Round 2 extended the rename list +and measured 1,416 of 1,798, under three hand-written configurations; the +one it called `server-db` was really every TU with every extension, ZLIB +included (the critic reproduced the count only with ZLIB). Round 3 does two +things: it adds the two token events the plan still lacked (`buffer_read_num`, +renamed in R1.6 so every buffer element read is re-decided, and the clamp, +deleted in R2.2), and it measures under the Makefile's exact flags. + +**Method.** +- A scratch copy of `src/`, never the worktree. Each token the plan renames + or deletes gets a declaration carrying clang's + `__attribute__((unavailable("P2 ...")))`, so every USE is a compile error + and the definition stays legal. The macros `SLOT_NUM_RAW` and + `EIGS_NUM_MAX` are routed through an unavailable `extern`; `VAL_NUM_RAW` + expands to the unavailable member `num_`. +- The tokens, by the commit that removes them (§2.7): + + | commit | tokens | + |---|---| + | R1.1 | `slot_is_num`, `slot_is_numeric`, `SLOT_NUM_RAW`, `slot_from_num`, `slot_as_double` | + | R1.2 | `VAL_NUM`, the member `num_` (so `VAL_NUM_RAW`), `VAL_NUM_OFFSET` | + | R1.3 | the AST literal payload `num`, the token payload `num_val` | + | R1.6 | the bridge (§2.7): `eigs_num_arg`, `eigs_num_arg_slow`, `eigs_list_num`, `eigs_elem_num`, `eigs_opt_num`, `eigs_slot_num`, `eigs_slot_elem_num`, `eigs_slot_opt_num`, `buf_num_arg`, `make_num`, `make_num_permanent`, the container helpers `list_set_num`, `list_append_num`, `list_rewrite_num`, `dict_value_set_num`, `dict_value_rewrite_num`, `list_get_num`, `dict_value_get_num`; `finite_num_to_byte`, `buffer_read_byte`, `buffer_read_num` | + | R2.2 | `num_guard`, `num_guard_named`, `EIGS_NUM_MAX` | +- clang 18.1.3, `-fsyntax-only -ferror-limit=0`, one translation unit at a + time, five configurations. Three are the Makefile's own build variants, + with flags and TU lists read at run time (`make print-FLAGS_`, + `make print-SRC_V_`); `freestanding` takes `tools/freestanding_check.sh`'s + flags; `all` adds the TUs no build variant compiles (`eigenlsp.c`, + `eigsdap.c`, the embed and JIT smoke programs), with every extension on: + + ``` + release (29 TUs) -Wall -Wextra -Werror=implicit-function-declaration -Werror=switch -Werror=comment + -Werror=misleading-indentation -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE + -DEIGENSCRIPT_EXT_HTTP=0 -DEIGENSCRIPT_EXT_MODEL=0 -DEIGENSCRIPT_EXT_DB=0 + -DEIGENSCRIPT_EXT_GFX=1 -DEIGENSCRIPT_VERSION="0.46.0" + server-db (35 TUs) the same CFLAGS, -I/usr/include/postgresql -DEIGENSCRIPT_EXT_HTTP=1 + -DEIGENSCRIPT_EXT_MODEL=1 -DEIGENSCRIPT_EXT_DB=1 -DEIGENSCRIPT_EXT_NET=1 + -DEIGENSCRIPT_EXT_GFX=1 (no ZLIB: the Makefile's FLAGS_server-db omits it) + zlib (29 TUs) release's flags plus -DEIGENSCRIPT_EXT_ZLIB=1 + freestanding (23 TUs) -O2 -ffreestanding -fno-stack-protector -U_FORTIFY_SOURCE + -Werror=implicit-function-declaration -Werror=switch -Werror=comment + -Werror=misleading-indentation -DEIGENSCRIPT_FREESTANDING=1 + -DEIGENSCRIPT_EXT_HTTP=0 -DEIGENSCRIPT_EXT_MODEL=0 -DEIGENSCRIPT_EXT_DB=0 + all (41 TUs) CFLAGS plus every extension, -DEIGENSCRIPT_EXT_ZLIB=1 included + ``` +- The generated `lsp_stdlib_index.h` is produced into the copy + (`tools/gen_lsp_stdlib_index.sh /lsp_stdlib_index.h`). +- An error line and each macro-expansion note under it are mapped back to + the unmutated copy's line (difflib). A site is **flagged** when its + file:line carries an error. A flagged site is attributed to the FIRST + commit whose token errors on its line: that is when a migrator must first + visit it. + +**Results** (the unmutated copy first, as the control): + +``` +base: cfg=release tus=29 errors=0 cfg=server-db tus=35 errors=0 cfg=zlib tus=29 errors=0 + cfg=freestanding tus=23 errors=0 cfg=all tus=41 errors=0 (unparsed_failures=0 everywhere) +R3: cfg=release tus=29 errors=2501 cfg=server-db tus=35 errors=2780 cfg=zlib tus=29 errors=2506 + cfg=freestanding tus=23 errors=1920 cfg=all tus=41 errors=3011 (unparsed_failures=0 everywhere) +inventory sites=1798 (non-JIT 1671, JIT-file 127) +cfg=release distinct_flagged_lines=1209 sites_flagged=1384 (non-JIT 1377, JIT-file 7) +cfg=server-db distinct_flagged_lines=1265 sites_flagged=1442 (non-JIT 1435, JIT-file 7) +cfg=zlib distinct_flagged_lines=1213 sites_flagged=1389 (non-JIT 1382, JIT-file 7) +cfg=freestanding distinct_flagged_lines=866 sites_flagged=1024 (non-JIT 1024, JIT-file 0) +cfg=all distinct_flagged_lines=1307 sites_flagged=1488 (non-JIT 1450, JIT-file 38) +UNION of the four build configs: sites_flagged=1447; all-TU config adds 41 +UNION: flagged=1488 (non-JIT 1450, JIT-file 38) inside-deleted-definitions=53 not-flagged=257 (non-JIT 168, JIT-file 89) +first forced in: R1.1=223 R1.2=464 R1.3=11 R1.6=743 R2.2=47 +errors not naming a planned token: {"cannot take the address of an rvalue of type 'double'": 5} +``` + +The error totals count a header's error once per TU that includes it; the +distinct lines and the sites are what matter. The five errors that name no +token are an artifact of the `SLOT_NUM_RAW` reroute (it takes an address, +and five uses pass an rvalue); every one sits on a line that also carries +an `unavailable` error, so no site depends on them. Every flagged site has a +first commit. + +**The 257 sites that never stop compiling, every one classified:** + +| class | sites | why it keeps compiling, and what forces it instead | +|---|---:|---| +| JIT emitter immediates and helpers | 86 | Tag compares (`0xFFF8`/`0xFFF9`/`0xFFFB`/`0xFFFD`) and the `emit_*` helpers. Reviewed per guard in §4.1: an SMI or a `VAL_INT` box fails every consuming guard and bails; the producer emitters keep their float meaning (§4.2). | +| `AST_NUM` | 52 | Under (B) the node kind still means "number literal". Its payload `num` is renamed in R1.3, so all 7 reads of the literal's value are flagged; a site that only tests the kind is right for both kinds. The critic made the same call. | +| `eigs_value_as_num` | 48 | The embed accessor: 40 call sites in `embed_smoke.c` and 6 in `embed_concurrent.c` (the embed test harnesses), plus its definition (`eigs_embed.c`) and declaration (`eigs_embed.h`). Q3 decided it converts an int as `lua_tonumber` does, so "this value as a double" is unchanged; exact ints get `eigs_value_as_int`/`eigs_value_is_int` (R1.8). | +| definitions | 44 | Declarations and bodies of the renamed and deleted names themselves. | +| `strtod` 14, `atof` 1 | 15 | The libc parsers. The lexer's result reaches the renamed `num_val` through `tok_add`, whose store is flagged; the literal's kind changes `tok_add`'s parameter, which forces the call. The JSON, store and trace scalar parsers return `make_num(d)` a few lines on (flagged, R1.6); trace.c:2324 fills a buffer's f64 storage (Q2). The rest parse model hyperparameters (float), the tape's observer config, and `tape_read.c:368`, which feeds the observer replay a double (`tape_read.c:635`); round 1 observes ints as doubles live and on replay alike (§8 risks), and round 3's change to `observer_slot_record_value`'s parameter forces this line. | +| `num_to_i64_exact` 6, `eigs_num_text` 3 | 9 | Pure helpers over a double, unchanged in meaning; every input reaches them through a renamed reader. N6 changes the formatter's output on purpose in R1.7, a token-less change listed in §2.7 with its rows. | +| `VAL_NUM_OFFSET` in jit.c | 3 | `offsetof` uses, which the attribute does not reach. A real rename does: renaming `num_` to `f64_` (and the macro) in a further scratch copy gives exactly 3 errors, jit.c:2367, 2368 and 3471. | +| **total** | **257** | | + +**What round 2 got wrong here, and round 3 fixes.** Round 2 kept +`buffer_read_num` (26 unflagged sites) as "a float by contract". Ten of its +38 sites read a value that is NOT a float: byte values (`str_from_bytes` +builtins_buf.c:240, `f64_from_bytes` :284, the zlib input :351, +`buf_from_pcm16le` :754 and :756, `write_bytes` builtins_host.c:1548, +`net_send` ext_net.c:361, and inside `buffer_read_byte` eigenscript.h:1920) +and indices (`scatter_add` builtins_tensor.c:1036, `flat_index_at` :1436, the +`gather` path the critic probed: an index of 1.5 truncates to 1 today). With +`buffer_read_num` renamed in R1.6, all 38 sites are flagged in that commit, +and each is re-decided: `buffer_read_f64` for data, the Q1 byte reader for a +byte, the N5 index reader for an index (§2.6). Without the rename, nine of +the ten lines stayed unflagged in round 2; most sit in a function another +token forces anyway (the `finite_num_to_byte` call a line later), but +`buf_from_pcm16le` names no other token before R2.2's clamp deletion, so +nothing would have made R1.6 visit it. + +So the headline is: **1,488 of 1,798 sites stop compiling across the +plan** (first forced: R1.1 223, R1.2 464, R1.3 11, R1.6 743, R2.2 47), 53 +more sit inside deleted definitions, and 257 keep a token for the reasons +above. None of the 257 changes meaning without a token event: the ones that +change meaning later do so in a commit listed in §2.7. + +### 2.4 The choice: Alt 1R + +1. **It is the only alternative whose missed site is loud by construction.** + Measured: 1,488 of 1,798 sites stop compiling until a person decides, + site by site, whether the site admits ints and with what semantics + (§2.3a). Alt 2 and Alt 3 leave 445 type tests and cases plus 484 checked + reads compiling with a widened or lossy meaning. +2. **The JIT argues for it independently.** All three of Alt 2's emitted + misreads exist because the emitter checks `type == VAL_NUM` and then loads + at a fixed offset (`VAL_NUM_OFFSET`). Under Alt 1R a boxed int has another + type, so the same unchanged code bails (§4). +3. **(B) does not require Alt 2.** (B) is a language-surface decision. + `val_type_name(VAL_INT)` returns `"num"`, and the `who` interrogative + answers "number". Lua itself keeps two variant tags under one basic type + (`LUA_VNUMINT` / `LUA_VNUMFLT` of `LUA_TNUMBER`) and tests the variant + wherever arithmetic cares, which is Alt 1's shape at the C level. +4. **No measurement favours another alternative** (§3). The slot cost is + identical for all four; the Value-level type test is one compare (Alt 1) + against two (Alt 2). + +The cost of Alt 1R is the size of the round-1 diff. §8 stages it with +deprecation warnings, so the compiler's warning count is the progress meter +and the final commit deletes the old names. A mechanical `sed` from old name +to new would reintroduce the silent class (`len` returning +`make_float(count)`), so the migration is per site, with the builtin table +(§6) deciding each builtin's accessor and the per-builtin behaviour table +(§9.4) checking it. + +### 2.5 The slot classifiers, derived + +Nothing produces `0xFFFA` yet. The first SMI producer meets every function +that classifies a slot, and today those guess: "not a number, null or bool" +is taken to mean heap. What an SMI would do, once one exists: +- **pointer misread**: `slot_type` (container.h:92), `eigs_slot_ref` + (container.h:98), `slot_bridge_unwrap` (vm.c:784), `slot_truthy` + (vm.c:862) dereference the payload as a `Value *`; +- **silent null**: `vm_slot_lift` (vm.c:826) and `slot_to_value` + (eigenscript.c:1885) turn the int into null; +- **silently unobserved**: `obs_slot_observable` (vm.c:1630) answers + "pointer or bool", so the binding answers from no data; +- **unreplayable**: `write_slot` (trace.c:2836) writes ``; +- **slow path into a misread**: `slot_as_double` (vm.c:872) returns 0; +- **right by accident**: `slot_type_name` (vm.c:3000) answers `"num"`. + +Round 0 listed these by hand and gave R1.1 a rule ("every function holding +both `slot_is_num` and `slot_as_ptr`") that the critic showed misses three of +them: `eigs_slot_ref`, `obs_slot_observable` and `slot_type_name`. Round 2 +derives the population instead (§1, check 4, `## Slot classifiers` in the +inventory file): +- **D1 (58)** names `slot_is_num`, `slot_is_numeric` or `slot_as_double`. + All three are renamed or deleted in R1.1, so the compiler stops at every + D1 entry. `slot_type`, `slot_bridge_unwrap`, `slot_truthy`, + `vm_slot_lift`, `slot_to_value`, `write_slot` and every arithmetic and + compare arm are D1. +- **D2 (20) and D3 (23)** name no number test, so nothing forces them. Each + has the reviewed row below. The inventory fails on a D2/D3 entry with no + row and on a row with no entry, and accepts only these dispositions: + - `guarded`: an SMI can reach it and is handled correctly as written (it + skips non-pointers, or tests one exact tag); + - `smi-arm`: R1.1 adds an explicit SMI arm; + - `refuse`: R1.1 makes it abort on an SMI, and on any tag it does not + handle, so a caller missed later fails loudly instead of misreading; + - `not-a-classifier`: it writes bits (a constructor, a bool result) and + classifies nothing. +- **T (67)** only reads a slot's `ValType` (§1). + +The three the critic named are now derived entries: `eigs_slot_ref` (D2, +refuse), `obs_slot_observable` (D3, refuse) and `slot_type_name` (D2, +smi-arm). + +**R1.1's C slot test** (`tests/phase2/slot_layer.c`) reaches the static +classifiers by including the single-file amalgamation (`make amalgamation`, +#397, which already leaves out the CLI's `main`). Its population is the +inventory file's D1/D2/D3 function entries (the file lists them; the test's +generator reads each signature from the source and keeps those whose first +parameter is an `EigsSlot`); it asserts `examined` equals that count and is +above zero. Each +entry is called under ASan with hand-built SMI slots at the boundary values +(0, ±1, ±(2^47 - 1), -2^47): a `refuse` entry must abort (in a forked +child), and every other entry must return without a sanitizer report, with +the int's Value, type name or truthiness where it returns one. This runs in +R1.1, before any program can produce an SMI. + +**As implemented in R1.1 (reported as design deviations in the R1.1 +result).** +- **The table covers D1 too.** The rename made the compiler stop at every D1 + entry, but a rename can be typed without a decision, so every D1, D2 and + D3 entry now has a reviewed row below (98 after R1.1), and the inventory + fails on a slot classifier of any class without one. +- **A fifth disposition, `unwired`.** Some D1 arms need what R1.1 does not + have: an int Value (`make_int`, R1.3), the int ValType (R1.2a), or tape v7 + (R1.3). Each calls `eigs_int_unwired(who, bits)`, which aborts naming the + reader. R1.2 completed `slot_type` and `slot_view` when `VAL_INT` + became available; it also completed `slot_int_value`'s boxed reader. + R1.3 deletes `eigs_int_unwired`, so the remaining arms in this group stop + compiling in the commit that makes ints reachable (2.7): `slot_to_value` (the one + place that boxes an SMI: the lifts, `slot_bridge_unwrap`, `eigs_slot_box` + and the `*_get_return` readers route there), `write_slot`, and `slot_from_int`'s out-of-range arm (the `VAL_INT` box). +- **The permanent refusals** (`refuse`) go through `eigs_slot_refuse(who, + slot)`, which prints the bits and aborts; `slot_as_ptr` refuses a non-heap + slot under ASan and `-DEIGS_SLOT_CHECKED`. Readers that used to turn an + unknown tag into null or `` (`slot_to_value`, `vm_slot_lift`, + `write_slot`) refuse it now; no slot carries such a tag today. +- **The C slot test's population** is + `tools/phase2_inventory.py --slot-population` (33 entries after R1.1); + `tests/phase2/slot_layer.sh` compares it with the entries the test + examined, both ways. The test also runs a tag matrix (a double, the + canonical NaN, -0.0, null, both bools, an SMI, a heap pointer, the + reserved tags and the sentinel against the predicates and the deciding + readers) and plant 1's named check (`slot_from_int` over the boundary + values). Its ABORT rows run in forked children and must name the reader. + +**As corrected in the R1.1 fix round (the R1.1 critic's FAIL).** +- **The four type-error arms are `unwired`, not `guarded`.** `NUM_CMP` + (`< > <= >=`), `INT_BINOP_R` (`& | ^ << >>`), `CASE(NEG)` and + `CASE(BNOT)` met an SMI with the type error naming `num`; inside a `try` + that error became a wrong value (`r is xs[0] < 6` with an SMI 5 gave + `false`, the float control `true`). "Handled correctly as written" was + false. Each now refuses through `eigs_int_unwired`, so R1.3, which deletes + the token, cannot compile until each has its int arm. All four are tied to + **R1.3**, bitwise included: R1.3's int literals reach `&` at once + (`tests/bench_dmg_shape.eigs:21`, `c.a is c.a & 255`), and §8 already + gives bitwise operators exact int64 operands at R1.3, with R1.5 changing + only the shift rule. +- **The tag matrix covers every entry.** Each of the 33 population entries + meets all twelve kinds (a double, the canonical NaN, -0.0, null, false, + true, an SMI, a heap pointer, the three reserved tags and the sentinel), + each kind with its expected outcome: a value, a refusal naming the reader, + or a raise. The runner requires the MATRIX set to equal the population. +- **Part D drives the operator arms through the VM.** A host builtin + (`phase2_poke`) writes an SMI into a list element, and a program then + applies each refusing operator to it, plain and inside a `try`: each must + abort naming its arm, where the float control computes the value. +- **The slot test builds at -O0** (about 41 s instead of 162 s, and well + under -O2's 602 MB peak), after the warnings #1775 listed were fixed: + `builtin_tensor_gather`'s missing return, and the maybe-uninitialized + locals behind `num_to_i64_exact` and `vm_index_resolve`. The first fix + round initialised those locals at all nine declarations, which cost + +2.07% Ir on DMG and +4.17% on a JIT-off float loop (the second R1.1 + critic). Moving the write into the readers instead (`slot_f64_value` + writing 0.0, or the slot's bits, when it answers 0; `vm_index_is_int` + writing on both paths) cost the same: measured with callgrind, any of + them changed `vm_run_ex`'s code, +3.8% (+0.44% for `vm_index_is_int` + alone) on the float loop, which executes neither failure path. What + stays is the initialisation at exactly the three declarations GCC + flags at -O1 (`INT_BINOP_R`'s `_ad`/`_bd`, the `i` of `CASE(INDEX_SET)` + and of `jit_helper_index_set`): against R1.1, -0.0001% on the JIT-off + float loop, +0.0001% with the JIT, -0.08% on the DMG canary. +- **A build warning fails the slot test under gcc only.** gcc is the + reference toolchain for #1775's class (-Wreturn-type, + -Wmaybe-uninitialized at -O0/-O1). R1.1's macOS lane (clang) printed 103 + warnings the gcc build does not have (-Wdefault-const-init-field-unsafe + on `EigsView`), which the main clang build prints too. Under any compiler + other than gcc (asked through its predefined macros: `__GNUC__` without + `__clang__`) the slot test follows the main build's policy on that + platform: warnings print, and only the -Werror set fails. +- **Its runner reads each tool on its own channel.** The scan and the test + each have a grammar; a line outside it, or any stderr, is a FAIL that + prints the line (the R1.1 critic: Python 3.13's DeprecationWarning on the + scan's stderr was read as two classifiers). The Python runs with `-W + error`. +- **"The int's Value"** cannot be checked in R1.1: no int Value kind exists, + so allocating readers were ABORT rows. R1.2 turned `slot_type` and `slot_view` into RETURN rows as scheduled + with the Value type (the R1.2i follow-up completed them). Their boxed + companion `slot_int_value` was completed in the same follow-up. + `slot_to_value` and the readers that allocate an int Value remain ABORT + rows until R1.3 introduces `make_int`. + + +| name | disposition | what an int (an SMI) does there, as of R1.1 | +|---|---|---| +| `builtins.c:bool_gate_name` | guarded | skips every non-pointer slot (`!slot_is_ptr`); an SMI is never a builtin (D2) | +| `builtins.c:sort_cmp_str` | guarded | reached only after `builtin_sort` proved every element a string (its homogeneity test names `VAL_NUM`, compile-forced) (D2) | +| `builtins_host.c:ls_entry_cmp` | guarded | sorts the string entries `ls` built itself (D2) | +| `container.h:dict_value_get_f64` | guarded | final float-storage reader: an SMI returns 0 without writing the output; no allocation or ownership transfer (D1) | +| `container.h:dict_value_get_int` | smi-arm | final int-storage reader: exact SMI or boxed int payload; no allocation or ownership transfer (D1) | +| `container.h:dict_value_get_ref` | smi-arm | NULL for an SMI or a double (container.h:137); since R1.3 a boxed int (\|i\| >= 2^47) is a heap slot and comes back as its non-NULL `VAL_INT` Value, so the caller types the element before reading it (D1) | +| `container.h:dict_value_get_return` | smi-arm | an int element returns a fresh int Value through `slot_to_value`, the one SMI boxer (unwired until R1.3) (D1) | +| `container.h:eigs_container_slot` | not-a-classifier | the canonicalizing Value-to-slot writer (R1.2): it reads a Value, not a slot, and writes the bits; a `VAL_INT` box becomes an SMI inside [-2^47, 2^47) and stays a heap slot outside it (2.1) (D3) | +| `container.h:eigs_slot_adopt` | guarded | decides ownership from what `eigs_container_slot` returned (compile-forced); a small int comes back as an SMI and its box is released (D3) | +| `container.h:eigs_slot_box` | smi-arm | an SMI is boxed as an owned int through `slot_to_value`; floats retain `make_num` (R1.3) (D1) | +| `container.h:eigs_slot_ref` | refuse | an immediate double or an SMI was dereferenced as a pointer; every caller tests for a number first. R1.1 aborts on any slot that is not a pointer, null or bool, so a caller missed later fails loudly (D2) | +| `container.h:eigs_slot_store_borrow` | guarded | an SMI is canonical storage and comes back as it is, with no reference to take (D1) | +| `container.h:eigs_slot_store_owned` | guarded | an SMI is canonical storage and comes back as it is, with no reference to release (D1) | +| `container.h:list_get_f64` | guarded | final float-storage reader: an SMI returns 0 without writing the output; no allocation or ownership transfer (D1) | +| `container.h:list_get_int` | smi-arm | final int-storage reader: exact SMI or boxed int payload; no allocation or ownership transfer (D1) | +| `container.h:list_get_ref` | smi-arm | NULL for an SMI or a double (container.h:133); since R1.3 a boxed int (\|i\| >= 2^47) is a heap slot and comes back as its non-NULL `VAL_INT` Value (D1) | +| `container.h:list_get_return` | smi-arm | an int element returns a fresh int Value through `slot_to_value` (unwired until R1.3) (D1) | +| `container.h:slot_type` | smi-arm | an SMI's type is `VAL_INT` (R1.2); any other unknown tag refuses instead of being read as a pointer (D1) | +| `container.h:slot_view` | smi-arm | an SMI's facade is an exact `VAL_INT` view (R1.2) (D1) | +| `eigenscript.c:#define GC_EDGE_TABLE` | guarded | a GC edge exists only for `slot_is_ptr`; an SMI has no edge (D2) | +| `eigenscript.c:ent_child_slot` | smi-arm | an int's entropy is its double's: the numeric channel until round 3 (design 7) (D1) | +| `eigenscript.c:env_get_hashed` | guarded | a non-pointer slot goes through `slot_to_value` (compile-forced) and comes back boxed: the lift boxes on purpose (§2.1) (D2) | +| `eigenscript.c:env_get_local_hashed` | guarded | as `env_get_hashed` (D2) | +| `eigenscript.c:gc_collect_at_exit` | guarded | skips every non-pointer slot (D2) | +| `eigenscript.c:module_ns_project` | guarded | an int skips the float-over-float rewrite: `slot_to_value` boxes it and the dict store re-canonicalizes it (D1) | +| `eigenscript.c:observer_entropy_now` | smi-arm | an int is the numeric kind, as its double (D1) | +| `eigenscript.c:slot_from_value` | not-a-classifier | the adopting Value-to-slot writer (R1.2): it reads a Value, not a slot, and writes the bits; a `VAL_INT` box becomes an SMI inside [-2^47, 2^47) (its box released) and stays a heap slot outside it (2.1) (D3) | +| `eigenscript.c:slot_to_value` | smi-arm | the one SMI boxer: a fresh owned `make_int` preserves the exact value (R1.3); an unknown tag refuses (D1) | +| `ext_store.c:store_json_encode_slot` | smi-arm | an int is written as its exact digits (design 7) (D1) | +| `jit.c:jit_compile_to_thunk/OP_EQ` | not-a-classifier | writes the bool result bits (D3) | +| `jit.c:jit_compile_to_thunk/OP_NEG` | not-a-classifier | the NOT arm writes bool result bits (D3) | +| `jit.c:jit_compile_to_thunk/OP_NULL` | not-a-classifier | the constant arm writes null and bool bits (D3) | +| `embed_smoke.c:main` | guarded | C smoke fixture checks a known result-list slot with slot_is_bool before reading its bool payload (R1.6z-D1) | +| `jit_smoke.c:run_bool_cases` | not-a-classifier | test harness: expected bool bits (D3) | +| `jit_smoke.c:run_index_bail_cases` | guarded | test harness: an exact null test of a result (D3) | +| `trace.c:trace_source_replay` | guarded | source-size metadata requires `VAL_INT` before the exact int reader; a rejected float is read only for main's unchanged diagnostic (sync #5, #1885) (D1) | +| `trace.c:write_slot` | smi-arm | v9 retains R1.3's exact int digits and float point/exponent markers; an unknown tag refuses (D1) | +| `value_slot.h:#define slot_as_heap` | refuse | alias of `slot_as_ptr` (D2) | +| `value_slot.h:slot_as_ptr` | refuse | the raw dereference: R1.1 refuses a non-heap slot in ASan builds and under `-DEIGS_SLOT_CHECKED` (the C slot test) (D2) | +| `value_slot.h:slot_decref` | guarded | acts only on tag `0xFFFB`; an SMI has no refcount (D3) | +| `value_slot.h:slot_f64_value` | guarded | the float reader (was vm.c's `slot_as_double`): answers 0 for an int, so each caller decided where an int goes (D1) | +| `value_slot.h:slot_false` | not-a-classifier | constructor (D3) | +| `value_slot.h:slot_from_bool` | not-a-classifier | constructor (D3) | +| `value_slot.h:slot_from_heap` | guarded | maps null and bool Values to their immediates and every other Value to a heap slot, so a `VAL_INT` box stays a box: the lifts depend on that (§2.1) (D3) | +| `value_slot.h:slot_from_int` | not-a-classifier | the one int writer: an SMI inside [-2^47, 2^47), otherwise an owned `VAL_INT` box with all int64 bits (R1.3) (D3) | +| `value_slot.h:slot_incref` | guarded | as `slot_decref` (D3) | +| `value_slot.h:slot_int_value` | smi-arm | an SMI's int, or a heap `VAL_INT`'s exact int64 (R1.2) (D1) | +| `value_slot.h:slot_is_bool` | guarded | exact tag test (D3) | +| `value_slot.h:slot_is_f64` | guarded | the double test (was `slot_is_num`): an SMI answers 0 (D1) | +| `value_slot.h:slot_is_heap` | guarded | exact tag test (D3) | +| `value_slot.h:slot_is_null` | guarded | exact bit test (D3) | +| `value_slot.h:slot_is_ptr` | guarded | exact tag test (D3) | +| `value_slot.h:slot_is_smi` | smi-arm | the SMI tag test (D1) | +| `value_slot.h:slot_null` | not-a-classifier | constructor (D3) | +| `value_slot.h:slot_smi_val` | smi-arm | an SMI's int, sign-extending bit 47 without a right shift (2.1) (D1) | +| `value_slot.h:slot_true` | not-a-classifier | constructor (D3) | +| `value_slot.h:slot_truthy_immediate` | smi-arm | an int is truthy when it is not 0, like a double (D1) | +| `vm.c:#define ARITH_FAST` | guarded | an int is not a float (`slot_f64_value`): the arm's slow path pops it through `slot_bridge_unwrap`; R1.3 gives that path int arithmetic (D1) | +| `vm.c:#define INT_ARITH` | smi-arm | two exact integer slots use checked int64 arithmetic; the result is an SMI when it fits and an owned int box otherwise, and overflow raises (R1.3) (D1) | +| `vm.c:#define INT_BINOP_R` | smi-arm | both operands use `vm_bit_operand`; R1.5 returns the int kind and uses `eigs_bit_shift` for Lua counts and logical right shift (D1) | +| `vm.c:#define NUM_CMP` | smi-arm | the float fast path remains; integer and cross-kind operands use exact N2 comparison (R1.3) (D1) | +| `vm.c:#define READ_SLICE_BOUND` | smi-arm | an int bound is its value when a C `int` holds it, else a value error (D1) | +| `vm.c:jit_helper_dot_get` | guarded | a non-null field is pushed as its bits; an SMI is a valid stack slot (D3) | +| `vm.c:jit_helper_dot_set` | guarded | an int value skips the immediate store: the slow path pops it through `slot_bridge_unwrap` (D1) | +| `vm.c:jit_helper_index_get` | guarded | an int index skips the fast path: the slow path lifts it through `slot_to_value` (D1) | +| `vm.c:jit_helper_index_set` | guarded | an int index or value skips the fast path: the slow path pops it through `slot_bridge_unwrap` (D1) | +| `vm.c:jit_helper_iter_next` | guarded | state must be a pointer; index and snapshot metadata use the exact checked iterator readers, matching CASE(ITER_NEXT) (R1.3) (D1) | +| `vm.c:jit_helper_local_dot_set` | guarded | an int value skips the immediate store: `vm_slot_lift` boxes it (D1) | +| `vm.c:jit_helper_local_idx_dot_get` | guarded | as `jit_helper_dot_get` (D3) | +| `vm.c:loop_counter_load` | smi-arm | private stack counters use slot_int_value: an SMI or a boxed int is exact; any non-int is an internal error (sync 10 #1906) (D1) | +| `vm.c:loop_counter_store` | smi-arm | exact int64 counter updates use slot_from_int; SMI writes need no ownership work, otherwise the prior slot is released and the new SMI or int box is owned (sync 10 #1906) (D1) | +| `vm.c:loop_iter_store` | smi-arm | public loop counts are exact ints; a same-kind in-range SMI update avoids boxing, while other counts publish make_int through the environment setter, with shared writes locked (S9-K1/sync 10 #1906) (D1) | +| `vm.c:obs_dump_scope` | smi-arm | an int's entropy, as its double (D1) | +| `vm.c:obs_dump_value` | smi-arm | an int prints as its exact digits (D1) | +| `vm.c:obs_slot_num` | smi-arm | the observer's numeric channel: a float, or an int as its exact double until round 3 (design 7) (D1) | +| `vm.c:obs_slot_observable` | refuse | answered "pointer or bool", so an SMI was silently unobserved. R1.1 aborts on an SMI or a reserved tag; every caller routes a number to `obs_slot_num` first. A BOXED int is a pointer and goes to `observer_slot_update(Value *)`, whose number arm (eigenscript.c:785) R1.2's `VAL_NUM` rename forces (§7, O map) (D1 since R1.1: it names `slot_is_f64`) | +| `vm.c:obs_slot_value` | guarded | called only after `obs_slot_observable` answered true (pointer or bool) (D2) | +| `vm.c:slot_bridge_unwrap` | smi-arm | an SMI materializes as an owned exact int through `slot_to_value` (R1.3) (D1) | +| `vm.c:vm_bit_operand` | smi-arm | an int64 operand is read exactly; float operands keep the #1740 conversion rule (R1.3) (D1) | +| `vm.c:vm_number_compare` | smi-arm | int and cross-kind comparisons use exact N2 order, without double rounding (R1.3) (D1) | +| `vm.c:vm_iter_index` | smi-arm | index metadata accepts an exact int in the C-int range, otherwise exhausts without narrowing (R1.3) (D1) | +| `vm.c:vm_run_ex/CASE(ITER_NEXT)` | guarded | S9-K1-C: a proven SMI position is overwritten in place with its next exact int; a float or heap slot uses the owning writer, preserving raw-bytecode support and releasing the replaced reference (D3) | +| `vm.c:vm_iter_limit` | smi-arm | int snapshot metadata clamps the live length; negative snapshots exhaust and oversized positive snapshots keep the live length (R1.3) (D1) | +| `vm.c:slot_truthy` | smi-arm | an int is truthy when it is not 0; a reserved tag refuses instead of being dereferenced (D1) | +| `vm.c:slot_type_name` | smi-arm | an SMI reached the final `return "num"` by accident. R1.1 adds the explicit SMI arm ("num", (B)) and aborts on a reserved tag (D1 since R1.1) | +| `vm.c:vm_borrow_scan` | guarded | compares heap slots with the result pointer; an SMI is not heap (D2) | +| `vm.c:vm_builtin_name_for` | guarded | skips every non-heap slot (D2) | +| `vm.c:vm_leaf_accessor_exec/OP_ADD|OP_SUB|OP_MUL` | guarded | bails unless both operands are floats, so the generic call computes an int or mixed operation (4.5) (D1) | +| `vm.c:vm_leaf_accessor_exec/OP_INDEX_GET` | guarded | bails on an int index; the generic call indexes (4.5) (D1) | +| `vm.c:vm_leaf_accessor_exec/OP_LOCAL_DOT_GET` | guarded | a non-pointer target bails (`return 0`); a non-null field is borrowed as its bits, an SMI included (D2) | +| `vm.c:vm_local_is_null` | guarded | exact null test (D3) | +| `vm.c:vm_local_lift` | guarded | a non-pointer, non-null slot goes through `slot_to_value` (compile-forced) and is boxed on purpose (D2) | +| `vm.c:vm_run_ex/CASE(BNOT)` | smi-arm | an int64 operand is read exactly; R1.5 returns its exact complement as the int kind (D1) | +| `vm.c:vm_run_ex/CASE(DESTRUCTURE_UNPACK)` | guarded | a non-pointer source takes the error path, which names the type through `slot_type_name` (D2) | +| `vm.c:vm_run_ex/CASE(DISPATCH)` | smi-arm | an int key is its exact value; a key outside C `int` raises `index_range` under main #1849/#2032, after N5's exact int64 check; the result returns through `slot_bridge_unwrap` (D1) | +| `vm.c:vm_run_ex/CASE(DOT_GET)` | guarded | exact null test of a field (D3) | +| `vm.c:vm_run_ex/CASE(DOT_SET)` | guarded | an int value skips the immediate store: the slow path pops it (D1) | +| `vm.c:vm_run_ex/CASE(EQ)` | guarded | an int is not a float: the slow path (`values_equal_op` on the lifted Values); R1.3 adds N2 (D1) | +| `vm.c:vm_run_ex/CASE(INT_CONST)` | not-a-classifier | int literal producer: a constant in the SMI range is emitted through `slot_from_int`; an out-of-range int borrows its constant-pool box into the stack with an owned reference (R1.3) (D3) | +| `vm.c:vm_run_ex/CASE(INDEX_GET)` | guarded | an int index skips the fast path: the slow path lifts it through `slot_to_value` (D1) | +| `vm.c:vm_run_ex/CASE(INDEX_SET)` | guarded | an int index or value skips the fast path: the slow path pops it (D1) | +| `vm.c:vm_run_ex/CASE(LOCAL_DOT_SET)` | guarded | an int value: `vm_slot_lift` boxes it (D1) | +| `vm.c:vm_run_ex/CASE(LOCAL_IDX_DOT_GET)` | guarded | exact null test of a field (D3) | +| `vm.c:vm_run_ex/CASE(LOCAL_IDX_DOT_SET)` | guarded | an int value: `vm_slot_lift` boxes it (D1) | +| `vm.c:vm_run_ex/CASE(NE)` | guarded | an int is not a float: the slow path; R1.3 adds N2 (D1) | +| `vm.c:vm_run_ex/CASE(NEG)` | smi-arm | an int is negated exactly and INT64_MIN raises value; floats keep their sign semantics (R1.3) (D1) | +| `vm.c:vm_run_ex/CASE(SLICE_GET)` | guarded | as DESTRUCTURE_UNPACK (D2) | +| `vm.c:vm_slot_lift` | smi-arm | an SMI is boxed through `slot_to_value`; a reserved tag refuses (R1.3) (D1) | +| `vm.c:vm_slot_value_opaque` | guarded | only a pointer can be a fn or builtin; an SMI answers "not opaque", which is right (D2) | +| `vm.c:vm_store_local_slot` | guarded | an int skips the in-place float store; the generic swap stores the SMI (D1) | + + +### 2.6 The accessor API after round 1 (Alt 1R) + +``` +/* slots (R1.1: readers and the SMI encoding; no int VALUE can exist yet) */ +int slot_is_f64(EigsSlot); /* high16 < 0xFFF8 */ +int slot_is_smi(EigsSlot); /* high16 == 0xFFFA */ +int slot_is_int(EigsSlot); /* SMI or heap VAL_INT */ +int slot_is_number(EigsSlot); /* f64 or int */ +EigsSlot slot_from_f64(double); /* the ONE double writer; round 2: canonical NaN */ +EigsSlot slot_from_int(int64_t); /* the ONE int writer: SMI in range, else a box. + Until R1.3 its only callers convert an + existing VAL_INT (§8, the int-source check); + R1.1's box arm is eigs_int_unwired */ +void eigs_slot_refuse(const char *, EigsSlot); /* R1.1: a broken contract */ +void eigs_int_unwired(const char *, uint64_t); /* R1.1; R1.3 deletes it (2.5) */ +int slot_f64_value(EigsSlot, double *); /* an immediate double or a VAL_FLOAT + box; 0 for anything else, ints + included. Replaces slot_as_double. */ +int slot_int_value(EigsSlot, int64_t *); /* an SMI or a VAL_INT box */ +#define SLOT_F64_RAW(s) /* was SLOT_NUM_RAW */ + +/* Values (R1.2a; make_int lands in R1.3, the first int producer) */ +Value *make_float(double); Value *make_float_permanent(double); +Value *make_int(int64_t); +#define VAL_F64_RAW(v) /* member f64_; were VAL_NUM_RAW and num_ */ +#define VAL_F64_OFFSET /* was VAL_NUM_OFFSET (the JIT's fixed-offset load) */ +#define VAL_I64_RAW(v) /* member i64_ of a VAL_INT */ + +/* the final readers, which replace the bridge in R1.6 (§2.7) */ +EigsNum eigs_number_arg(const Value *, const char *who); /* int-preserving */ +double eigs_float_arg(const Value *, const char *who); /* float-producing: int converts */ +int64_t eigs_int_arg(const Value *, const char *who); /* int-requiring: N5 exact rule */ +int eigs_int_arg_range(const Value *, int64_t lo, int64_t hi, + int64_t *out, const char *who); /* N5, then the C range */ +uint8_t eigs_byte_arg(const Value *, const char *who); /* Q1: N5, then 0..255 */ +/* buffer elements (buffer_read_num renamed in R1.6; every site re-decided) */ +double buffer_read_f64(const Value *buf, int64_t i); /* data: an f64 element (Q2) */ +int buffer_byte_at(const Value *buf, int64_t i, uint8_t *out, const char *who); + /* a byte value: N5, then 0..255 (Q1) */ +int buffer_index_at(const Value *buf, int64_t i, int64_t *out, const char *who); + /* an index: N5 (gather, scatter_add) */ +/* the container f64/int helpers (R1.6): list_set_f64, list_append_f64, + list_get_f64, dict_value_set_f64, ... and their _int twins (were *_num) */ + +/* Q7 (decided): continuous parameters, by the quantity they represent */ +int eigs_geom_shape(const Value *args, int first, EigsShapeKind kind, + const EigsRect *drawable, EigsShape *out, const char *who); +int eigs_bounded_arg(const Value *, double lo, double hi, int *out, const char *who); +int eigs_magnitude_arg(const Value *, int64_t lo, int64_t hi, int64_t *out, + const char *who); + +/* literals (R1.3) */ +OP_INT_ZERO, OP_INT_ONE, OP_INT_CONST /* appended to OpCode; §2.7 */ +/* AST literal payload `num` and the token's `num_val` become an EigsNum */ +``` + +Each `_arg` reader also has the list, slot and optional forms of today's +checked accessors. + +**The Q7 readers** (decided in +[#1758 comment 6174302316](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6174302316); +the builtin table's `q7` column, §6, names each builtin's classes): +- **Geometry** (coordinates and sizes): clip the WHOLE shape in float + space against the drawable area, then round to nearest. A per-argument + reader cannot do that: saturating each endpoint of + `gfx_rect of [-3e9, 0, 6e9, 10]` separately covers half the screen + instead of all of it, so the reader takes the shape. `eigs_geom_shape` + reads all of a shape's numbers as doubles (a non-finite one raises + `value`, N13), then clips by kind and only then converts: + - `POINT` (x, y): outside the drawable rect, the shape is empty; + - `RECT` (x, y, w, h; also the clip rect, the framebuffer's destination + `dx, dy, w*scale, h*scale`, and a glyph cell of `gfx_text`): intersect + with the drawable rect in float space; an empty intersection draws + nothing; + - `LINE` (x1, y1, x2, y2): Liang-Barsky clipping of the segment in float + space, so the visible part keeps its slope; + - `CIRCLE` (cx, cy, r) and `RRECT` (x, y, w, h, r): reject when the + bounding box misses the drawable rect; otherwise clamp the span loop to + the drawable rows and columns before converting, so a radius of `3e9` + costs the visible rows, not 6e9 iterations; + - every surviving coordinate is then rounded half away from zero (Q4's + rule) into a C `int`, which the clip guarantees holds it. + `EigsShape.empty` tells the caller to draw nothing; that is the only + outcome a finite out-of-range shape has. The implementation retains the original + continuous coordinates beside clipped integer bounds for curved spans and + framebuffer source mapping; intermediate edge arithmetic must not overflow + merely because finite coordinates extend beyond the drawable area. The line + reader retains exact binary64 product sums through clip-edge and half-tie + decisions, without relying on a wider host long-double format. +- **Bounded** (colour channels, gain, volume, PCM samples): clamp to the + domain, then round (`eigs_bounded_arg`); a NaN raises `value` (N13). +- **Magnitude** (durations, sleeps, timeouts, sample counts, window sizes, a + measurement's scale): round, and raise `value` naming the builtin when the + result does not fit what the C call can honour (`eigs_magnitude_arg`). + +### 2.6a Stored non-finite buffers (R1.6z-D1) + +Buffer equality compares the stored doubles element by element with C `==` +and requires equal counts. NaN equals nothing, including itself; +inf equals +only +inf, -inf only -inf, and -0.0 equals +0.0. Equality is total: it never +raises and never writes math flags, in either strict mode. Buffers stay out +of the identity shortcut (#1417). This applies N2/N11 scalar equality to +unboxed elements and avoids fabricated finite answers (Principles §2). + +Measured prior art in the ruling: Python `array.array('d')` and `memoryview` +compare a NaN-containing buffer unequal to itself, matching NumPy +`array_equal` with its default `equal_nan=False`. Python list identity +shortcuts are deliberately rejected. Refusal would make equality partial; +bit comparison would incorrectly distinguish signed zeros. + +**R1.6z-D2 (ruling #1758 comment 6385903088): structural all the way down.** +A list or dict compared with itself is equal exactly when every element pair +it reaches is equal. A container reaching a NaN-holding buffer is unequal to +itself, including through nested or cyclic containers. This also governs +`!=`, `list_contains` and `list_index_of`. Measured prior art in D2: + +| language | `l == l`, `l = [nan-buffer]` | `d == d` | `a == a` (buffer) | +|---|---|---|---| +| Python 3 (`array('d')` in a list/dict) | `True` | `True` | `False` | +| Rust std (`Vec>`, `HashMap`) | `false` | `false` | `false` | + +Python's element identity shortcut produces the inconsistency rejected here +(Principles §2); Rust's `PartialEq` is consistent. Identity remains a fast +path only after a visited-set scan proves no reachable non-reflexive element. +Before R2.2 that element is a NaN-holding buffer; R2.2 extends the predicate to +NaN floats. The scan is linear in distinct containers and their stored edges +and elements, terminates on cycles, never raises and never writes +`g_math_flags`. Distinct containers retain today's pairwise traversal and +the #1665 depth cap unchanged. `N11.container-self-equal-ieee` pre-registers +the structural cases and a 40-level `[x, x]` finite-buffer DAG cost witness. + +The public C getter `eigs_value_buffer_get` returns the stored double exactly, +including NaN payloads, infinities and negative zero. It never normalizes, +raises for a valid element, or writes math flags. It mirrors the setter's +bit-preserving store and Lua `lua_tonumberx`/CPython `PyFloat_AsDouble`, +which also hand C the raw double. Clamping fabricates a value; a new status +channel would change the ABI for a property C can test with `isfinite`. +Wrong-type and out-of-range reports remain the separately decided API contract. +The getter migration and public API docs land in the embed file group. + +VM indexing and iteration use the finite data boundary, matching `get_at` +(R1.6d-D1): lenient NaN and every infinity raise `value`; strict NaN keeps +its first diagnostic and INVALID flag. Speculative leaf evaluation bails +before raising, preserving the callee's error kind, message and source line. + +### 2.7 Every meaning change coincides with a token change + +Round 0's order changed meanings with no token change: R1.1 widened +`slot_as_double` with an SMI arm, and R1.3 re-meant `OP_NUM_ZERO`/`OP_NUM_ONE` +as ints. Neither produces a compile error, so a missed caller or a missed +executor (the critic found the leaf evaluator's `OP_NUM_ZERO`/`OP_NUM_ONE` +arms, vm.c:1872-1877) would keep compiling with the old meaning. Round 2's +order had a second flaw the critic found: R1.2 deleted the builtin readers +(a compile error) while claiming to preserve behaviour, and R1.6 then changed +about 114 builtin rows with no token event at all. + +The rule: **a meaning change lands in the same commit as the token change +that stops every affected site**, or it is listed below with the reason no +token exists and the gate that catches a miss instead. + +**The bridge.** The builtin-facing readers and producers are kept, under +their current names, until R1.6, and they are the bridge: `eigs_num_arg` and +its family, `buf_num_arg`, `make_num`/`make_num_permanent`, the container +`*_num` helpers, `finite_num_to_byte`, `buffer_read_byte` and +`buffer_read_num`. Through R1.5 they keep exactly today's meaning: a number +read is a double (an int converts, as every number already is on the tag), a +number produced is a float. R1.6 deletes the bridge, so every builtin site +stops compiling in the commit that gives it its §6 semantics. R1.2 changes no reachable builtin behaviour: no int exists yet. Its exact +sort and JSON text reader arms are the explicit exceptions to the bridge +policy below; they retain final int semantics instead of converting to double. + +| commit | meaning change | token change in that commit | a missed site | +|---|---|---|---| +| R1.1 | a slot may hold an SMI (no producer yet) | `slot_is_num` -> `slot_is_f64`, `SLOT_NUM_RAW` -> `SLOT_F64_RAW`, `slot_from_num` -> `slot_from_f64`; `slot_is_numeric` and **`slot_as_double`** deleted | compile error: 223 sites first forced here (§2.3a), the 58 D1 classifiers among them | +| R1.1 | a slot classifier without a number test meets an SMI | none can exist (they name no number token) | the derived D2/D3 table (§2.5): `refuse` entries abort, `smi-arm` entries answer; the inventory fails on an unreviewed entry | +| R1.2a | a Value number may be an int (`VAL_INT`; no producer yet) | `VAL_NUM` -> `VAL_FLOAT`, `num_` -> `f64_`, `VAL_NUM_RAW`/`VAL_NUM_OFFSET` renamed; `VAL_INT` appended, so `-Werror=switch` stops every default-less ValType switch; the one `default:` (builtins.c:998) removed in the same commit | compile error: 464 sites first forced here. Each forced site gets its final int arm, live from R1.3; a builtin's number test admits an int and reads it through the bridge | +| R1.3 | **the first int values**: int literals, and int arithmetic on them | the AST payload `num` and the token's `num_val` become an `EigsNum`; **new opcodes `OP_INT_ZERO`, `OP_INT_ONE`, `OP_INT_CONST`, appended** (the opcode-ABI rule; no `EIGS_BYTECODE_ABI` bump); `make_int` introduced; `eigs_int_unwired` deleted (the R1.1 placeholders, 2.5) | compile error at the 11 payload sites, at each `eigs_int_unwired` caller, and the four exhaustive OpCode switches (`op_name` chunk.c:337, `op_verify_operands` chunk.c:466, `op_verify_stack_effect` chunk.c:615, `op_stack_effect` compiler.c:305; `tools/obs_marker_check.sh` requires each new enumerator's `/*obs:NONE*/`); the JIT and leaf scanners refuse an opcode they do not know (their `default:`); the interpreter's dispatch table is the one executor the compiler cannot check, and a missing entry is a NULL jump that crashes the first int-literal row (loud) | +| R1.3 | `OP_NUM_ZERO`, `OP_NUM_ONE`, `OP_CONST` | **no meaning change**: they keep their float meaning in all three executors (interpreter vm.c:3257, JIT emitter jit.c:2797, leaf evaluator vm.c:1872); an int is never an `OP_CONST` operand | n/a | +| R1.4 | `//` | `OP_IDIV` appended, `TOK_SLASH_SLASH` appended (and `//=`, Q6) | compile error at the exhaustive switches | +| R1.6 | **every builtin's §6 semantics** (N5 raises, int results, Q1 byte and Q7 continuous readers, the index readers of `gather`/`scatter_add`) | **the bridge is deleted** (21 tokens, §2.3a) | compile error: 743 sites first forced here; the per-builtin behaviour table (§9.4) checks every row's result against §6, on the tag and the branch | +| R1.8 | the embed API gains ints | `eigs_value_new_int`, `eigs_value_as_int`, `eigs_value_is_int` added; `eigs_value_as_num` keeps its name **by decision** (Q3: it converts, like `lua_tonumber`) | the embed gate row (MAP `gate.N7.embed-int`) | +| R2.2 | NaN and inf become reachable | `num_guard`, `num_guard_named`, `EIGS_NUM_MAX` deleted | compile error: 47 sites first forced here (the rest of the clamp's 74 sites were forced earlier) | + +**The meaning changes that cannot carry a token**, each with the gate that +catches a miss: +- **R1.3, the int arms written in R1.1 and R1.2 go live.** They were forced + then and written with their final semantics while no int could reach them. + Gate: R1.3's 44 MAP rows and the C slot test; and the int-source check + (§8) proves nothing before R1.3 could produce an int, so nothing earlier + could have exercised an unfinished arm. The R1.1 arms that could not be + final (`unwired`, 2.5) are token-forced instead: R1.3 deletes + `eigs_int_unwired`. Since the R1.1 fix round the four operator arms that + raised a type error for an SMI (`NUM_CMP`, `INT_BINOP_R`, `NEG`, `BNOT`) + are among them. +- **R1.3, the tape parser.** `parse_value_p` (trace.c:2284) reads digits as + an int from v7 on; the version bump refuses an older tape. Gate: the T1 + rows (`T1.twice-int`, `T1.v6-refused`). +- **R1.3, the leaf evaluator vs the generic path.** The leaf has no frame and + no `OP_LINE`, so nothing marks it when the generic path's semantics change. + Gate: the `trace` tier of §9.2, which runs every row with the leaf off and + requires byte-identical output. +- **R1.4 `%` and R1.5 the shift rules.** Each has one live executor in round + 1: the interpreter (`CASE(MOD)`, `INT_BINOP_R`). The JIT scanner refuses + MOD and the bitwise ops from R1.3, the leaf admits neither, and nothing + folds constants (checked: the compiler and parser evaluate no operator at + compile time). Gate: the N4 and N8 rows, including their `F` shapes. +- **R1.7, the float text.** N6 changes `eigs_num_text`, the one shared + float formatter, and in the same commit the writers that format a float on + their own: the JSON encoder, `store_json_encode` and `db_build_query` + (#1762), and trace.c's `%.17g` writers (2817, 2836, 2950). (The decoders' + int kinds for integer text, `json_decode`, `json_path`, `store_query`, + `db_query_value` and `num of`, land in R1.6, where deleting `make_num` + forces them.) Gate: one row per writer (`N6.*`, `N14.encode-kinds`, + `N14.store-exact-digits`, `gate.N14.db-param-exact`, `T1.twice-float`). +- **R2.2, consumers that assumed a finite double.** No token marks code that + merely receives a NaN or an inf once the clamp is gone: + - `values_equal_impl`'s identity shortcut (`if (a == b && ...) return + 1;`) would call a NaN box equal to itself. No program path reaches it + with one float box on both sides today (EQ compares two floats in its + fast path, and container comparisons pass fresh element views; read, + not probed, since no NaN exists yet), so R2.2 excludes floats from the + shortcut as #1417 excluded buffers, and extends the container reflexivity + scan to NaN floats (see §2.6a, R1.6z-D1/D2). The C slot test calls it with + one NaN box on both sides; + - C casts of a double to an integer type (undefined on NaN or inf): every + builtin argument goes through a §2.6 reader, which refuses a non-finite + value (N13); the internal casts left are the #1746 residue (R2.6); + - the sort comparators and `min`/`max` (Q8, decided): `sort` and `sort_by` + order floats by IEEE 754 `totalOrder`, the canonical NaN after +inf; + `min` and `max` propagate NaN. Gate: `N11.sort-total-order`, + `N11.min-max-nan`. + +--- + +## 3. Measured prototype + +**Question.** What does an SMI arm cost in the VM's ADD fast path, in which +order, and does inline SMI beat boxing every int? + +**Probe.** A 155-line standalone C file +(`/tmp/claude-1000/.../p2r0/bench/p2bench.c`, not committed). It mirrors +`ARITH_FAST` (vm.c:3290) on a two-slot stack. Each variant is one `noinline` +handler; the accumulator round-trips through memory as on the VM stack. It +is built with the runtime's flags: `-O2 -fstack-protector-strong +-D_FORTIFY_SOURCE=2 -fPIE`, gcc 13.3. All nine runs agree on the checksum +(2984375 at 1e6 iterations). + +**Instrument.** `perf` is unavailable (perf_event_paranoid=4). Instruction +counts come from callgrind at 1e6 and 2e6 iterations. The difference divided +by 1e6 cancels startup cost, and both runs scale exactly, so the counts are +deterministic. Wall time is the interleaved median of 7 runs at 2e7 +iterations. The box (Celeron N3350) carried load 1.2-3.8 from another job, +so read wall time as secondary. + +| variant | data | Ir/op | wall median ns/op | +|---|---|---:|---:| +| A: today, double-only + `num_guard` | float | **38** | 8.38 | +| B: float first, then SMI (range check) | float | 40 (+2) | 8.88 | +| C: SMI first, then float | float | 44 (+6) | 10.17 | +| D: float first, SMI via shifted add + overflow flag | float | 40 (+2) | 9.04 | +| E: no SMI, every int a boxed int64 (freelist hit) | float | 49 (+11: harness prologue, see below) | 8.76 | +| B | int | 50 | 10.97 | +| C | int | **42** | 10.46 | +| D | int | **44** | 10.45 | +| E | int | **77** | 17.24 | + +**Reading the disassembly.** +- B's +2 on floats is two register copies the compiler keeps live for the + SMI arm (`movq %xmm0,%rdx; mov %rdx,%rcx`). That is a real cost of sharing + the operand bits. +- E's +11 on floats is four callee-saved push/pop pairs in the per-variant + function prologue. That is an artifact of the harness: in the VM the + handler is inside `vm_run_ex`, which saves those registers once per call. + +**What it decides.** +1. **Check order: float first.** It costs the float path +2 Ir (5% of the + isolated op). SMI-first costs floats +6. Float-first keeps every existing + float program within 2 Ir per arithmetic op. +2. **Overflow check: the shifted add (D)** is 6 Ir cheaper on the int path + than a range check on the sum (B). Put both payloads in the top 48 bits + and let the hardware overflow flag fire exactly when the sum leaves 48 + bits. This is a round-5 fast-path choice, recorded here because it + decides the int arm's shape. +3. **Inline SMI against boxing every int: +33 Ir (+75%) and +65% wall per + add for boxing, even when every allocation hits a freelist.** This + confirms #1758's SMI tag decision with a number. + +**Scale.** The real interpreter spends 685 Ir per iteration of `loop while i +< N: i is i + 1` (callgrind on the existing main binary, `EIGS_JIT_OFF=1`, +1e5 against 2e5 iterations: 69,804,271 and 138,304,271). The SMI arm's +2 +on the float path is 0.3% of that. + +**What it does not decide.** Alt 1R against Alt 2. Their slot cost is +identical, and at the Value level the difference is one compare against two. +The choice rests on §2.3, not on speed. + +**A measured risk it exposes.** The same loop under the default JIT costs 402 +Ir per iteration (42,924,355 against 83,124,355; `[jit] compiled=1`). After +N1, `i` is an int: the JIT scanner stops at the new `OP_INT_ONE` (§2.7), and +the LT and ADD guards bail on the SMI (§4). Expect at least the +interpreter's 685 (+70%) on int loops until round 5's fast paths. + +The leaf evaluator (#366, §4.5) loses int helpers the same way: an int +literal makes its scanner refuse the chunk, and an int argument bails at the +first arithmetic or index op. `define inc(x) as: return x + 1` then takes the +generic call, the round trip #366 measured at about 200 ns per call +(vm.c:1836-1842), until round 5 gives the leaf int arms. + +The CodSpeed lane's two-sided Ir gate (#1220) will fire on its JIT-tier +targets with int loops: `scalar_loop`, `bench_perf`, `bench_dmg_shape`, +`bench_idxset` and the real DMG `cpu_instrs` row (codspeed.yml). The lane's +own measurement bounds the size: "JIT removes 48% of the instructions on +bench_dmg_shape, 11.8% on DMG itself" (codspeed.yml:65-67). Losing the JIT +on int code costs up to about 1.9x Ir there, and about 1.13x on DMG. That +must be accepted up front, not discovered. + +--- + +## 4. The JIT and the leaf evaluator + +Three evaluators compute numbers: the interpreter (`vm_run_ex`), the JIT +(`jit_compile_to_thunk`, admitted by `jit_supported_prefix`) and the leaf +evaluator (`vm_leaf_accessor_exec`, admitted by `chunk_scan_leaf_accessor`; +§4.5). The inventory's executor matrix (§1, check 3) lists which of them +implements each opcode. + +Round 1 needs correctness by bailing; fast paths are round 5. Every +consuming guard was read for how it treats a slot whose high 16 bits are +`0xFFFA` (SMI), and how it treats a heap slot whose `Value` is a boxed int. +Line numbers are jit.c at `0740af03`. + +### 4.1 Consuming guards: all bail on SMI + +| guard | the tag-range test | SMI (`0xFFFA`) | boxed int, Alt 1R (`VAL_INT`) | +|---|---|---|---| +| `emit_immediate_num_check_rax/rdx` (1625-1636) | `shr $48; cmp $0xFFF8; jae bail` | 0xFFFA >= 0xFFF8: **bail** | 0xFFFB >= 0xFFF8: **bail** | +| `emit_load_numeric_operand` (2343-2378) | `< 0xFFF8` is a double; `< 0xFFFB` bails (the comment reads "Tag 0xFFF8-0xFFFA (null/bool/SMI-reserved): not numeric"); `>= 0xFFFD` bails; else a heap Value must have `type == VAL_NUM && refcount >= 2` | **bail** | type is `VAL_INT`, not `VAL_NUM`: **bail** | +| `emit_jump_unless_bool_rax` (1911-1916) | `cmp $0xFFF9; jnz` | not a bool, falls to the num check: **bail** | not a bool: **bail** | +| `emit_dict_cache_probe` (2172-2185) | `cmp $0xFFFB; jne slow` | **slow path** (helper) | heap, then type `!= VAL_DICT`: **slow** | +| `OP_SET_LOCAL` in-place branch (3410-3475) | TOS `>= 0xFFF8` goes to the generic swap; the old slot must be a heap `VAL_NUM` with rc 1 | TOS SMI: **generic swap** (correct) | old slot `VAL_INT`: **generic swap** | +| `OP_INDEX_SET` inline (4067-4130) | index and value use `emit_immediate_num_check_*`; target `== 0xFFFB && type == VAL_BUFFER` | **helper** (C, the interpreter's semantics) | **helper** | +| `OP_LOCAL_DOT_GET` inline (3108-3170) | the field uses `emit_immediate_num_check_rax` | **helper** | **helper** | + +The refcount paths treat an SMI as an immediate, which is correct (it has no +refcount): +- `emit_conditional_incref_rax` (1279) and `emit_conditional_decref_rsi_hook` + (2028) test `cmp $0xFFFB; jb skip`, and 0xFFFA < 0xFFFB. +- The binop commit's OR-of-tags exit (2391, 2421) already lists "bool|smi + tags OR-ing up to 0xFFFB" as taking the per-slot path, which handles every + tag. + +The opcodes that consume through these guards are: +- ADD, SUB, MUL, DIV, MOD, EQ, NE, LT, GT, LE and GE through + `emit_load_numeric_operand`; +- BAND, BOR, BXOR, SHL, SHR, NEG, NOT, BNOT and JUMP_IF_* through + `emit_immediate_num_check_*`. + +ITER_NEXT, INDEX_GET and LOCAL_IDX_GET are C helper calls (`jit_helper_*` in +vm.c). They are migrated with the C code, and the inventory counts their +sites (for example `jit_helper_index_get` 17). + +**Finding: no consuming guard reads SMI tag bits as a double.** Under Alt 2 +three would misread a boxed int (§2.3). Under Alt 1R none does. + +### 4.2 Producer emitters: these do not bail + +The emitter writes bits directly, so a guard cannot protect these. Round 0 +re-meant two of them; round 2 changes none of their meanings, so none can be +missed (§2.7): + +| emitter | today | round 1 | +|---|---|---| +| `OP_NUM_ZERO`/`OP_NUM_ONE` (2797-2814) | pushes the double bits of 0.0 and 1.0 | **Unchanged: they keep meaning float 0.0 and 1.0** in all three executors (this emitter, the interpreter vm.c:3257, the leaf vm.c:1872-1877). Today the compiler (compiler.c:2073-2074) emits them for any literal `== 0.0` or `== 1.0`, so `0` and `0.0` share an opcode; from R1.3 the compiler emits them only for a float literal, and `0`/`1` become the new `OP_INT_ZERO`/`OP_INT_ONE`. | +| `OP_CONST` (2816-2838) | `VAL_NUM`: the bits of `num_`; else pushes a `TAG_HEAP` pointer plus an incref | **Unchanged**: an int is never an `OP_CONST` operand. Its `VAL_NUM` test is renamed `VAL_FLOAT` (forced), and `last_imm` (4375) stays 1 exactly for a float constant. | +| `OP_INT_ZERO`/`OP_INT_ONE`/`OP_INT_CONST` (new, R1.3) | none | **Refused by the scanner** (its `default:`), so an int literal ends the compiled prefix and runs interpreted. Round 5 emits them: SMI bits computed by `slot_from_int` at compile time, or a `TAG_HEAP` box for an int64 outside the SMI range; `last_imm` is then 1 exactly when the bits are immediate (the jit skill's lockstep rule). | +| BAND/BOR/BXOR/SHL/SHR (3716-3768), BNOT (3819-3830) | double operands, cvttsd2si, op, cvtsi2sd: a **double** result | N8 makes bitwise results ints. Round 1: **the scanner refuses these opcodes** (the op interprets) until round 5 emits SMI results. | +| MOD (3511-3585) | `call fmod`, which truncates toward zero | N4 makes `%` floor modulo (sign of the divisor), so fmod is wrong for mixed-sign floats (`-7.5 % 2` gives -1.5; Lua gives 0.5). Round 1: **the scanner refuses MOD** (§8 R1.3; the MAP's `N4.fn-floor-mod` witness expects it); round 5 may emit the floor correction. | + +### 4.3 For round 2 (N11/N12) + +The experiment already did each of these and the branch carries the code: +- `max_normal_bits` (2634) becomes inf's bits, so a NaN result bails; +- NEG gains a NaN bail, because a flipped canonical NaN spells + `SLOT_NULL_BITS`; +- comparisons gain a `jp` bail; +- CONST canonicalizes a NaN constant. + +### 4.4 For round 5 (recorded so it is not rediscovered) + +An int fast path must bail when its result leaves the SMI range. A boxed +result is a heap push, and `last_imm = 1` on ADD (4366) would then skip its +decref at the following POP: one leak per iteration (the #231 shape). +`jit_smoke` needs SMI rows for every guard above, each with a value that +differs only in the tag bits, so a guard that compared the wrong bits would +fail. The jit skill's wide-bit rule applies. + +### 4.5 The leaf evaluator (`vm_leaf_accessor_exec`, #366) + +**What it is.** `chunk_scan_leaf_accessor` (chunk.c:1072-1134) runs once per +compiled function (compiler.c:2703 and 2767). It flags a chunk `leaf_accessor` when the body is one +side-effect-free expression over the parameters ending in `RETURN`: at most +128 bytes of code, at most 8 parameters, stack depth at most 8, no +defaulted parameters, and only +the 11 opcodes `LINE`, `CONST` (a `VAL_NUM` constant only), `NUM_ZERO`, +`NUM_ONE`, `GET_LOCAL` (a parameter), `LOCAL_DOT_GET`, `INDEX_GET`, `ADD`, +`SUB`, `MUL`, `RETURN`; its `default:` refuses everything else. An +exactly-fed call to such a chunk is then computed by +`vm_leaf_accessor_exec` (vm.c:1856-1971) against the caller's stack, with no +frame, no env, no `OP_LINE` and no dispatch. Its contract: return 1 with the +generic call's stack effect, or return 0 having touched nothing, and the +generic path re-executes. + +**Where it runs.** Both call paths try it before the generic call: +`CASE(CALL)` (vm.c:4253) and `jit_helper_call` (vm.c:2503, the JIT's calls). +The gate is `!g_sandbox_active && leaf_accessor && argc == param_count && +!g_trace_hist`. So it computes `define sq(x) as: return x * x` and +`one of 7` in **every** tier, `EIGS_JIT_OFF=1` included. Probed on the main +binary: 200 calls of a leaf `sq` leave its chunk with no entries at all in +`EIGS_JIT_HOT` under all three tiers (the leaf path never counts an entry; +both generic paths do, vm.c:2617 and vm.c:4353). + +**It is off under `EIGS_TRACE`.** `g_trace_hist` is set while recording, so a +recorded run computes every leaf call on the generic path and a live run +computes it in the leaf. Probed: the same program records `sq` with 200 +generic entries (`jit yes`, `adv RET`). A recorded run and a live run +therefore execute different evaluators. That is correct only while the two +agree on every value, so it gets a gate: the `trace` tier of §9.2 runs every +row with the leaf off and requires byte-identical output. Round 1 keeps the +leaf off under trace (the tape records every line, and the leaf skips +`OP_LINE`). + +**Every arm** (vm.c line, today, round 1): + +| arm | today | round 1 | +|---|---|---| +| `LINE` (1863) | skips the operand | unchanged | +| `CONST` (1866) | `slot_from_num(VAL_NUM_RAW(constant))`; the scanner admitted only a `VAL_NUM` constant (chunk.c:1090) | renamed `slot_from_f64(VAL_F64_RAW(...))`; the scanner's test becomes `!= VAL_FLOAT`, so an int constant is refused (it is an `OP_INT_CONST` anyway) | +| `NUM_ZERO` (1872), `NUM_ONE` (1875) | `slot_from_num(0.0)` / `(1.0)` | renamed `slot_from_f64`; **meaning unchanged** (float 0.0/1.0) | +| `OP_INT_ZERO/ONE/CONST` | do not exist | **not admitted**: the scanner's `default:` refuses the chunk, so an int literal sends every call down the generic path. Round 5 adds scanner and evaluator arms together (the inventory requires both directions) | +| `GET_LOCAL` (1878) | borrows the argument slot | unchanged (an SMI argument is a valid slot) | +| `LOCAL_DOT_GET` (1883) | the target must be a pointer to a dict, else bail; a missing field is null; a field is borrowed as its bits | unchanged: an SMI field is borrowed as an SMI (§2.5 row `guarded`) | +| `INDEX_GET` (1907) | the index through `slot_as_double` (an immediate or a boxed `VAL_NUM`) then `vm_index_is_int`; a list element is borrowed; a buffer element is `slot_from_num(buffer_read_num(...))` | `slot_as_double` is deleted (R1.1), so this arm stops compiling. It reads the index with `slot_f64_value` and **bails on an int index**; the buffer element is `slot_from_f64` (Q2: f64 storage), read through `buffer_read_f64` once R1.6 renames `buffer_read_num`. Round 5 adds the SMI index | +| `ADD`/`SUB`/`MUL` (1933) | both operands through `slot_as_double`; `r = a op b`; the underflow flag for MUL (#1079); `slot_from_num(num_guard(r))` | stops compiling (R1.1). Reads with `slot_f64_value` and **bails unless both operands are floats**, so an int or mixed operation runs on the generic path with N2/N3/(A) semantics. Round 5 adds int arms whose overflow bails (never raises inside the leaf: the generic path raises with the caller's line). R2.2 deletes `num_guard` here with the others | +| `RETURN` (1957) | increfs the result, pops args and fn, pushes the result | unchanged (`slot_incref` ignores an SMI) | +| `default` (1966) | unreachable; returns 0 | unchanged | + +**Its witness.** No counter exists for the leaf. The design uses the hot-chunk +table instead of adding one (§9.2): both generic call paths count an entry +and the leaf never does, so "the callee has no entries in the normal run, and +K entries in the leaf-off (`trace`) run" proves that all K calls were +computed by the leaf. Probed on main: `sq` absent with the leaf on, `sq +200` with it off. + +--- + +## 5. N12 and SMI share the NaN space + +- **The canonical quiet NaN is `0x7FF8000000000000`**, with the sign bit + clear, as in #1746 and the `exp-ieee-p1` `SLOT_CANON_NAN`. +- **Every tag has the sign bit set** (high 16 bits `0xFFF8`-`0xFFFF`). The + SMI tag is `0xFFFA`. +- So canonical NaN (high 16 = `0x7FF8`) and every tag, SMI included, differ + in bit 63. **They cannot collide.** +- The patterns that *can* collide are NaNs with the sign set and the quiet + bit set (high 16 `0xFFF8`-`0xFFFF`). This includes x86's default NaN + `0xFFF8000000000000`, which **is** `SLOT_NULL_BITS`, and any NaN built + from bytes (`f64_from_bits of 0xFFFA000000000005` spells SMI 5). +- `slot_from_f64` is the single double-to-slot writer, and the JIT bails + before storing a NaN result, so no such pattern reaches a slot. Every NaN + is rewritten to `0x7FF8000000000000`. The other negative NaNs (high 16 + `0xFFF0`-`0xFFF7` with a nonzero mantissa: the signalling ones) do not + collide but are canonicalized too: one NaN. +- `-0.0` (`0x8000...`) and `-inf` (`0xFFF0000000000000`) are below `0xFFF8`, + so they are ordinary doubles. + +**`f64_bits of x`** returns the IEEE bits as an int, exact (int64 +reinterpretation, two's complement). Cases: +- for a float held in a slot, the stored bits; for NaN that is always + `0x7FF8000000000000` = 9221120237041090560, which is outside the SMI + range, so a boxed int; +- `f64_bits of -0.0` = INT64_MIN (boxed); +- `f64_bits of 1.0` = 4607182418800017408 (boxed). + +**`f64_from_bits of i`** builds the double from `i`'s 64 bits. If they spell +a NaN, the result enters a slot as the canonical NaN. Its payload is gone +from the float but still held by the int `i`. So `f64_bits of (f64_from_bits +of i) == i` for every `i` that is not a non-canonical NaN pattern, and +payload fidelity goes through ints (N12: "Ints are never NaN-canonicalized"). + +**Buffers** hold raw doubles and are not slots, so a buffer element may hold +any payload. Reading it as a float (`buf_get`) puts it in a slot, so it is +canonicalized. Reading it through the new bits accessor (N12: "Buffer +elements are also readable and writable as bits") returns the stored bits +exactly as an int. That is the path the bar critic found missing: payload +`0x7FF0...01` came back canonical through every route on the prototype. + +--- + +## 6. Builtin classification + +**Classes.** The reference for counterparts is Lua 5.4 (manual §6.7, +`math`; §3.4.1-3.4.3, operators and coercions; §6.4, `string`; §6.6, +`table`). + +- **int-preserving:** returns an exact int. + - Where the inputs are ints: `abs`, `min`, `max`, `sum`, `floor`, `ceil`, + `round`. These follow §6.7's "integer/float" annotation: "integer + results for integer arguments". `math.min`/`math.max` return the winning + argument. + - Where the result is a count, code or index by definition: `len`, + `range`, `ord`, `index_of`, handles. These follow Lua's `#` and + `string.byte`. + - Rounding (§6.7): "math.ceil, math.floor, and math.modf return an integer + when the result fits in the range of an integer, or a float otherwise". + `round` has no Lua counterpart and follows floor and ceil (Q4, + decided: half away from zero, an int when it fits int64). +- **float-producing:** ints convert to float and the result is a float. + Examples: Lua's `math.sin`, `sqrt`, `log`, `exp`; `/` and `^`, which + "always convert integer operands to floats" (§3.4.1); and the float-tensor + and audio-sample builtins (buffer storage is float). +- **int-requiring:** an argument is integer-valued, so it converts by N5's + exact rule, then the C type's range: an index, count, size, code, bit + pattern, byte value, handle, port or seed (Q1, decided: the parameter's + meaning decides, not the C type). Examples: Lua's string positions, + `table.insert`/`table.remove` positions, bitwise operands (§3.4.2), + `math.random(m, n)` bounds, `math.randomseed`, and `string.char`, which + raises on `65.5` and on `256` (probed, 5.4.9). +- **other:** everything else that takes or returns numbers: + - formatting (N6); + - JSON, store and db codecs (N13, N14); + - parsing (`num`, N10); + - `type` ((B)); + - continuous parameters (Q1, decided): gfx coordinates and sizes, colour + channels, durations and audio gain accept an int or a float. Where the + C API needs an int the value rounds to nearest (half away from zero, as + `round`), replacing today's truncation toward zero, which gets negative + coordinates wrong. What a finite value outside the C range does follows + the quantity (Q7, decided): geometry is clipped as a whole shape in + float space, a bounded quantity is clamped, a magnitude raises `value` + (§2.6). (Byte consumers, which truncated and wrapped modulo 256 here in + round 0, are int-requiring under Q1.) + +**Exact readers completed in R1.2.** `sort` and `sort_by` compare int +values and numeric keys exactly, and `json_build` and `json_path` format an +int as exact digits. These final semantics are retained: rounding through +the double bridge would lose information above 2^53 (principle 2, No silent +wrong answers). The sort comparators and `json_build` become reachable with +R1.3's first int literals, so MAP `N2.sort-exact` is R1.3. `json_path`'s +reader is ready too; its parser still starts producing integer values in +R1.6. Float formatting stays on its existing path until R1.7, and the +remaining builtin readers keep the bridge until R1.6. + +**The `loss` column.** This records the original inventory tree's risk, +rather than remaining work in R1.2; the completed readers above retain their +historical marks. `LOSS` marks a builtin whose inventoried body reads its +argument through a double accessor, or builds its result from a computed +double, on a path where its class needs an exact int. An int beyond 2^53 +would then come out silently wrong if the body were kept and its accessor +re-pointed to int-to-float conversion. Under Alt 1R the old accessors no +longer compile, so every row is revisited anyway; `LOSS` rows are where the +mechanical re-point would be wrong. Builtins that only call the shared +formatter or equality (`print`, `str`, `join`, `list_contains`) are `-`, +because their exactness change happens once, in `eigs_num_text` or +`values_equal_impl`. + +Two consequences of the definition, made explicit in design round 2: +- **The Q1 rows are not LOSS.** A byte, a count, a handle, a port or a C + `int` is range-checked after the exact rule, and every int beyond 2^53 is + outside every such range, so the double path cannot return a wrong + in-range value: it raises either way. What WAS lossy beyond 2^53 is the + modulo-256 wrap the byte consumers did, and Q1 deletes it. Seeds are the + exception (a seed's range is all of int64), so `seed_random` and + `task_sched_seed` stay LOSS. +- **A writer that loses digits of a double is LOSS even without ints.** + `store_put`/`store_update` (the record encoder's `%.15g`) and + `db_execute`/`db_query_json`/`db_query_value` (the parameter binder's + `%g`) lose digits on main and on the tag today, filed as + [#1762](https://github.com/InauguralSystems/EigenScript/issues/1762). + Round 0 missed them; the critic found them. R1.7, N14's int half, fixes + both writers by routing them through the one N6 number writer: + `store_json_encode` (ext_store.c:184-202) writes an int as exact digits + and a float as its shortest round-trip repr, replacing the int32 `%d` arm + and `%.15g`; `db_build_query` (ext_db.c:57-58) binds the same text + instead of `"%g"`. N13's refusal of a non-finite value stays inside the + encoder (R2.3). If #1762 lands on main first (it does not depend on ints), + R1.7 inherits the fix and only adds the int arm. Its MAP entries are the + row `N14.store-exact-digits` and the gate `gate.N14.db-param-exact`. + +**The `q7` column** (design round 3) names the Q7 class of each continuous +parameter a builtin converts: an ordered subset of `geometry`, `bounded`, +`magnitude`, or `-`. The inventory refuses any other token, and checks the +column against the note both ways: a row's note says "continuous" exactly +when its `q7` column is not `-`. Counts: geometry 9, bounded 13, magnitude 13. + +**Counts** (from the table below, checked by `tools/phase2_inventory.py`, +which prints them): 309 numeric builtins (including N9, R1.6g; S10-D2 retires +`proc_read_buf`). By class: 37 int-preserving, 58 float-producing, +161 int-requiring, 53 other. The decided Q1 moved 16 rows +from other to int-requiring (counts, byte values, handles, flags and +selectors); `ppu_render_frame` is a new int-requiring row; design round 3 +moved `buf_from_pcm16le` from float-producing to int-requiring (its inputs are +byte values, #1758 comment 6174964306) and corrected `gather` (its indices +truncate today). The current table has 37 LOSS rows: +- arithmetic: `abs`, `add`, `subtract`, `multiply`, `negative`, `sum`, + `num_copy`; +- min/max and rounding: `min`, `max`, `floor`, `ceil`, `round`; +- bitwise: `sign_extend`, `hex`; +- clock, RNG and seeds: `monotonic_ns`, `random_int`, `seed_random`, + `task_sched_seed`; +- shared counters and HTTP header decoding: `shared_incr`, `http_post` (R1.6b-D3); +- integer scanners: `scan_ints`, `scan_int_tokens`; +- ordering: `sort`, `sort_by`; +- store keys: `store_get`, `store_delete`, `store_update`, plus + `store_query` and `db_query_value`, which decode integer text with + `strtod`; +- writers that drop digits today (#1762): `store_put` (and `store_update`) + through `%.15g`, `db_execute` and `db_query_json` (and `db_query_value`) + through `%g`; +- JSON and parsing: `json_decode`, `json_path`, `num`, `json_encode`, + `json_build`. + +Probes on the main binary (0.46.0, built 2026-10-13; not the tag) show the +current state behind several rows: +- `str of 3.0` prints `3`. +- `json_encode of [3.0, 0.1]` gives `[3,0.1]`. +- `9007199254740993 == 9007199254740992` is `true`: the lexer rounds. +- `store_get of [s, "c", 1.9]` finds record `"1"` (a silent truncating key). +- `store_get of [s, "c", 4294967297]` returns `null`, because the `(int)` + cast is out of range (a pre-existing defect, filed as + [#1760](https://github.com/InauguralSystems/EigenScript/issues/1760); R1.6's + `eigs_int_arg` for the key closes it if it is still open then). + + +| builtin | class | loss | q7 | note | +|---|---|---|---|---| +| `abs` | int-preserving | LOSS | - | Lua math.abs (integer/float); abs of INT64_MIN raises (N4b) | +| `acos` | float-producing | - | - | Lua math.acos | +| `add` | int-preserving | LOSS | - | scalar arm mirrors `+` (overflow raises, (A)); buffer arms stay float | +| `append` | other | - | - | appends any value; VAL_NUM appears only as a diagnostic default | +| `asin` | float-producing | - | - | Lua math.asin | +| `assert` | other | - | - | message via value_to_string (N6) | +| `atan` | float-producing | - | - | Lua math.atan | +| `atan2` | float-producing | - | - | Lua math.atan(y, x) | +| `audio_capture_open` | int-requiring | - | - | freq (samples per second): N5, then the C int range; channels: N5, then the domain 1..255 (Q1; R1.6e-D1), checked before SDL/device/tape operations; returns an int device id | +| `audio_envelope` | float-producing | - | magnitude | float samples; the attack and release times are continuous durations: a sample count the C int cannot hold raises (Q7 magnitude) | +| `audio_gain` | float-producing | - | - | float samples | +| `audio_mix` | float-producing | - | - | float samples | +| `audio_music_play` | int-requiring | - | - | loop count: N5, then the C int range (Q1) | +| `audio_music_volume` | other | - | bounded | the volume is a continuous gain: clamped to SDL_mixer's 0..128, then rounded (Q7 bounded) | +| `audio_noise` | float-producing | - | magnitude | float samples; the duration is continuous: a sample count the C int cannot hold raises (Q7 magnitude) | +| `audio_open` | int-requiring | - | - | freq (samples per second): N5, then the C int range; channels: N5, then the domain 1..255 (Q1; R1.6e-D1), checked before SDL/device/tape operations; returns an int device id | +| `audio_pause` | int-requiring | - | - | the pause flag is discrete: N5 (Q1; today `(int)0.5` silently un-pauses) | +| `audio_play` | other | - | bounded | the samples are continuous: clamped to [-1, 1], then rounded to int16 (Q7 bounded; today they truncate); returns a channel number | +| `audio_play_loop` | int-requiring | - | bounded | loop count: N5, then the C int range (Q1); the samples are continuous: clamped and rounded to int16 (Q7 bounded); returns a channel | +| `audio_queue_size` | int-preserving | - | - | byte count (computed in a double, below 2^53) | +| `audio_saw` | float-producing | - | magnitude | float samples; the duration is continuous: a sample count the C int cannot hold raises (Q7 magnitude) | +| `audio_sine` | float-producing | - | magnitude | float samples; the duration is continuous: a sample count the C int cannot hold raises (Q7 magnitude) | +| `audio_square` | float-producing | - | magnitude | float samples; the duration is continuous: a sample count the C int cannot hold raises (Q7 magnitude) | +| `audio_stop` | int-requiring | - | - | channel number | +| `audio_stream_open` | int-requiring | - | - | freq (samples per second): N5, then the C int range; channels: N5, then the domain 1..255 (Q1; R1.6e-D1), checked before SDL/device/tape operations; returns an int device id | +| `audio_stream_push` | other | - | bounded | the samples are continuous: clamped to [-1, 1], then rounded to int16 (Q7 bounded) | +| `audio_stream_queued` | int-preserving | - | - | sample count | +| `audio_sweep` | float-producing | - | magnitude | float samples; frequencies and amplitude stay floats; the duration is continuous: a sample count the C int cannot hold raises (Q7 magnitude); the wave selector is integer-valued: N5 (Q1) | +| `audio_volume` | int-requiring | - | bounded | channel index: N5 (Q1); the volume is a continuous gain: clamped to the mixer's range (Q7 bounded) | +| `bit_and` | int-requiring | - | - | Lua `&` (§3.4.2): exact int64 operands, exact int64-pattern result (N8, R1.5) | +| `bit_not` | int-requiring | - | - | Lua unary `~` | +| `bit_or` | int-requiring | - | - | Lua `\|` | +| `bit_shl` | int-requiring | - | - | Lua `<<` (R1.5): count >=64 or <=-64 gives 0; a negative count shifts logically right | +| `bit_shr` | int-requiring | - | - | Lua `>>` (N8, R1.5) is logical; count >=64 or <=-64 gives 0; a negative count shifts left | +| `bit_xor` | int-requiring | - | - | Lua binary `~` | +| `buf_copy` | int-requiring | - | - | offsets and counts | +| `buf_deinterleave` | int-requiring | - | - | channel count and index | +| `buf_dot` | float-producing | - | - | buffer dot product | +| `buf_fill` | float-producing | - | - | fills float storage; offsets by N5 | +| `buf_from_list` | float-producing | - | - | buffer storage is float: an int element converts (Q2) | +| `buf_from_pcm16le` | int-requiring | - | - | its input elements are byte values: each is read through the Q1 byte reader (N5, then 0..255), so a byte of 300 or 1.5 raises `value` (#1758 comment 6174964306; today 300 and 1.5 decode silently); the decoded samples are floats | +| `buf_get` | int-requiring | - | - | index; returns the float element | +| `buf_len` | int-preserving | - | - | element count | +| `buf_mix` | float-producing | - | - | float storage | +| `buf_peak` | float-producing | - | - | float max abs | +| `buf_resample_linear` | float-producing | - | - | float storage | +| `buf_scale_range` | float-producing | - | - | float storage | +| `buf_set` | int-requiring | - | - | index; the value converts to float storage (Q2) | +| `buf_to_pcm16le` | other | - | bounded | the samples are continuous: clamped to [-1, 1] and rounded (Q7 bounded, today's behaviour); the result is byte values | +| `buffer` | int-requiring | - | - | size or [rows, cols] | +| `build_corpus` | int-requiring | - | - | counts; returns counts | +| `ceil` | int-preserving | LOSS | - | Lua math.ceil: an int when the result fits int64, else a float | +| `channel` | int-preserving | - | - | handle dict with int id/gen fields | +| `channel_closed` | int-requiring | - | - | channel handle ids | +| `char_at` | int-requiring | - | - | string position (Lua string.sub) | +| `chr` | int-requiring | - | - | byte 1..255, already exactness-checked (Lua string.char) | +| `clock_unix` | float-producing | - | - | fractional seconds | +| `close_channel` | int-requiring | - | - | channel handle ids | +| `copy_into` | int-requiring | - | - | offset; values convert to float storage | +| `cos` | float-producing | - | - | Lua math.cos | +| `db_execute` | other | LOSS | - | binds every numeric parameter with `%g`, 6 significant digits (ext_db.c:57-58, #1762). R1.7: the N6/N14 writer | +| `db_query_json` | other | LOSS | - | binds parameters with `%g` (#1762); encodes rows (N14) | +| `db_query_value` | other | LOSS | - | binds parameters with `%g` (#1762); decodes a numeric column with strtod, so integer text beyond 2^53 rounds | +| `dispatch` | int-requiring | - | - | integer table key, already exactness-checked | +| `divide` | float-producing | - | - | mirrors `/` (Lua §3.4.1: always float) | +| `dot` | float-producing | - | - | buffer dot product | +| `eigen_eval_loss` | float-producing | - | - | token ids are indices; the loss is a float | +| `eigen_generate` | int-requiring | - | - | token ids and max_tokens are integer-valued (N5, Q1); temperature and top_p are floats | +| `eigen_model_load` | other | - | - | parses model JSON numbers with strtod | +| `exec_capture` | other | - | magnitude | the timeout is a continuous duration in seconds: a poll interval the C int cannot hold raises (Q7 magnitude); returns exit status ints | +| `exit` | int-requiring | - | - | exit code: N5, then the C int range (Q1) | +| `exp` | float-producing | - | - | Lua math.exp | +| `f64_from_bytes` | int-requiring | - | - | 8 byte values: N5, then 0..255 (Q1; the modulo-256 wrap is gone); the float result's NaN is canonical (N12) | +| `f64_to_bytes` | other | - | - | float to 8 byte ints; f64_bits (N12) is the int-valued sibling | +| `fill` | int-requiring | - | - | count | +| `floor` | int-preserving | LOSS | - | Lua math.floor: an int when the result fits int64, else a float | +| `gather` | int-requiring | - | - | each index, from a list or an index buffer, is integer-valued: N5 raises on 1.5 (today `flat_index_at` truncates: `gather of [t, ib]` with `ib[0]` = 1.5 returns `[11, 22]`, probed); returns elements unchanged | +| `get_at` | int-requiring | - | - | index, row and col; returns the element | +| `get_observer_thresholds` | float-producing | - | - | observer thresholds (O3 drops h_low) | +| `gfx_circle` | other | - | geometry,bounded | centre and radius are continuous geometry: the circle is clipped in float space against the drawable area, then rounded; the colour channels are continuous and bounded: clamped to 0..255, then rounded (Q7) | +| `gfx_clear` | other | - | bounded | the colour channels are continuous and bounded: clamped to 0..255, then rounded (Q7) | +| `gfx_clip` | other | - | geometry | the clip rect is continuous geometry: intersected with the drawable area in float space, then rounded (Q7) | +| `gfx_delay` | other | - | magnitude | the milliseconds are a continuous duration: rounded; a delay Uint32 cannot hold raises (Q7 magnitude) | +| `gfx_fb` | int-requiring | - | geometry | w and h lay out the pixel buffer (integer-valued: N5, Q1); its elements are palette bytes read through the Q1 byte reader; dx, dy and scale are continuous geometry: the destination rect is clipped in float space, then rounded (Q7) | +| `gfx_line` | other | - | geometry,bounded | the endpoints are continuous geometry: the segment is clipped in float space against the drawable area, then rounded; the colour is bounded (Q7) | +| `gfx_open` | other | - | magnitude | window width and height are continuous sizes with no drawable area yet to clip against: rounded, and a size the C int cannot hold raises (Q7 magnitude) | +| `gfx_point` | other | - | geometry,bounded | the position is continuous geometry: a point outside the drawable area draws nothing; the colour is bounded (Q7) | +| `gfx_poll` | int-preserving | - | - | event fields x, y, scancode, button are ints | +| `gfx_read` | other | - | geometry | the origin is continuous geometry: rounded, and a point outside the drawable area reads nothing (Q7); returns the channel values as ints | +| `gfx_rect` | other | - | geometry,bounded | the rect is continuous geometry: intersected with the drawable area in float space, then rounded; the colour is bounded (Q7) | +| `gfx_rrect` | other | - | geometry,bounded | the rect and radius are continuous geometry: clipped in float space, then rounded; the colour is bounded (Q7) | +| `gfx_text` | other | - | geometry,bounded | the position and scale are continuous geometry: each glyph cell is clipped in float space, then rounded; the colour is bounded (Q7) | +| `gfx_text_height` | int-preserving | - | magnitude | pixel height, an int; the scale is continuous, and a measurement has no drawable area to clip against: rounded, and a scale the C int cannot hold raises (Q7 magnitude) | +| `gfx_text_width` | int-preserving | - | magnitude | pixel width, an int; the scale is continuous, and a measurement has no drawable area to clip against: rounded, and a scale the C int cannot hold raises (Q7 magnitude) | +| `gfx_ticks` | int-preserving | - | - | SDL ticks (Uint32 ms) | +| `hex` | int-requiring | LOSS | - | refuses above 2^53 by a double compare, so int 2^53+1 reads as 2^53 and passes silently | +| `http_early_bind` | int-requiring | - | - | port | +| `http_post` | other | LOSS | - | header JSON ints decode exactly (N14, B6); lossy before R1.6 | +| `http_route` | other | - | - | formats via value_to_string | +| `http_route_authed` | other | - | - | formats via value_to_string | +| `http_serve` | int-requiring | - | - | port | +| `http_static` | other | - | - | formats via value_to_string | +| `index_of` | int-preserving | - | - | byte index or -1 | +| `inflate` | int-requiring | - | - | byte values: N5, then 0..255 (Q1; the modulo-256 wrap is gone) | +| `join` | other | - | - | elements via value_to_string (N6) | +| `json_build` | other | LOSS | - | N14 encoder; exact int text arm written in R1.2, live from R1.3; float text stays on the bridge until R1.7 | +| `json_decode` | other | LOSS | - | N14 decoder; integer text decodes through strtod (a double) | +| `json_encode` | other | LOSS | - | N13/N14 encoder; reads values through eigs_num_arg; its `default:` writes null (builtins.c:998) | +| `json_path` | other | LOSS | - | exact int text reader written in R1.2; decoder int production remains R1.6, float text R1.7 (N14, N6) | +| `leaky_relu` | float-producing | - | - | ML activation (float tensors; no Lua counterpart) | +| `len` | int-preserving | - | - | count (Lua `#` is an integer) | +| `list_contains` | other | - | - | equality via values_equal (N2) | +| `list_index_of` | int-preserving | - | - | returns an index; equality via values_equal (N2) | +| `list_insert_at` | int-requiring | - | - | position (Lua table.insert) | +| `list_remove_at` | int-requiring | - | - | position (Lua table.remove) | +| `list_slice` | int-requiring | - | - | start and end indices | +| `list_truncate` | int-requiring | - | - | new length | +| `log` | float-producing | - | - | Lua math.log; log of 0 is -inf (N11) | +| `log_softmax` | float-producing | - | - | ML (float) | +| `matmul` | float-producing | - | - | float tensors | +| `matmul_at` | float-producing | - | - | float tensors | +| `matmul_bt` | float-producing | - | - | float tensors | +| `max` | int-preserving | LOSS | - | Lua math.max returns the winning argument with its own subtype; empty input raises `value` in both call forms and strict modes (R1.6b-D2, #1895); a NaN argument makes the result NaN (Q8: IEEE 754-2019 maximum) | +| `mean` | float-producing | - | - | arithmetic mean | +| `min` | int-preserving | LOSS | - | Lua math.min; empty input raises `value` in both call forms and strict modes (R1.6b-D2, #1895); a NaN argument makes the result NaN (Q8: IEEE 754-2019 minimum) | +| `model_load_weights` | other | - | - | alias of eigen_model_load | +| `monotonic_ms` | float-producing | - | - | fractional milliseconds | +| `monotonic_ns` | int-preserving | LOSS | - | exact below 2^53 ns (104 days of uptime), a rounded double after; an exact int under N1 | +| `multiply` | int-preserving | LOSS | - | scalar arm mirrors `*` (overflow raises, (A)); buffer arms float | +| `native_train_step_builtin` | int-requiring | - | - | token ids are integer-valued (N5, Q1); lr is a float (a numeric string is accepted through strtod) | +| `nearest_in_range` | float-producing | - | - | geometry floats; the `index` field is an int | +| `nearest_in_range_all` | float-producing | - | - | geometry floats; the `index` field is an int | +| `negative` | int-preserving | LOSS | - | mirrors unary `-`; negating INT64_MIN raises (N4b) | +| `net_accept` | int-requiring | - | - | socket handle | +| `net_close` | int-requiring | - | - | socket handle | +| `net_dial` | int-requiring | - | - | port; returns a handle | +| `net_listen` | int-requiring | - | - | port; returns a handle | +| `net_port` | int-requiring | - | - | socket handle; returns a port | +| `net_recv` | int-requiring | - | - | handle and max bytes | +| `net_send` | int-requiring | - | - | the handle and the byte values: N5, bytes then 0..255 (Q1; the modulo-256 wrap is gone) | +| `norm` | float-producing | - | - | L2 norm | +| `num` | other | LOSS | - | N10 parser: integer text decodes through strtod today (a double) | +| `num_copy` | int-preserving | LOSS | - | copies a number and must keep its subtype; today re-boxes through a double | +| `numerical_grad` | float-producing | - | - | finite differences | +| `numerical_grad_cols` | float-producing | - | - | finite differences; column indices int | +| `numerical_grad_rows` | float-producing | - | - | finite differences; row indices int | +| `observe` | other | - | - | observer readouts (O4) | +| `ord` | int-preserving | - | - | byte code (Lua string.byte) | +| `pi` | float-producing | - | - | Lua math.pi | +| `pow` | float-producing | - | - | mirrors `^` (Lua §3.4.1: exponentiation is always float) | +| `ppu_render_frame` | int-requiring | - | - | reads the emulated memory buffer through `buffer_read_byte`: byte values by N5, then 0..255 (Q1; the modulo-256 wrap is gone) | +| `print` | other | - | - | N6 printing via value_to_string | +| `random` | float-producing | - | - | Lua math.random() in [0,1) | +| `random_hex` | int-requiring | - | - | digit count | +| `random_int` | int-requiring | LOSS | - | Lua math.random(m, n) is an integer; bounds read as doubles today | +| `random_normal` | float-producing | - | - | dims int; samples float | +| `range` | int-preserving | - | - | bounds by N5; the elements are ints (floats today, inside int range) | +| `read_bytes` | int-preserving | - | - | byte values 0..255 | +| `read_bytes_buf` | int-requiring | - | - | cap and length; bytes stored as floats | +| `read_text` | other | - | - | string result; numeric over-cap size is internal tape metadata (#1668), with no numeric argument or successful numeric result | +| `record_history` | other | - | - | truthiness flag in; previous flag out | +| `recv` | int-requiring | - | - | channel handle ids | +| `recv_timeout` | int-requiring | - | magnitude | the channel handle: N5 (Q1); the milliseconds are a continuous duration: a timeout the C long cannot hold raises (Q7 magnitude) | +| `relu` | float-producing | - | - | ML activation (float tensors; no Lua counterpart) | +| `reshape` | int-requiring | - | - | rows and cols | +| `round` | int-preserving | LOSS | - | no Lua counterpart; follows floor and ceil (Q4) | +| `sandbox_run` | int-requiring | - | - | iteration, byte and work limits; returns line ints | +| `scan_int_tokens` | int-preserving | LOSS | - | integers accumulate in a double | +| `scan_ints` | int-preserving | LOSS | - | integers accumulate in a double | +| `scatter_add` | float-producing | - | - | indices are exactness-checked today, but raise an index error for 1.5; R1.6 reads them through the N5 index reader (`value`); float accumulation | +| `seed_random` | int-requiring | LOSS | - | `(long)` of a double; Lua randomseed takes integers | +| `send` | int-requiring | - | - | channel handle ids | +| `set_at` | int-requiring | - | - | index, row and col; a buffer value converts to float | +| `set_exit_code` | int-requiring | - | - | graceful exit code: N5, then 0..255 (Q1); main #1909 already checks fractions and range; R1.6-S2 stages the bridge migration with exit in round c | +| `sgd_update` | float-producing | - | - | float update | +| `sgd_update_cols` | float-producing | - | - | column indices int | +| `sgd_update_rows` | float-producing | - | - | row indices int | +| `shape` | int-preserving | - | - | dimension counts | +| `shared_delete` | int-preserving | - | - | removed count | +| `shared_incr` | int-preserving | LOSS | - | shared counter: delta and stored value in doubles | +| `shared_size` | int-preserving | - | - | entry count | +| `sign_extend` | int-requiring | LOSS | - | N5 exact int64 arguments and result; existing subtraction is checked under (A), raising `value` naming sign_extend on underflow (R1.6b-D1, #1894); high-bit masking is separate (#1896) | +| `sin` | float-producing | - | - | Lua math.sin | +| `softmax` | float-producing | - | - | ML (float) | +| `sort` | other | LOSS | - | exact int comparator written in R1.2, live from R1.3 (N2.sort-exact); from R2.2 floats sort by IEEE totalOrder, NaN after +inf (Q8) | +| `sort_by` | other | LOSS | - | numeric key kind retained for exact comparison in R1.2, live from R1.3; from R2.2 by IEEE totalOrder, NaN after +inf (Q8) | +| `spawn` | int-preserving | - | - | handle dict with int id/gen fields | +| `sqrt` | float-producing | - | - | Lua math.sqrt; sqrt of -1 is NaN (N11) | +| `state_at` | int-requiring | - | - | step index | +| `store_close` | int-requiring | - | - | handle id/gen | +| `store_collections` | int-requiring | - | - | handle id | +| `store_count` | int-requiring | - | - | handle and page ids; returns a count | +| `store_delete` | int-requiring | LOSS | - | key formatted `%d` of an `(int)` cast: 1.9 finds "1"; keys above INT_MAX are UB (probed) | +| `store_drop` | int-requiring | - | - | handle and page ids | +| `store_get` | int-requiring | LOSS | - | same key cast as store_delete; decodes records (N14) | +| `store_open` | int-preserving | - | - | handle dict with int id/gen fields | +| `store_put` | other | LOSS | - | the record encoder writes a non-int32 number with `%.15g`: 1234567890123456 is stored as 1234567890123460 and 0.1 + 0.2 as 0.3, on main and the tag (#1762). R1.7: the N6/N14 writer; R2.3: refusal inside the encoder | +| `store_query` | other | LOSS | - | decodes records; integer text through strtod (N14) | +| `store_update` | other | LOSS | - | same key cast as store_delete; re-puts through the `%.15g` encoder (#1762); N13/N14 | +| `str` | other | - | - | N6 printing via value_to_string | +| `str_from_bytes` | int-requiring | - | - | byte values: N5, then 0..255 (Q1; Lua string.char raises on 65.5 and on 256) | +| `stream_open` | int-requiring | - | - | element count, already floor-checked | +| `stream_write` | float-producing | - | - | writes a raw f64 | +| `substr` | int-requiring | - | - | start and length (Lua string.sub) | +| `subtract` | int-preserving | LOSS | - | scalar arm mirrors `-` (overflow raises, (A)) | +| `sum` | int-preserving | LOSS | - | an all-int input sums exactly, and overflow raises (A); today a guarded double sum | +| `tan` | float-producing | - | - | Lua math.tan | +| `task_alive` | int-requiring | - | - | task handle (packed below 2^53, exactness-checked) | +| `task_detach` | int-requiring | - | - | task handle | +| `task_join` | int-requiring | - | - | task handle | +| `task_kill` | int-requiring | - | - | task handle | +| `task_sched_seed` | int-requiring | LOSS | - | `(int64_t)` of a double | +| `task_self` | int-preserving | - | - | task handle | +| `task_send` | int-requiring | - | - | task handle | +| `task_sleep` | other | - | - | milliseconds of virtual time, kept as a float: no C integer conversion, so Q7 does not apply | +| `task_spawn` | int-preserving | - | - | returns a task handle | +| `tensor_load` | float-producing | - | - | float tensor; shape ints | +| `tensor_save` | float-producing | - | - | writes float cells | +| `text_builder_append` | other | - | - | formats via value_to_string (N6) | +| `text_builder_append_line` | other | - | - | formats via value_to_string (N6) | +| `thread_join` | int-requiring | - | - | thread handle id/gen | +| `throw` | other | - | - | message via value_to_string | +| `token_name` | int-requiring | - | - | token id | +| `try_recv` | int-requiring | - | - | channel handle ids | +| `num_type` | other | - | - | N7: int/float kind, null for a non-number | +| `type` | other | - | - | `type of` an int or a float is "num" ((B)) | +| `usleep` | other | - | magnitude | the microseconds are a continuous duration: rounded; a sleep the C int cannot hold raises (Q7 magnitude) | +| `vm_run_bytecode` | int-requiring | - | - | opcode bytes and ABI numbers | +| `write` | other | - | - | N6 printing | +| `write_bytes` | int-requiring | - | - | byte values: N5, then 0..255 (Q1; the modulo-256 wrap is gone); returns bytes written | +| `zeros` | int-requiring | - | - | size or [rows, cols]; elements are float 0.0 | +| `zeros_like` | float-producing | - | - | float zeros | +| `i8_wrapping_add` | int-requiring | - | - | N9: i8 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `i8_wrapping_sub` | int-requiring | - | - | N9: i8 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `i8_wrapping_mul` | int-requiring | - | - | N9: i8 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `i8_checked_add` | int-requiring | - | - | N9: i8 checked add; N5 then type range without masking; exact int result (Rust std) | +| `i8_checked_sub` | int-requiring | - | - | N9: i8 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `i8_checked_mul` | int-requiring | - | - | N9: i8 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `i8_saturating_add` | int-requiring | - | - | N9: i8 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `i8_saturating_sub` | int-requiring | - | - | N9: i8 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `i8_saturating_mul` | int-requiring | - | - | N9: i8 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `i16_wrapping_add` | int-requiring | - | - | N9: i16 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `i16_wrapping_sub` | int-requiring | - | - | N9: i16 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `i16_wrapping_mul` | int-requiring | - | - | N9: i16 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `i16_checked_add` | int-requiring | - | - | N9: i16 checked add; N5 then type range without masking; exact int result (Rust std) | +| `i16_checked_sub` | int-requiring | - | - | N9: i16 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `i16_checked_mul` | int-requiring | - | - | N9: i16 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `i16_saturating_add` | int-requiring | - | - | N9: i16 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `i16_saturating_sub` | int-requiring | - | - | N9: i16 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `i16_saturating_mul` | int-requiring | - | - | N9: i16 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `i32_wrapping_add` | int-requiring | - | - | N9: i32 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `i32_wrapping_sub` | int-requiring | - | - | N9: i32 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `i32_wrapping_mul` | int-requiring | - | - | N9: i32 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `i32_checked_add` | int-requiring | - | - | N9: i32 checked add; N5 then type range without masking; exact int result (Rust std) | +| `i32_checked_sub` | int-requiring | - | - | N9: i32 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `i32_checked_mul` | int-requiring | - | - | N9: i32 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `i32_saturating_add` | int-requiring | - | - | N9: i32 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `i32_saturating_sub` | int-requiring | - | - | N9: i32 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `i32_saturating_mul` | int-requiring | - | - | N9: i32 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `i64_wrapping_add` | int-requiring | - | - | N9: i64 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `i64_wrapping_sub` | int-requiring | - | - | N9: i64 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `i64_wrapping_mul` | int-requiring | - | - | N9: i64 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `i64_checked_add` | int-requiring | - | - | N9: i64 checked add; N5 then type range without masking; exact int result (Rust std) | +| `i64_checked_sub` | int-requiring | - | - | N9: i64 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `i64_checked_mul` | int-requiring | - | - | N9: i64 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `i64_saturating_add` | int-requiring | - | - | N9: i64 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `i64_saturating_sub` | int-requiring | - | - | N9: i64 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `i64_saturating_mul` | int-requiring | - | - | N9: i64 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `u8_wrapping_add` | int-requiring | - | - | N9: u8 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `u8_wrapping_sub` | int-requiring | - | - | N9: u8 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `u8_wrapping_mul` | int-requiring | - | - | N9: u8 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `u8_checked_add` | int-requiring | - | - | N9: u8 checked add; N5 then type range without masking; exact int result (Rust std) | +| `u8_checked_sub` | int-requiring | - | - | N9: u8 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `u8_checked_mul` | int-requiring | - | - | N9: u8 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `u8_saturating_add` | int-requiring | - | - | N9: u8 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `u8_saturating_sub` | int-requiring | - | - | N9: u8 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `u8_saturating_mul` | int-requiring | - | - | N9: u8 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `u16_wrapping_add` | int-requiring | - | - | N9: u16 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `u16_wrapping_sub` | int-requiring | - | - | N9: u16 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `u16_wrapping_mul` | int-requiring | - | - | N9: u16 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `u16_checked_add` | int-requiring | - | - | N9: u16 checked add; N5 then type range without masking; exact int result (Rust std) | +| `u16_checked_sub` | int-requiring | - | - | N9: u16 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `u16_checked_mul` | int-requiring | - | - | N9: u16 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `u16_saturating_add` | int-requiring | - | - | N9: u16 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `u16_saturating_sub` | int-requiring | - | - | N9: u16 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `u16_saturating_mul` | int-requiring | - | - | N9: u16 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `u32_wrapping_add` | int-requiring | - | - | N9: u32 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `u32_wrapping_sub` | int-requiring | - | - | N9: u32 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `u32_wrapping_mul` | int-requiring | - | - | N9: u32 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `u32_checked_add` | int-requiring | - | - | N9: u32 checked add; N5 then type range without masking; exact int result (Rust std) | +| `u32_checked_sub` | int-requiring | - | - | N9: u32 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `u32_checked_mul` | int-requiring | - | - | N9: u32 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `u32_saturating_add` | int-requiring | - | - | N9: u32 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `u32_saturating_sub` | int-requiring | - | - | N9: u32 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `u32_saturating_mul` | int-requiring | - | - | N9: u32 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `u64_wrapping_add` | int-requiring | - | - | N9: u64 wrapping add; N5 then type range without masking; exact int result (Rust std) | +| `u64_wrapping_sub` | int-requiring | - | - | N9: u64 wrapping sub; N5 then type range without masking; exact int result (Rust std) | +| `u64_wrapping_mul` | int-requiring | - | - | N9: u64 wrapping mul; N5 then type range without masking; exact int result (Rust std) | +| `u64_checked_add` | int-requiring | - | - | N9: u64 checked add; N5 then type range without masking; exact int result (Rust std) | +| `u64_checked_sub` | int-requiring | - | - | N9: u64 checked sub; N5 then type range without masking; exact int result (Rust std) | +| `u64_checked_mul` | int-requiring | - | - | N9: u64 checked mul; N5 then type range without masking; exact int result (Rust std) | +| `u64_saturating_add` | int-requiring | - | - | N9: u64 saturating add; N5 then type range without masking; exact int result (Rust std) | +| `u64_saturating_sub` | int-requiring | - | - | N9: u64 saturating sub; N5 then type range without masking; exact int result (Rust std) | +| `u64_saturating_mul` | int-requiring | - | - | N9: u64 saturating mul; N5 then type range without masking; exact int result (Rust std) | +| `u64_compare` | int-requiring | - | - | N9: N5 int64 bit patterns; unsigned order gives int -1, 0 or 1 (Rust std) | +| `u64_div` | int-requiring | - | - | N9: N5 int64 bit patterns; unsigned quotient as int64 bits; zero raises value (Rust std) | +| `u64_rem` | int-requiring | - | - | N9: N5 int64 bit patterns; unsigned remainder as int64 bits; zero raises value (Rust std) | +| `u64_to_string` | int-requiring | - | - | N9: N5 int64 bit pattern to unsigned decimal string (Rust std) | +| `fround` | float-producing | - | - | N9: int converts to f64 first; f64 to f32 to f64, float result; current result guard until R2.2 | + + +**Builtins to add**, outside the checked table until they exist: +- `num_type` (N7); +- `f64_bits` and `f64_from_bits`, plus buffer bits get/set (N12); + +**N9 width family (R1.6g, in the checked table above):** + +The decided width family ([#1758 comment 6304880619](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6304880619)): + `__`, with types `i8 i16 i32 i64 u8 u16 u32 u64`, modes + `wrapping checked saturating`, and ops `add sub mul` (72 builtins, one + table-driven implementation in `builtins_int.c`). Examples: + `i64_wrapping_add`, `u8_checked_add`, `i8_saturating_sub`. + N5 accepts integral floats and refuses fractions; operands must then fit + the named type's range, without masking. `i64` and `u64` accept every + int64; u64 uses its int64 bit pattern (`-1` is u64 max). Results are ints: + wrapping reduces modulo the width, checked raises `value` naming the + builtin on overflow, and saturating clamps to the type's range. +- `u64_compare` (returns -1, 0 or 1), `u64_div`, `u64_rem`, and unary + `u64_to_string`; a zero divisor raises. Rust's std is the oracle, with + `as u64` / `as i64` mapping the bit pattern. No width-family negation, + shifts, division, remainder or power are added. +- `fround`: `(double)(float)x`, converting an int first and returning a + float (JavaScript's `Math.fround`). Together these are the 77 builtins added by R1.6g. + +--- + +## 7. Semantics-to-sites map + +Each item lists the files and functions that implement it. The site lists +inside each function are in `docs/design/phase2-inventory.txt`. All line +numbers are at `0740af03`. + +- **N1 (int literals; decimal outside int64 is a float; hex wraps):** + - lexer.c:717-753 (number lexing, hex accumulates in a double today); + the token's `num_val` (eigenscript.h:188); + - parser.c:791 (`TOK_NUM` to `AST_NUM`, `data.num`); + - compiler.c:2071-2077 (`AST_NUM` emission, reading the renamed payload): + from R1.3 an int literal emits the new `OP_INT_ZERO`/`OP_INT_ONE`/ + `OP_INT_CONST`, and a float literal keeps `OP_NUM_ZERO`/`OP_NUM_ONE`/ + `OP_CONST` (§2.7); compiler.c:2929/2985/3030 (index fast paths read + `data.num`); + - vm.h `OpCode` (the three opcodes appended, each `/*obs:NONE*/`), and the + exhaustive switches that must name them: `op_name` (chunk.c:337), + `op_verify_operands` (chunk.c:466), `op_verify_stack_effect` + (chunk.c:615), `op_stack_effect` (compiler.c:305); the interpreter's + dispatch table (vm.c:3091) and three new CASEs; `tests/test_opcode_abi.c`; + - chunk.c:193-260 (constant pool dedup: `const_hash_value`, `const_equal`, + `chunk_add_constant_ex`, which must key on kind and bits); + - vm.c:3238 `CASE(CONST)` and 3257 `CASE(NUM_ZERO)`/`NUM_ONE`: renamed, + float meaning unchanged; + - the JIT: the scanner refuses the new opcodes in round 1 (round 5 emits + them); its `NUM_ZERO`/`NUM_ONE`/`CONST` emitters (jit.c:2797/2816) keep + their float meaning (§4.2); + - the leaf evaluator: its scanner refuses the new opcodes (chunk.c:1129, + the `default:`); its `CONST`, `NUM_ZERO` and `NUM_ONE` arms + (vm.c:1866-1877) keep their float meaning (§4.5); + - lint.c, lint_host.c and eigenlsp.c (the `AST_NUM` type knowledge, 34 + sites); + - `src/freestanding/mini_strtod.c`; + - ouroboros `src/frontend.eigs` (the AOT's lexer). +- **N2 (subtype visible; exact comparison across kinds):** + - vm.c:3579 `CASE(EQ)`, 3600 `CASE(NE)` and 3621 `NUM_CMP`; + - eigenscript.c:2849 `values_equal_impl`; + - builtins.c:5868 `sort_cmp_num`, and `sort_by`, `list_contains`, + `list_index_of`; + - JIT comparisons bail on SMI (§4.1). + - The exact int/float compare: + - if the float is NaN, the result is unordered (every comparison + false except `!=`); + - if `f >= 2^63`, the float is greater than every int; if `f < -2^63`, + it is smaller; + - otherwise compare `i` with `fl = (int64_t)floor(f)`: `i < fl` or + `i > fl` decides, and when `i == fl` the float is greater exactly when + `f != floor(f)`. + - Lua's `LTintfloat`/`EQintfloat` (lvm.c) reach the same answers by + converting whichever side converts exactly. +- **N3 (`+ - *` on ints give an int; overflow raises):** + - vm.c:3290 `ARITH_FAST`, 3324 `CASE(ADD)` and 3377 `NUM_BINOP` + (SUB, MUL); + - builtins_tensor.c `op_add`, `op_sub`, `op_mul` and + `tensor_elementwise` (the scalar arms of `add`, `subtract`, + `multiply`), plus `sum` and `shared_incr`; + - JIT bails (§4.1); + - the leaf's `ADD`/`SUB`/`MUL` arm (vm.c:1933-1956): bails unless both + operands are floats in round 1, so int, mixed and overflowing operations + take the generic path (§4.5). +- **N4 (`/` float; `//` new; `%` floor; division by zero):** + - vm.c:3407 `CASE(DIV)` (int/int gives a float) and 3432 `CASE(MOD)` + (fmod becomes floor-mod; int by zero raises); + - **new `OP_IDIV`**, appended at the end of `OpCode` (vm.h). The + extend-vm checklist applies: dispatch table, handler, `op_name` + (chunk.c), `op_stack_effect` (compiler.c), an `/*obs:NONE*/` marker + (`tools/obs_marker_check.sh`) and `tests/test_opcode_abi.c`. Appending + needs no `EIGS_BYTECODE_ABI` bump. + - Lexer: `//` lexes as two `TOK_SLASH` today, so a new `TOK_SLASH_SLASH` + is appended to the token enum, the X2 table (lexer.c:241) and the token + text (lexer.c:357). `//=` is appended too and uses the X3 table. Keep + both text homes (`tok_base_string` and `tok_type_name`) in lockstep; + `tok_base_string_id_count` follows the last appended token, since + `TOK_EOF` keeps its existing ID. Append-only compound tokens also need + the parser's compound predicate and conversion outside the old range. + - parser.c (multiplicative level), fmt.c, lint, the LSP and ouroboros + `frontend.eigs`; + - JIT MOD (§4.2). + - Float and mixed `//` follow CPython's `float_divmod` quotient correction + (#1882): derive `(a - fmod(a,b)) / b`, subtract one for an opposite-sign + nonzero remainder, then floor and snap upward when the fractional part + exceeds 0.5; a zero quotient takes the sign of `a/b`. This gives + `1.0 // 0.1 == 9.0` and `1e-300 // -1e300 == -1.0`, unlike `floor(a/b)`. + The VM's `CASE(IDIV)` is the only evaluator; the JIT and leaf refuse it. + - Float `x % 0` (NaN), `x / 0` (inf) and float/mixed `x // 0` + (`floor(x/0)`, ±inf or NaN) wait for N11 in round 2. All raise today; + the `//` rule is #1758 comment 6294926822. +- **N4b (INT64_MIN edges):** + - vm.c:3522 `CASE(NEG)` (int arm raises on INT64_MIN); + - builtins.c `builtin_abs` (2056); + - the new `OP_IDIV` (`x // -1`); + - `CASE(MOD)`: `INT64_MIN % -1` is 0, computed without `idiv`, which + faults on x86. +- **N5 (float-to-int by the exact rule wherever an int is needed):** + - eigenscript.h:1910 `num_to_i64_exact` (#1740); + - every int-requiring row of §6, through `eigs_int_arg` (with + `eigs_int_arg_range`, `eigs_byte_arg` for Q1's integer-valued + parameters; the Q7 readers `eigs_geom_shape`, `eigs_bounded_arg` and + `eigs_magnitude_arg` for its continuous ones); + - vm.c index paths (`jit_helper_index_get`/`set`, `CASE(INDEX_*)`) and + `range_bound` (builtins.c:3877); + - the JIT `OP_INDEX_SET` integrality guard (jit.c:4100-4112) stays + correct, because a non-exact index goes to the helper; + - the leaf's `INDEX_GET` arm (vm.c:1907-1931): in round 1 it bails on an + int index (the generic path applies N5), and a float index still goes + through `vm_index_is_int`; + - `buffer_read_byte` and `finite_num_to_byte` (Q1 byte values), deleted + in R1.6 (§2.7), and the byte reads `buffer_read_num` performs today: + `buf_from_pcm16le` (builtins_buf.c:754, :756; #1758 comment 6174964306), + `str_from_bytes` (:240), `f64_from_bytes` (:284), the zlib input (:351), + `write_bytes` (builtins_host.c:1548) and `net_send` (ext_net.c:361), each + through `buffer_byte_at` after R1.6 renames `buffer_read_num`; + - the index reads: `gather`'s `flat_index_at` (builtins_tensor.c:1428-1440, + both its list path and its buffer path truncate today) and + `scatter_add` (:1036, exactness-checked but raising an index error), + each through `buffer_index_at` or `eigs_int_arg` (N5, `value`); + - the Q7 readers (§2.6) for every continuous parameter the `q7` column + names: the gfx shapes (ext_gfx.c), the audio samples, gains and + durations, `usleep`, `gfx_delay`, `exec_capture`, `recv_timeout`. +- **N6 (printing):** + - eigenscript.c:2938 `eigs_num_text` (today integral doubles below 2^53 + print without `.0`) and 2957 `value_to_string`; + - the f-string conversion (`EIGS_FSTR_CONV_NAME`, builtins.c:6207); + - vm.c `obs_dump_num` (106); + - chunk.c `chunk_disassemble`; + - the JSON encoders (h); + - trace.c's `%.17g` writers (2817, 2836, 2950), whose tape text is §7 T1; + - `src/freestanding/mini_fmt.c`. + - Also chunk.c:196, the constant dedup folding `-0.0` into `+0.0`. Under + N6, `-0.0` prints `-0.0`, so the dedup must compare bits. +- **N7 (B: `type of` is `"num"`; `num_type`):** + - builtins.c:864 `builtin_type`, unchanged; + - `val_type_name(VAL_INT)` returns `"num"`; + - vm.c:5476 `who`, which answers "number"; + - new `num_type` (builtins.c, registration at 6184). +- **N8 (bitwise on ints, Lua shift rules):** + - vm.c:3459 `INT_BINOP_R` (`& 63` becomes Lua's rule; `>>` becomes + logical) and 3548 `CASE(BNOT)`; + - builtins.c:349-414 `bit_*`; + - JIT bitwise is refused (§4.2). +- **N9 (width builtins):** new `builtins_int.c`, with §6's decided names, + operand rules and Rust std oracle; `fround` (`(double)(float)x`). +- **N10 (`num of`):** builtins.c:494 `builtin_num`; the decided grammar is + exactly the lexer's numeric-literal grammar, plus surrounding ASCII + whitespace and an optional leading sign (#1758 comment 6304880619). + Decimal integer text is an int when it fits int64 and a float otherwise; + a decimal point or exponent selects float. Hex `0x`/`0X` integer text + wraps as the literal does. Partial input (#1638), empty/blank text, + underscore separators, `0b`, `0o`, hex floats, `inf` and `nan` raise + `value`. `try_parse` stays the non-raising form. Lua 5.4.9's `tonumber` + is the oracle, with its `nil` corresponding to a raise; hex floats are + the declared divergence because Lua accepts them and this lexer does not. + MAP `N10.radix` pins both hex spellings and the two refused prefixes. +- **N11 (IEEE doubles; strict becomes opt-in):** + - eigenscript.h:1876 `num_guard`, `num_guard_named`, `EIGS_NUM_MAX` and + `STRICT_DOMAIN`: the 74 clamp sites (`num_guard` 60, `num_guard_named` + 6, `EIGS_NUM_MAX` 8); + - vm.c `CASE(DIV)`, `CASE(MOD)` and the float/mixed arm of `CASE(IDIV)`: + all three zero-divisor checks raise through R1.4; N11 changes them together + to IEEE division, floor division (`floor(x/0)`, ±inf or NaN) and modulo + (NaN), per #1758 comment 6294926822. Int `// 0` and `% 0` still raise; + - builtins_tensor.c `op_sqrt` (711) and `op_log_safe` (729); + - builtins.c `asin`/`acos` domain; + - state.c:62 `st->strict` defaults to 1 and must become opt-in; + - jit.c:2634 `max_normal_bits`; + - the leaf's `num_guard(r)` (vm.c:1954), deleted with the others; + - the consumers no token marks (§2.7): `values_equal_impl`'s identity + shortcut (eigenscript.c, first line of the function) must not call a + NaN box equal to itself; the sort comparators (`sort_cmp_num`, + builtins.c:5868, and `sort_by`'s key comparison, 5815) order floats by + IEEE `totalOrder`, NaN after +inf; `minmax_reduce` (builtins.c:2086-2088, + `min`/`max`) propagates NaN (Q8, decided). +- **N12 (canonical NaN; payload fidelity through ints):** + - value_slot.h:73 `slot_from_num`, becoming `slot_from_f64`: the single + writer, including the leaf's five calls (vm.c:1869, 1873, 1876, 1927, + 1954), so a NaN the leaf computes is canonical too; + - the JIT NaN bails (§4.3); + - new `f64_bits`/`f64_from_bits` and buffer bits accessors; + - builtins_buf.c `f64_from_bytes` and `f64_to_bytes`; + - eigs_embed.c:339 (the host boundary; #1746 round 3, item 4). +- **N13 (refusal inside writers):** + - builtins.c:907 `eigs_json_encode_value`; + - ext_store.c's encoder: the #1746 residue is `store_nonfinite_scalar` + (ext_store.c:278 on the experiment), which must be deleted in favour of + refusal inside the encoder; + - the byte encoders (`eigs_byte_arg`, which replaces `finite_num_to_byte` + and `buffer_read_byte` in R1.6) and the `buf_to_pcm16le` quantizer; + - every int conversion (`eigs_int_arg`, `num_to_i64_exact` callers); + - the tape writers record, not refuse. +- **N14 (JSON):** + - builtins.c:907 encode and 1406 `builtin_json_decode`, plus + `eigs_json_parse_value`, `json_path` and `json_build`; + - the ext_store.c record codec; + - ext_http.c request/response JSON; + - model_io.c `json_*` (model files). +- **(A) (overflow raises, no wraparound):** the N3 and N4b sites; `sum`, + `shared_incr`, `sign_extend`'s subtraction (R1.6b-D1), the `add`/`subtract`/`multiply` scalar arms; the N9 + builtins provide explicit wrapping. +- **(B):** as N7; `type of` and the 54 consumer `type of ... == "num"` lines + keep working. +- **T1 (tape v7):** + - trace.h:32 `TRACE_FORMAT_VERSION`, 6 becoming 7; + - trace.c:2817 `write_value_ptr`, 2836 `write_slot` and 2950 + `write_value_ptr_full`: ints as digits, floats in a form that always + shows the kind (N6 repr), `inf`/`-inf`/`nan` in round 2; + - trace.c:2284 `parse_value_p`: digits only give an int, anything else + goes through strtod as a float; + - trace.c:2194 and tape_read.c:155 (version refusal); + - tape_read.c:368 (the step reader's `strtod`); + - eigsdap.c; + - docs/TRACE.md (bump plus the compat decision, #411); + - the `O cfg` record (trace.c:1577-1584 writes `h_low`; tape_read.c:389 + reads it). O3 drops it in round 3; + - the leaf evaluator is off while recording (`g_trace_hist`, vm.c:2505 + and vm.c:4255), so a tape always holds the generic path's values; the + `trace` tier (§9.2) gates that the live leaf agrees; + - the Done-when's "record twice and replay strict byte-identically" for + int, float, inf and NaN programs: rows `T1.twice-int` (R1.3), + `T1.twice-float` (R1.7, N6's text), `T1.twice-inf` and `T1.twice-nan` + (R2.5). Each records one deterministic program twice, requires + byte-identical tapes, and replays strict against the live (leaf-on) + output. +- **O1-O4 (observer, round 3):** + - eigenscript.c:698 `observer_slot_record_value`, 804 + `observer_slot_update_num`, the `obs_num_*` predicates (935-1193) and + `compute_entropy_impl` (435), from which entropy is removed for numerics; + - vm.c `obs_slot_observable` (1630); + - **the routing of an int into the numeric channel, from R1.3 (when the + first int exists), converted to a double for the distance until round + 3.** Two paths, both forced before R1.3 and both live in R1.3: + - **an SMI**: the observe sites test `slot_is_num` first (D1, forced in + R1.1): `CASE(OBSERVE_ASSIGN_LOCAL)` (vm.c:5189, its elided + `unobserved:` path at 5202), `CASE(OBSERVE_NAME_POST)` (5431), + `jit_helper_observe_assign_local` (1678-1684) and + `jit_helper_observe_name_post` (1730). Each gains `slot_is_smi(s)` -> + `observer_slot_update_num(e, slot, (double)smi)` (or the sample form); + `obs_slot_observable` aborts on an SMI that reaches it (§2.5); + - **a boxed int** (an int outside [-2^47, 2^47), or any int a lift + boxed): `obs_slot_observable` answers true for any pointer, so it goes + to `observer_slot_update(Value *)` (eigenscript.c:769), whose number + arm (`newval->type == VAL_NUM`, :785) feeds the value channel and whose + `else` ends the numeric trajectory (#861). R1.2's `VAL_NUM` rename + forces that arm; it gains `VAL_INT` -> + `observer_slot_record_value(s, (double)i)`. The same holds for the + elided `observer_slot_sample_gated` (:851, its arm at :852) and for + `compute_entropy_impl`'s exhaustive switch (:437), whose `VAL_INT` arm + gives an int the entropy of its double; + - the MAP row `O1.int-observed-numeric` (R1.3) pins both: an SMI and a + boxed int (2^47 and up) must answer every observer readout exactly as + their float twins do; + - vm.c:5460 `CASE(INTERROGATE)` (`where`/`why`/`how`); + - `builtin_observe`; + - the `O cfg` record; + - tools/observer_oracle/score.py (the IEEE mode and `--floor` from the + oracle PR). +- **The leaf evaluator, arm by arm** (vm.c:1856-1971, scanner + chunk.c:1072-1134; every arm is in §4.5): + - `CONST` (1866), `NUM_ZERO` (1872), `NUM_ONE` (1875): N1, float + literals only; the renamed writer `slot_from_f64` (N12); int literals + are refused by the scanner (§2.7); + - `GET_LOCAL` (1878), `LOCAL_DOT_GET` (1883): borrow any slot, an SMI + included (§2.5, `guarded`); + - `INDEX_GET` (1907): N5 (index exactness), Q2 (a buffer element is an + f64); bails on an int index in round 1; + - `ADD`/`SUB`/`MUL` (1933): N3, (A), N2's float half, #1079's underflow + flag, N11 (`num_guard` goes in R2.2), N12 (canonical NaN through + `slot_from_f64`); bails unless both operands are floats in round 1; + - `RETURN` (1957): unchanged; + - the gate `!g_trace_hist` (vm.c:2505, vm.c:4255): T1, and the `trace` + tier of §9.2. + +--- + +## 8. Round plan + +Every commit builds, passes the full suite, and carries its own MAP rows. +"Unchanged" means a differential against the baseline binary captured +before the commit (the verify-and-fix rule), not "the suite passed". Every +meaning change lands in the commit that carries its token change, or is in +§2.7's token-less list with its gate. `phase2_check.sh --through ` +checks each commit against exactly the rows that have landed (§9.2). + +### Where the first int value appears: R1.3 + +No int VALUE exists before R1.3, and this is structural, not a promise: +- `make_int` is introduced in R1.3, so nothing earlier can call it; +- `slot_from_int` exists from R1.1 (the C slot test needs it), but until R1.3 + its only production callers are the canonicalizing writers of §2.1, which + convert an existing `VAL_INT`, and none exists. `phase2_check.sh --through + R1.2` checks this: it derives the callers of `slot_from_int` and + `make_int` from the source and fails on any caller outside that list + (the int-source check). A producer can also write the tag bits without + calling the writer, so the check fails on `TAG_SMI` or the SMI bit pattern + anywhere outside its definitions in `value_slot.h`, and its dynamic half + runs the corpus on a `make smi-trap` build, where every slot classifier + aborts on an SMI and `slot_from_int` aborts on any call (the R1.1 fix + round, after the critic's plants P3-P5); +- the lexer produces only float literals until R1.3, and the tape cannot + carry an int until v7 (R1.3). + +**Every consumer of an int is converted AT R1.3**, either by code forced and +written earlier (R1.1, R1.2) that goes live, or by code R1.3 adds: + +| consumer | converted by | how it treats an int at R1.3 | +|---|---|---| +| VM arithmetic, comparison, negation (N2, N3, N4b, (A)) | R1.1 forced the arms (`slot_as_double` deleted); R1.3 adds the int semantics | exact int64, overflow raises | +| `/` and `%` | R1.3 (transitional) | converted to doubles, today's semantics, until R1.4 | +| bitwise operators | R1.5 | exact int64 operands (#1740), int results and Lua 5.4 shifts | +| equality, ordering, hashing, the constant pool | R1.2 (forced arms) | by value across kinds (N2); the pool keys on kind | +| printing (`value_to_string`, f-strings) | R1.2a (the exhaustive switch) | exact digits, as today for small ints | +| `type of`, `num_type` | R1.2a (`val_type_name`), R1.3 (`num_type`) | `"num"`; `"int"` | +| containers | R1.2 (the four canonicalizing writers) | an SMI, or a box outside the SMI range (§2.1) | +| the observer | R1.1 (SMI sites, D1) and R1.2 (the boxed arm at eigenscript.c:785, the entropy switch) | the numeric channel, as a double until round 3 (§7) | +| the tape | R1.3: v7 writer and parser | ints as digits, floats marked as floats; a v6 tape is refused | +| the JIT | nothing to convert: its guards bail on an SMI or a `VAL_INT` box (§4.1), and its scanner refuses `OP_INT_*` | interpreted | +| the leaf evaluator | R1.1 (its arithmetic and index arms bail) | the generic path computes it | +| builtins | R1.2's number tests admit ints; most readers retain the bridge | a double until R1.6, except the exact `sort`/`sort_by` comparators and `json_build` text reader (§6); `json_path`'s exact text reader is also complete, awaiting decoder int production in R1.6 | +| JSON, store and db encoders | R1.2a (their `VAL_INT` arms) | exact digits | +| the embed API (`eigs_value_as_num`) | R1.2 (its forced `VAL_NUM` test) | converts to a double (Q3) | +| lint and the LSP | R1.3 (the AST payload rename) | the literal's kind | + +### Round 1: the core model (Alt 1R) + +1. **R1.1, the slot layer: the SMI exists, every slot reader is forced (no + producer).** + - Add `TAG_SMI`, `slot_is_smi`, `slot_from_int`, `slot_smi_val`, + `slot_f64_value` and `slot_int_value` (§2.6). + - The token changes: `slot_is_num` -> `slot_is_f64`, `SLOT_NUM_RAW` -> + `SLOT_F64_RAW`, `slot_from_num` -> `slot_from_f64`; `slot_is_numeric` + deleted; **`slot_as_double` deleted**. Its 15 calls are rewritten with + an explicit int decision: the arithmetic and compare arms take their + float fast path through `slot_f64_value` and send anything else to the + slow path; the leaf evaluator's `INDEX_GET` and `ADD`/`SUB`/`MUL` arms + bail on anything that is not a float. 223 sites first forced (§2.3a). + - The D2/D3 dispositions (§2.5): `eigs_slot_ref`, `obs_slot_observable`, + `slot_as_ptr` and `slot_as_heap` refuse an SMI (abort; `slot_as_ptr` + in ASan and debug builds); `slot_type_name` gains its SMI arm. + - `tools/phase2_check.sh` lands here with its calibration (§9.2). + - Gates (a mechanical round, zero MAP rows): the compile errors, the C + slot test (§2.5) and plant 1 (§9.3); the int-source check; language + behaviour unchanged (differential over the suite's `.eigs` corpus). +2. **R1.2, the Value layer (behaviour-preserving; no builtin decision).** + - R1.2a: `VAL_INT` appended; `VAL_NUM` -> `VAL_FLOAT`, `num_` -> `f64_`, + `VAL_NUM_RAW`/`VAL_NUM_OFFSET` renamed, with deprecated aliases as the + progress meter; `val_type_name(VAL_INT) = "num"`; **the `default:` arm + of `eigs_json_encode_value` (builtins.c:998) deleted**, so + `-Werror=switch` reaches the last switch that could absorb `VAL_INT` + silently. + - R1.2b-n: one commit per file group. Each forced site gets its final + int arm (dead until R1.3), and a builtin's number test admits an int + and normally reads it through the bridge (§2.7). The exact sorting and + JSON text readers are completed here (§6), to avoid a later lossy + conversion. The bridge's names are NOT touched; no behaviour changes + before ints exist. 464 sites first forced. + - The three reader arms scheduled with the Value kind are complete in + R1.2i: `slot_type` types an SMI, `slot_view` exposes an exact int + facade, and `slot_int_value` extracts a boxed int. None produces an + int; R1.3 does not implement these arms a second time. + - R1.2z: delete the deprecated aliases. A straggler fails to compile. + - Gates (a mechanical round, zero MAP rows): the compile errors; the + differential over the suite's `.eigs` corpus (identical to the + baseline); the int-source check. +3. **R1.3, the flip: the first int values.** See the table above. + - N1, with its token changes in this same commit: the lexer produces an + int literal; the AST payload `num` and the token's `num_val` become an + `EigsNum`; **`OP_INT_ZERO`, `OP_INT_ONE` and `OP_INT_CONST` are + appended** (the four exhaustive OpCode switches; three new interpreter + CASEs; the JIT and leaf scanners refuse them by their `default:`); + `make_int` is introduced. `OP_NUM_ZERO`/`OP_NUM_ONE`/`OP_CONST` keep + their float meaning in all three executors. + - The exact `sort`/`sort_by` comparators and `json_build` reader written + in R1.2 go live; `N2.sort-exact` is checked here, not R1.6 (§6). + - N3 with (A), N2's exact cross-kind comparison, N4b `-x`, N7 `num_type`; + EQ/NE's only bit fast path is "both SMI" (§2.1). + - The JIT scanner refuses `BAND`/`BOR`/`BXOR`/`SHL`/`SHR`/`BNOT`/`MOD`. + - **Tape v7 in this commit**: a v6 writer would print `3` for both kinds + and replay would turn ints into floats. The float token marks its kind + now (`3.0`); N6's repr replaces it in R1.7, still inside v7. + - The remaining `eigs_int_unwired` placeholders are deleted, including: + `slot_to_value` boxes an SMI with + `make_int`, `write_slot` writes v7 digits, and `slot_from_int` + boxes an int outside the SMI range (each R1.1 placeholder, 2.5). + - The observer routes an SMI and a boxed int into the numeric channel + (§7). + - The `trace` tier (the leaf differential) and the tier witnesses gate + from here. + - Risks: JIT int loops leave the JIT (§3: +70% Ir on a counter loop), + and the leaf stops computing int helpers; declare the CodSpeed baseline + move first. `tools/phase2_cost.sh` reads per-workload and per-tier TSV + six-column declarations in `tests/phase2/cost_expect.txt` (`workload`, `tier`, + `max_local`, `max_ci`, `recover_round`, `reason`). R1.8 applies #1939: + each maximum comes from its own gate’s measured delta. The local gate + enforces and STALE-checks only `max_local`; undeclared rows retain + +0.3%. Declare only measured R1.3 costs with §3 as the reason and + `recover_round=R5`, the JIT/leaf int fast-path round. A row above its + declared maximum fails, and so does a row more than 0.5 points below it + (STALE, the R1.3 CI fix): a round that recovers cost must bring the + declaration down, or a new regression hides under it. Each method’s + maximum is its own measured delta plus 0.2 points (the largest measured + shift: a different workload path moves `alloc_heavy` by 0.16 points), rounded up + to 0.1. A row whose cost another round moves names that component with + its measurement: R1.6's int `range` alone removes 58% (jit) and 66% + (off) of `scalar_loop`'s excess, and raises 14 other rows by up to 44 + points (the range-int control, measured at R1.3). + `bench/check_regression.sh --vs` (CI's bench job, the default tier) + reads the same `jit` rows through + `phase2_cost.sh --declared jit --method ci`: a declared workload is + bounded and STALE-checked only against `max_ci`, and prints its + recovery round; an undeclared one keeps the 5% bound. The CI maximum + comes from the CI bench gate’s own log for the pushed head, never + from a local run or a value chosen to satisfy both methods (#1939). + The observer oracle’s known-wrong capability + verdicts are declared the same way (#1830): `tests/phase2/oracle_expect.txt` + (`arm`, `row@checkpoint`, `predicate`, `recover_round`, `reason`) lists the + 32 `int64_count` verdicts R1.3 first measures, `recover_round=R3`, the round + whose predicate definitions make `int64_count` its acceptance row. + `make observer-oracle` and CI's lane pass it to `score.py --declared`; an + undeclared wrong verdict fails, and so does a stale declaration (its verdict + is right now, or was not examined). **Pre-merge condition (#1776):** + `tools/phase2_cost.sh --final BASE CAND` must pass, which requires both + `tests/phase2/cost_expect.txt` and `tests/phase2/oracle_expect.txt` to be + empty; temporary costs and known-wrong verdicts must be recovered before + integration merges. + Consumer hashes that relied on double rounding now raise + ((A)); they move to the N9 wrapping builtins. The stdlib's own + real-valued formulas must not: `tools/phase2_lib_diff.sh` (§9.5, #1848) + runs every exported lib function with int arguments on main and the + branch, from R1.3 in every round. +4. **R1.4, N4.** `//` (token, parser, `OP_IDIV` appended, + `op_name`/`op_stack_effect`/obs marker/ABI test), `//=` beside `/=` (Q6), + `%` floor modulo for ints and floats, int division by zero raises, + `INT64_MIN % -1` is 0. +5. **R1.5, N8.** Bitwise operators on ints with Lua's shift rules. +6. **R1.6, the bridge deletion: every builtin's §6 semantics.** The bridge's + 21 tokens are deleted in per-file-group commits. Each commit moves its + sites off the bridge and gives those builtins their final behaviour in + that same commit (§2.7). The binding R1.6 plan splits the work as follows; + a builtin's file is where its inventory C function is defined. + + | sub-round | work | table rows | MAP rows or mechanical gates | + |---|---|---:|---| + | R1.6a | No behaviour change: §2.6 readers except `eigs_geom_shape`; bounded and magnitude included; opt-in meter; pre-registered table and tool; round order and records | none migrate | mechanical: C reader test and table calibration | + | R1.6b | Every LOSS row in any file, plus `builtins.c`'s other int-preserving rows | 46 | `A.sum-overflow-raises`, `A.tensor-scalar-overflow-raises`, `N5.range-fraction-raises`, `N5.store-key-fraction-raises`, `N10.partial-refused`, `N10.radix`, `N10.int-or-float`, `N14.json-path-int-exact`, `N14.decode-kinds` | + | R1.6c | Remaining `builtins.c` (including `set_exit_code`); `builtins_host.c` (including `read_text`); `vm.c`'s builtin | 77 | `N5.substr-fraction-raises` | + | R1.6d | Non-LOSS `builtins_tensor.c`, `builtins_buf.c`, `model_io.c`, `model_infer.c`, `model_train.c`; re-decide `buffer_read_num` sites as f64, byte or index reads | 53 | `N5.gather-index-fraction-raises`, `N5.pcm16-byte-raises` | + | R1.6e | `ext_gfx.c`, the Q7 readers and whole-shape `eigs_geom_shape` clipping (Liang-Barsky for lines) | 38 | `gate.N5.q7-readers`; table run | + | R1.6f | Non-LOSS `ext_store.c`, `ext_db.c`, `ext_http.c`, `ext_net.c` | 19 | mechanical: table run | + | R1.6g | N9's 77 new width-family builtins in `builtins_int.c`, Rust std oracle | +77 | `N9.wrapping`, `N9.checked-raises`, `N9.saturating`, `N9.unsigned-ops`, `N9.fround` | + | R1.6z | Remaining bridge sites in runtime, containers, embed, trace, compiler and task; delete all 21 tokens and all helpers left without callers | none | mechanical: deletion compile and final full table | + + The pre-N9 rows summed to 233; R1.6g added 77 for a total of 310. + S10-D2 later retires `proc_read_buf` from round c: current c=76, total=309. + Its two frozen cases and dependent plants are explicitly retired in + `tests/phase2/builtins/RULINGS.md` and `builtins_changes.md`; the unchanged + `read_bytes_buf` case preserves the host-byte buffer f64 witness. Ruling R1.6a-D1 added `read_text`, already + present in the R1.6a start (`1fd4422c`) through main's #1841/#1668, to + round c: the original plan's c=75 and total=231 became c=76 and total=232 + at that import. Sync #5's R1.6-S2 adds main's `set_exit_code` to round c, + so the pre-N9 counts became c=77 and total=233; `rm` had no table row and + its deletion under R1.6-S1 does not change the counts. + `tools/phase2_builtins.sh` derives and checks the sub-round by this rule. Every sub-round uses the table gate + (§9.4); later builders add their completed round to + `tests/phase2/builtins_landed.txt` in that commit. R1.6a leaves it empty; + it migrates no builtin and its declaration is not required for later + sub-rounds to advance the derived prefix. Its reader/table gates always run. + The declaration and complete MAP row files together determine CI's + `--through`, so a file that arrived early cannot advance it. + + **R1.6a meter.** `EIGS_R16_DEPRECATED(msg)` marks the 21 bridge tokens + only with `make EIGS_PHASE2_METER=1`; ordinary builds have no deprecation + attribute. The meter is a progress count, not a behaviour change. + Native Make meter builds use the server-db and zlib profiles to include + every capability. Counts below include emitted diagnostics per profile + and the union of distinct file:line:column warning locations (a header + location repeated across translation units counts once in that union). + + | File | server-db emitted | zlib emitted | Distinct locations | + |---|---:|---:|---:| + | `src/builtins.c` | 218 | 218 | 201 | + | `src/builtins_buf.c` | 49 | 53 | 51 | + | `src/builtins_host.c` | 38 | 38 | 37 | + | `src/builtins_tensor.c` | 123 | 123 | 119 | + | `src/compiler.c` | 1 | 1 | 1 | + | `src/container.h` | 420 | 348 | 12 | + | `src/eigenscript.c` | 21 | 21 | 21 | + | `src/eigenscript.h` | 105 | 87 | 3 | + | `src/eigs_embed.c` | 2 | 2 | 2 | + | `src/ext_db.c` | 2 | 0 | 2 | + | `src/ext_gfx.c` | 194 | 194 | 194 | + | `src/ext_http.c` | 11 | 0 | 11 | + | `src/ext_net.c` | 14 | 0 | 14 | + | `src/ext_store.c` | 34 | 34 | 33 | + | `src/model_infer.c` | 15 | 0 | 15 | + | `src/model_train.c` | 4 | 0 | 4 | + | `src/task.c` | 5 | 5 | 5 | + | `src/trace.c` | 1 | 1 | 1 | + | `src/vm.c` | 80 | 80 | 67 | + | **Total** | **1337** | **1205** | **793** | + +7. **R1.7, the float text (N6) and N14's writers.** + - Python-repr floats; ints as exact digits. + - The constant dedup compares bits, so `-0.0` survives. + - JSON writes floats with `.0`; #1762's writers (`store_json_encode`, + `db_build_query`) go through the same N6 writer, unless #1762 landed on + main first. + - `src/freestanding/mini_fmt.c` and `mini_strtod.c` follow, gated by + `make freestanding-libc-diff`. + - This is the largest visible change: every golden that prints an + integral float. +8. **R1.8, the embed API (Q3), lint/LSP type knowledge and docs.** + `eigs_value_as_num` converts an int (like `lua_tonumber`); + `eigs_value_new_int`, `eigs_value_as_int` (with a success flag, like + `lua_tointegerx`) and `eigs_value_is_int` are added; `EIGS_TYPE_NUM` + covers both kinds. Gate: `gate.N7.embed-int` (the embed smoke test), the + executed doc fences (suite [89]/[90]) and the LSP and lint suite sections. + Docs: SPEC, COMPARISON, README, llms.txt; the CLAUDE.md same-PR docs rule + applies to the integration PR as a whole. + +### Round 2: IEEE (N11-N14 remainder), on the experiment's evidence + +1. **R2.1, canonical NaN at the single double writer** (`slot_from_f64`) + **and the JIT NaN bails** (§4.3), while `num_guard` still keeps NaN out: + memory safety first. A mechanical round (no program can make a NaN yet): + the C slot test writes every tag-spelling NaN payload, and the #1746 + plant (a raw store) segfaults in `slot_incref`. +2. **R2.2, retire `num_guard`, `num_guard_named`, `EIGS_NUM_MAX` and the + 1e308 clamp** (74 sites, the leaf's included; 47 first forced here). + `EIGS_STRICT` becomes an opt-in trap (state.c:62). `sqrt of -1` is NaN, + `log of 0` is -inf, `x/0` is ±inf, `0/0` and float `x%0` are NaN. Q8: + `sort`/`sort_by` order floats by IEEE `totalOrder` (NaN after +inf), and + `min`/`max` propagate NaN. `values_equal_impl`'s identity shortcut + excludes floats and extends the container reflexivity predicate to NaN + floats, following §2.6a (R1.6z-D1/D2). +3. **R2.3, N13 refusal inside the writers:** the JSON encoder, the store + encoder (delete the pre-walk `store_nonfinite_scalar`), the byte + encoders and the int conversion. This removes the class #1746 chased for + three rounds, rather than its instances. +4. **R2.4, N12.** `f64_bits`/`f64_from_bits`, buffer bits get/set, and a + canonical NaN at the embed boundary. +5. **R2.5, tape v7 `inf`/`-inf`/`nan` tokens; N6 non-finite printing; + N14 `1e400` raises.** +6. **R2.6, the #1746 residue:** + - the internal `(int)` casts the Q7 readers do not cover (the audio + generators' `dur * rate`, the model builtins); + - `net_send` continuing after a raise (ext_net.c:364); + - `buf_to_pcm16le` with a NaN sample; + - the `numerical_grad` waiver. + +### Round 5 obligations (recorded by earlier rounds) + +Round 5 gives the JIT and the leaf evaluator their int fast paths (§4.4). +It must also recover the declared interpreter costs in +`tests/phase2/cost_expect.txt`: an `off` row runs with `EIGS_JIT_OFF=1`, so +native execution alone cannot recover it. R1.5's fresh R1.4 controls attribute +the DMG-shaped benchmark's N8 increment to exact-int state reaching generic +dictionary/boxed Value paths. The other moved rows have no bitwise operations; +their measured increment is shared VM/helper code generation, including an +out-of-line `slot_from_int`. Interpreter int/slot paths are therefore part of +R5's recovery obligation; each row retains its measured bound and component. + +Until then a check that needs EMITTED code for an int program cannot fire, +so its fixture runs a float program instead. Each such fixture gets its int +twin back when R5 compiles ints: +- `tests/tsan_spawn_emitted_jit.eigs` (TSan shape fixture, R1.3 CI fix). + It races a spawn against emitted JIT code, and its row requires + `[jit] compiled>=1` and `hot`'s native RET after 80 warm calls. The R1.2 + version multiplied ints and stopped compiling at R1.3. It now writes float + literals and float arguments. At R5 the int version returns beside it as + its own `SHAPE_FIXTURES` row with the same witness, and `SHAPE_DECLARED` + grows by one. +- Five more JIT-purposed fixtures (#1858, the R1.3 hardening round). Each + compiled less, or nothing, once int literals were refused. Each now writes + floats in the code its JIT purpose needs. Each int twin returns at R5 beside + its float version, run by the same row: + - `tests/test_spawn_jit.eigs` (suite [100] and the TSan slice). The + main-thread `check`, the shared `helper` and `worker` count in floats. + - `tests/test_underarity_1661_repro.eigs` and + `tests/test_underarity_1661_trycatch.eigs` (suite [0fd], in all three tiers, + the forced-JIT tier included). `g` compares, counts and returns floats. + - `tests/tsan_mt_clear_respawn.eigs` (the TSan slice). `hot`, its argument, + and the module's two loops (OSR after each join) count in floats. + - `tests/test_spawn_jit_warm.eigs` (suite [129] and the TSan slice). `helper`, + `warm`, `worker` and `gmul` are floats, so `helper`'s thunk runs to its + return again. + + The compile counts (`EIGS_JIT_STATS`, summed over threads) were 1, 1, 1, 4 + and 1 on main 94657581. The tip before the change compiled 0, 0, 0, 2 and 1; + after it, 1, 1, 1, 4 and 1. These rows assert no JIT witness of their own + (#1858's second Done-when), so another refusal would again pass them + silently. + +### Risks across both rounds + +- The AOT is a round of its own (ouroboros, pinned to the integration SHA). + Its numeric model is C `double` throughout: 294 `double` in aot_rt.h and + 228 in compile.eigs, with `make_num` 40+27 and `num_guard` 61+27. Under + Alt 1R its generated C fails to compile against the renamed runtime, + which is loud. + - The AOT's existing int census (a `long` storage regime for counters) + must follow literal kinds, not inference. + - `fuzzdiff` must never apply `ULP_BOUND` to an int result (the + aot-differential skill: "Integer data compare exactly even inside a + reduction arm"). +- Consumers. N6 printing moves goldens in all 16; N4 changes `%` on + negatives; (A) raises where doubles lost precision silently; Q1 raises on + a non-integral count, handle or byte, and on a byte outside 0..255 that + used to wrap; Q7 raises on a duration or window size the C call cannot + honour. The DMG emulator feeds `ppu_render_frame` its memory buffer + through `buffer_read_byte`; the consumer acceptance run (Done-when) shows + whether any program stored a non-byte there. +- Observer. Until round 3, ints are observed as their double value (the + distance channel works on doubles), so `where`/`why`/`how` keep today's + meaning. +- The leaf evaluator. It is off while recording, so every recorded run and + every live run compute leaf helpers on different evaluators. The `trace` + tier is the only thing that keeps them equal; it runs on every row. + +--- + +## 9. Test plan + +### 9.1 `tests/phase2/MAP.md` + +The skeleton is committed with this design. Every item (N1-N14, N4b, (A), +(B), T1, O1-O4) has planned rows, each recording: +- an id; +- the program; +- the expected branch output; +- the round that lands it; +- why the row **fails on the v0.46.0 tag**; +- its **shape** and its **tier witness** (below). + +Behaviour the branch must *preserve* (`1 == 1.0` is true on both binaries) +cannot fail on the tag, so it is pinned two ways: +- inside a mapped row that also checks the kind (the row fails on the tag + because the kind is invisible there); +- as a separate **control** row, counted on its own line and never in + `tag_fail` (§121: each population pinned separately). + +**Shapes.** The round-0 rows were all module-level straight-line code. The +critic ran them: both JIT tiers printed `[jit] scanned=0 compiled=0`, so +`phase2_check.sh`'s three tiers would have run the interpreter three times, +and no row reached a JIT producer emitter or the leaf evaluator. Every row +now has one of four shapes: +- `S`: straight-line module code. It runs interpreted in every tier and + claims nothing about tiers (witness `none`). These rows pin semantics. +- `L`: a one-line helper, `define h(x) as: return `, called K >= 1 + times: the leaf evaluator's shape (§4.5). +- `F`: a multi-statement function called at least 60 times, above the + default entry threshold of 50 (jit.c:2478): the function JIT's shape. + `EIGS_JIT_HOT` records a chunk's LAST run, so the witnessed call must be + the function's last call (round 2's two truthiness controls ended on a + float call and could not hold; both are reordered). +- `O`: a `loop while true:` loop, in a function or at module level, run in + the `osr` tier (`EIGS_JIT_OSR_THRESHOLD=1`), **whose only exit is a + `return` placed right after the claimed ops** (`if : return `; at + module level, `print of ` first). The OSR thunk's record holds only its + last entry, and with a `loop while :` header that entry is always the + final condition check, which exits through the header whether or not the + body ever ran natively. With the return inside the body, the only way the + last entry ends at the return sentinel is to run the whole body natively + first. + +The `L`, `F` and `O` rows (44 in the MAP) cover every JIT-emitter and leaf +arm that holds a numeric site (the inventory's `## Numeric arms`, 20 today; +§9.2 checks the coverage): the literals (`NUM_ZERO`, `NUM_ONE`, `CONST` and +the new `INT_*`), the arithmetic, `DIV`/`MOD`, `NEG`/`NOT`/`BNOT`, the +comparisons, the bitwise ops, the conditional jumps, the field, local, name +and index stores, and the leaf's `INDEX_GET`, over ints and floats, with +overflow rows for the leaf, the function JIT and OSR. + +**Tier witnesses.** A row that claims a tier must prove the tier ran its +operation; otherwise it is **VACUOUS**, a FAIL with its own counter. The +witness line is `witness: ... []` (a `leaf` or `leaf-gen` +witness may name several helpers, each of which must hold), followed by +`ops: ...`, the opcodes whose JIT or leaf arm the row exercises: + +| kind | holds when (read from the branch run) | +|---|---| +| `none` | no claim (shape `S`) | +| `leaf ` | in every tier, the normal run's `EIGS_JIT_HOT` table has no entries for ``, and the `trace` run (leaf off) has K >= 1: the leaf computed every call | +| `leaf-gen ` | the normal run and the `trace` run both show the same K >= 1 entries: every call took the generic path (the scanner refused the chunk, or the leaf bailed) | +| `fn ` | in the default and `osr` tiers, ``'s row has `jit yes` and `adv RET`: its last call ran natively to its return | +| `fn-stop ` | `jit yes`, and the opcode at offset `adv` of ``'s bytecode (`EIGS_DUMP_BC=1`) is ``: the thunk handed back at ``, by a compile stop or a guard bail | +| `osr ` | in the `osr` tier, ``'s row has `osr yes` and `oadv == len`: the OSR thunk's last entry reached the return sentinel (jit_advance -1, which `EIGS_JIT_HOT` prints as the chunk length, jit.c:244), so it ran the whole body natively on the way. Round 2's form (`oent + oadv` past `JUMP_BACK`) is withdrawn: the critic's plant ran native `INDEX_SET` 0 times and still passed it | +| `osr-stop ` | `osr yes`, `oadv != len`, and the opcode at `oent + oadv` is ``: every entry handed back at `` | + +Why `EIGS_JIT_HOT` and not only `EIGS_JIT_STATS`/`EIGS_JIT_STOPS`: +`STATS` counts are process-wide (`scanned`, `compiled`), and `STOPS` records +where each compile's prefix ended. Neither says whether a run executed the +operation natively. `HOT` records, per chunk, the last run's native advance +(`adv` for the function thunk, `oadv` from `oent` for the OSR thunk, jit.c: +237-245), which is exactly that. `phase2_check.sh` reads all three: `STATS` +must show `compiled >= 1` for an `fn`/`osr` claim, and for a `-stop` claim +whose stop is a compile stop, `STOPS` must name ``. The leaf has no +counter; its witness is the entry count, because both generic call paths +count an entry (vm.c:2617, vm.c:4353) and the leaf path never does. + +**Calibrated on the main binary: every kind holds on its honest row and is +VACUOUS on a plant where the claimed op did not run as claimed** (design +round 3; a scratch model of the predicates read `EIGS_JIT_HOT` and +`EIGS_DUMP_BC`; the `osr` pair also counted native `jit_helper_index_set` +calls under gdb, with the critic's `gdbcount.gdb`): + +| kind | honest row (main) | evidence | plant | evidence | +|---|---|---|---|---| +| `leaf sq` | `define sq(x) as: return x * x`, one call with 1.5 | `sq normal=0 trace=1`: HOLDS | the body split into two statements (not leaf-eligible) | `sq normal=1 trace=1`: VACUOUS | +| `leaf-gen nth` | `define nth(xs, i) as: return xs[i]`, called on a string (the leaf's `INDEX_GET` bails) | `nth normal=1 trace=1`: HOLDS | called on a list (the leaf computes it) | `nth normal=0 trace=1`: VACUOUS | +| `fn keep` | `t is q / return t`, 60 calls with 2.5 | `keep jit=yes adv=RET`: HOLDS | `zz is [q]` first (the scanner stops at `LIST`) | `keep jit=no stop=LIST`: VACUOUS | +| `fn-stop cat ADD` | `t is a + b / return t`, 60 calls with two strings (the `ADD` guard bails) | `cat jit=yes adv=11`, offset 11 is `ADD`: HOLDS | 60 calls with two floats | `adv=RET`: VACUOUS | +| `osr fill` | the critic's `osr_ret`: `loop while true: (xs[i] is big / i is i + 1.0 / if i >= n: return xs)` | `osr=yes oadv=123 len=123 oent=37`; 3 native `INDEX_SET` helper calls: HOLDS | the critic's `osr_ret_PLANT2`: `s is s + "a"` first in the body | `oadv=20 len=157`; 0 native calls: VACUOUS | +| `osr fill`, round 2's shape | `loop while i < n:` (the critic's `osr_index_set`) | 3 native calls, but no sentinel: VACUOUS under the new rule | `zz is [i]` first (`osr_index_set_PLANT`) | 0 native calls; `oent + oadv` = 109, past `JUMP_BACK` at 106: the round-2 rule PASSED it; the new rule is VACUOUS | +| `osr-stop g ADD` | `loop while true: (s is s + "a" / i is i + 1.0 / if i >= n: return s)` | `oent=33 oadv=20`, offset 53 is `ADD`: HOLDS | `s` a float (`s is s + 1.5`) | `oadv=122=len` (the sentinel): VACUOUS | + +The witness uses only existing diagnostics: nothing in production changes so +a test can observe it. + +**What the witnesses expect in round 1.** The JIT and the leaf bail on an +int that meets an arithmetic, comparison, bitwise or index op, and refuse an +int literal, until round 5 (§4), so such a row's honest witness is +`fn-stop`, `osr-stop` or `leaf-gen`: it proves the guard fired (or the +scanner refused) and the interpreter produced the row's output. An int that +only moves, into a local, a field, a list element or a module binding, +passes through the JIT's C helpers and the leaf's borrows, so those rows +(`N1.fn-int-local`, `N1.fn-dot-fields`, `N1.osr-global-int-store`, +`N5.osr-index-set`, `N1.leaf-dot-get`) have native witnesses already in +round 1. A float row's witness is native too: it proves the float meaning of +`NUM_ZERO`/`NUM_ONE`/`CONST` survived in the JIT and the leaf. Round 5 flips +the int witnesses to native, one row at a time, and each flip is a +deliberate edit of the row. + +### 9.2 `tools/phase2_check.sh [--through ] ` (R1.1 builds it) + +**The landed-round filter.** Each MAP row names the commit that makes it +pass (`[R1.3]`; a row needing two commits names both, `[R2.1/R2.4]`, and +lands with the later). `--through Rk` selects the rows that have landed by +Rk, in the order R1.1 < ... < R1.5 < R1.6a < R1.6b < R1.6c < R1.6d < +R1.6e < R1.6f < R1.6g < R1.6z < R1.7 < R1.8 < R2.1 < ... < R2.6 < R3; controls +run in every mode; the default is the whole plan. A round is checked against +exactly its landed rows, so R1.3's check cannot fail on an R1.7 row, and the +final check runs all of them. Each round has a non-zero set of its own rows, +or a mechanical guarantee stated here, which `--through` runs instead of +failing on zero rows: + +| round | its own MAP rows | its gates, or the mechanical guarantee | +|---|---:|---| +| R1.1 | 0 | mechanical: the compile errors (223 sites), the C slot test (§2.5; `examined` equals the derived classifier count), plant 1, the int-source check, and the differential over the suite's `.eigs` corpus | +| R1.2 | 0 | mechanical: the compile errors (464 sites), the int-source check, and the differential over the suite's `.eigs` corpus (behaviour-preserving) | +| R1.3 | 51 | `gate.N5.iterator-metadata` | +| R1.4 | 14 | | +| R1.5 | 7 | | +| R1.6a | 0 | mechanical: C reader test (`tools/phase2_readers.sh`) and the per-builtin table (§9.4) | +| R1.6b | 9 | per-builtin table | +| R1.6c | 1 | per-builtin table | +| R1.6d | 2 | per-builtin table | +| R1.6e | 1 | `N5.audio-channel-domain` (R1.6e-D1); mechanical: per-builtin table, `gate.N5.q7-readers` through the no-SDL amalgamation, and the pre-registered `gate.N13.audio-buffer-boundary` / `gate.N5.gfx-byte-boundary` actual-client fixtures (all compiled and executed here and at every later `--through`) | +| R1.6f | 3 | R1.6f-D1 shared store handle refusals; R1.6d-D1 net_send byte refusals; mechanical: per-builtin table and the `gate.N5.net-byte-boundary` actual-client socketpair fixture (R1.6f-D2; compiled and executed here and at every later `--through`) | +| R1.6g | 5 | per-builtin table, including the new N9 rows | +| R1.6z | 0 | mechanical: bridge-token deletion compile and final full table | +| R1.7 | 13 (11 change rows, 2 invariant controls) | `gate.N14.db-param-exact`, `gate.N6.const-dedup-bits` (R1.7-D3/D4) | +| R1.8 | 0 | `gate.N7.embed-int` (the embed smoke test), the executed doc fences (suite [89]/[90]), the LSP and lint suite sections | +| R2.1 | 0 | mechanical: the C slot test's NaN-payload rows and plant 2 (no program can make a NaN before R2.2) | +| R2.2 | 9 | | +| R2.3 | 3 | | +| R2.4 | 6 | | +| R2.5 | 6 | | +| R2.6 | 1 | | +| R3 | 5 | `gate.O1.oracle-floor`, `gate.O2.predicate-floors` | + +(The counts are the MAP's at this commit; `phase2_check.sh` derives them, and +fails if a round named here has neither rows nor its mechanical gates.) + +- **Rows** live in `tests/phase2/rows/.eigs`. Each has a header + `# ROW: `, `# ITEMS: N1 N2`, `# EXPECT-RC: `, `# SHAPE: S|L|F|O`, + `# WITNESS: ... []`, `# OPS: ...`, plus `.out` + (the exact branch stdout). Controls live in `tests/phase2/controls/`. + A row that needs a capability build adds `# PROFILE: server|db|zlib`. The + checker then runs it on `$TAG_BIN_

`/`$BRANCH_BIN_

`, the per-builtin + table's matched builds (ruling A3), after each prints `profile-ok` from its + `_profiles/

.eigs` witness. An unset or wrong build is a population + failure; a row never rewrites its own binary path (R1.6f-D4). +- **Population:** + - the row set is the directory listing (it cannot drift from a list); + - every row id appears in MAP.md and every MAP id has a file (both + directions), with the same shape and witness; + - every item in the fixed list (N1-N14, N4b, A, B, T1, O1-O4, transcribed + from #1758 Revision 1) has at least one row; + - the `ops:` of the `L`, `F` and `O` rows together cover every + JIT-emitter arm and leaf-evaluator arm that holds a numeric site, read + from the inventory file's `## Numeric arms` (15 emitter arms and 5 leaf + arms today, each with its opcode chain; an arm is covered when some + row's `ops:` names an opcode of its chain that the row runs natively or + stops exactly at; the MAP's shaped rows cover all 20); + - zero rows is a FAIL, except a mechanical round under `--through` + (the table above), which runs its named gates instead. +- **Tiers:** + - `off` = `EIGS_JIT_OFF=1`; + - `default`; + - `osr` = `EIGS_JIT_OSR_THRESHOLD=1`; + - `trace` = `EIGS_TRACE=/t.tape` with the default JIT. The leaf + evaluator is off under trace (§4.5), so this tier is the **leaf + differential**: every `.eigs` row must print exactly what it prints in + the other tiers, so an `L` row compares the leaf with the generic path + on its int, float or overflow program. (`sh` rows that set `EIGS_TRACE` + themselves skip this tier.) + - Every run is bounded at 30 s, with `= 128 is `SIGNAL`. Both + are a FAIL on the branch; on the tag they count as `tag_fail` and are + also shown in their own counters. +- **Output, one line per tier:** `tier= examined=N rows tag_fail=N + branch_pass=N vacuous=0 tag_timeout=K tag_signal=K`, then `controls=M + both_pass=M`, `witnessed=W` (the rows whose witness held), + `controls_witnessed=C controls_vacuous=0` (shaped controls must prove their + tier on the branch too), and an environment banner (both binaries' `--version` and sha256). +- **Exit 0** iff in every tier examined == tag_fail == branch_pass > 0 + (or, for a mechanical round under `--through`, its named gates pass), + vacuous == 0, the controls all pass on both, and the population checks + pass. +- **Calibration** (one-time, in the commit that adds it): + - tag passed as both binaries: `branch_pass=0`, exit 1; + - one `.out` corrupted: exit 1, naming the row; + - an empty rows directory: exit 1; + - a MAP id with no file, and a file with no MAP id: exit 1 each; + - a `leaf` row run against a scratch build whose leaf scanner returns + early (`chunk_scan_leaf_accessor` never sets the flag): `vacuous=1`, + exit 1; + - an `fn` row whose function is called 10 times instead of 60: `vacuous=1` + (the JIT never compiled it); + - an `L` row run against a scratch build whose leaf `MUL` arm computes + `a * b + 1`: the `trace` tier differs from the others, exit 1, naming + the row; + - **one negative plant per witness kind, each a row where the claimed op + did not run as claimed** (the rows of §9.1's calibration table, which + this design ran on main): `leaf` (the helper split into two statements), + `leaf-gen` (the helper computed by the leaf), `fn` (an unsupported op + before the claimed one), `fn-stop` (the claimed op runs natively), `osr` + (the critic's `osr_ret_PLANT2`: a string concat ahead of the claimed op, + 0 native `INDEX_SET` calls), `osr-stop` (the claimed op runs natively to + the sentinel). Each must give `vacuous=1`, exit 1, naming the row; + - `--through R1.2` on a tree with no R1.1/R1.2 rows: exit 0 by the + mechanical gates, `mapped=0`; and with the int-source check planted (a + production `slot_from_int(count)` call): exit 1. + +**As built in R1.1** (`tools/phase2_check.sh`, a wrapper over +`tools/phase2_check.py`, whose docstring is the reference): +- **Controls land with R1.3**, the first round with rows of its own. Under a + mechanical `--through` they are not required: their witnesses are about + ints (`ctl.N1.fn-int-truthiness` expects the int call to bail), which + cannot hold before an int exists. +- **The leaf witness is read in the default and `osr` tiers.** With + `EIGS_JIT_OFF=1` the hot table counts no call at all (probed on the R1.1 + binary: a function called 60 times has no row), so the `off` tier cannot + witness either way. +- **The token gate** checks, in every mode, that each token a landed round + deleted (2.7) occurs in no code line of `src/` or `tests/` C files. + (Until the second R1.1 fix round it ran only in the mechanical rounds; + it runs in every mode now, as this line said.) +- **The inventory gate** runs in every mode too: + `tools/phase2_inventory.py --check` must PASS, so a slot classifier with + no reviewed disposition row fails the check, and so does a committed + `docs/design/phase2-inventory.txt` that is not exactly the generated + file (#1804: nothing ran the inventory's verdict, and the committed file + had gone stale). +- **The corpus differential** is `tools/phase2_corpus_diff.sh + `: both binaries run every `.eigs` under the branch tree's + `tests/` from the branch tree's `src/` (the suite's cwd), so the file and + whatever it loads by a relative path are the same bytes for both; only + the binary and its stdlib differ. A file only the branch has (a new + fixture) is counted as `new`; one only the baseline has is a DELETED + failure. R1.6a declares `tests/phase2/builtins/` in + `tests/phase2/corpus_exclude.txt`: those programs are owned by + `tools/phase2_builtins.sh`, which provides their fixtures, observation + hooks, matched capability profiles and stdin. The corpus applies the same + declared directory exclusions to both inventories and reports their paths, + counts, owners and reasons. Unsafe, overlapping, stale or ownerless + declarations, and a zero remaining population, fail. This file is the sole + ownership list: JIT/OBS, replay, the Phase 2 corpus differential, the full + Valgrind corpus and the road fixture walker all use + `tools/phase2_corpus_exclude.py`. Each filters its broad population before + narrowing to its existing subset, reports the excluded count and rejects a + declaration that excludes zero files. These probes remain covered by §9.4's + table gate and its once-only calibration plants. + + **As corrected in the R1.1 fix round** (the critic's plants B + and D passed the first version): + - each binary runs each file TWICE. A file is deterministic when the + baseline's two runs agree, and then any branch run that differs is a + DIFF: a branch that is wrong on every other run is wrong; + - randomness runs LIVE with a pinned seed. The runtime has no seed flag + or variable, so the tool builds an `LD_PRELOAD` shim. The drand48 + self-seed is `srand48(tv_sec ^ tv_nsec ^ getpid())` from a + `CLOCK_MONOTONIC` reading taken just before; the shim records each + thread's last such reading and pins a seed equal to that formula, + whatever its value, while a script's `seed_random of N` passes + unchanged. (The first fix round's shim pinned seeds >= 2^20 instead, + and the self-seed falls below that: 5 of 3,000 draws went unpinned; the + second round's pinned 3,000 of 3,000.) libc `rand()`, which + `audio_noise` draws from and `main()` seeds with `srand(time(NULL))`, + is pinned at every `srand` call. `random_hex` reads `/dev/urandom` by + design and stays unpinned; the shim marks every run that opens it, and + those files are listed (`UNPINNED`) with their verdicts. The tool + checks the shim first (two pinned draws agree). A random builtin changed + on the branch therefore shows wherever the corpus observes it; + `test_system.eigs` now draws `random_int` 300 times against its bounds; + - a tape is the last resort, for a file whose baseline disagrees with + itself even pinned (timing, threads): the baseline records it and + replays it twice, the branch replays it once and must match + (`same_under_replay`). Such a file's nondeterministic builtins did not + run live, so their behaviour is invisible there; the report lists every + file adjudicated that way (`REPLAYED`). Files whose replays disagree with + each other stay NONDET, also listed; + - no blanket memory cap: only files that drive the gfx, ui or audio + surface run under `ulimit -v 2097152` (listed as `CAPPED`). + `handles_full.eigs` varied only because of the old blanket cap. + + **As corrected in the second R1.1 fix round** (the second critic's plants + C and B2 passed the whole oracle while the suite went red on C): + - each file runs under its run conditions from the one loader the suite, + `jit_diff.sh` and `replay_diff.sh` use (`tests/suite_program_env.sh`, + policy `tests/suite_program_env.txt`). Before, every file started in + the default strict mode, so 14 fail-soft numeric-boundary files died at + an early strict raise on both binaries, about 394 asserts never ran, and + the two early deaths were counted `same`. The four files the suite ran + with an inline `EIGS_STRICT=0` (and one with an inline + `SDL_VIDEODRIVER=dummy`) now have rows there, so there is no second + source; + - a file with conditions must complete (rc 0) on both baseline runs, the + rule `jit_diff.sh` already applied; otherwise it is INCOMPLETE and + never compared; + - every file whose baseline exits nonzero must be declared, with that + code and a reason, in `tests/phase2/corpus_nonzero.txt` (32 rows: the + runaway guard's fixture, deliberate errors, fixtures of other hosts, + capabilities the release build lacks). An undeclared exit is + UNDECLARED-NONZERO and a row whose file exits otherwise is STALE: both + fail the run. So removing a file's run conditions turns the + differential red, and so does a new test that dies early. Every + declared file is listed with its code (`SAME-NONZERO`); + - a timeout or a crash all four runs agree on is counted as + `timeout_both` or `crash_both`, no longer also as `same`; + - every run uses SDL's dummy audio device (`SDL_AUDIODRIVER=dummy`): a + real capture device delivers samples or not by timing, so + `test_audio.eigs` printed a SKIP on some runs, and with its conditions + applied the two-run baseline check let it through as deterministic and + a branch run that differed read as a DIFF. +- **The smi-trap gate** (`--smi-trap BIN`, R1.1 and R1.2) runs the same + differential with a `make smi-trap` build as the branch; until R1.3 it + must show no difference, because no program may make an int. +- **The calibration** is `phase2_check.sh --calibrate [--build-plants] + `, on the fixtures in `tests/phase2/calib/`: each ends in a line + that fails on the tag (#1740: `5.5 & 1` raises `value` on the branch and + truncates on v0.46.0), so an honest fixture has tag_fail == branch_pass. + `--through R1.2` cannot pass on the R1.1 tree (its token gate would find + `VAL_NUM`, correctly), so the R1.1 run of that item is `--through R1.1`; + the int-source plant runs against a planted copy of `src/`. Cases the + design did not list were added for the plan-level checks (an item with no + row, an arm no row exercises, a round with neither rows nor gates) and + for the token gate (a reintroduced `slot_is_num`). The fix round added the + critic's plants: the differential's A (a deterministic file prints one + more line), B (a branch wrong on alternate runs, both phases) and D (a + build whose `random_int` answers lo+1..hi+1), each of which must make the + differential name the file DIFF; the int-source plants P1-P5, each of + which the static half must name at R1.1 (at R1.2, P4 sits inside an + allowed writer and the static half passes it); and P4 built into a + smi-trap binary, which the dynamic half must stop. The second fix round + added the second critic's plants C (a build whose buffer equality reads + raw elements) and B2 (an int whose tag bits are spelled from + `SLOT_QNAN_MASK`, which the static half does not see, on the smi-trap + build), each of which must make the differential name a conditioned file + DIFF; a removed policy row, which must be UNDECLARED-NONZERO; a ledger row + for a completing file (STALE); a conditioned file that does not complete + (INCOMPLETE); and the inventory gate's plants (`vm.c:critic_new_reader` + with no disposition row, and a stale committed inventory). A restricted + run (the calibration's file list) prints `RESTRICTED` and exits 3, never + PASS. + The calibration is a `tools/selftests.txt` row (`phase2_check.py + --selftest`, which builds v0.46.0 and the four planted binaries itself): + it runs when the checker or its fixtures change, and nightly, never on + every suite or CI run (docs/PRINCIPLES.md 5: a check proves it fires once, + when it is written or changed). + +### 9.3 `tools/phase2_plants.sh` (round 1 builds it; detached, about 7 release builds) + +Each plant: +1. `git archive HEAD` into a scratch dir (no `.git`); +2. applies one `sed`, asserting the original text occurs exactly once before + and the planted text after (a plant that did not apply is `BROKEN`); +3. builds the release with plain `make`, once at a time, `timeout 1500`; +4. runs **its named check** and requires the check's own FAIL line naming + the row (keyed on the gate's message, not on a nonzero exit, §179). + +Verdicts: `KILLED`, `SURVIVED`, or `BROKEN` (patch, build or timeout). +Output: `plants=7 killed=7 survived=0 broken=0`; exit 0 only for exactly +that. + +| # | plant | where | named check that must turn RED | +|---|---|---|---| +| 1 | an int that fits inline stored boxed | `eigs_container_slot` (or `slot_from_int`) skips the small-int rule and stores a `VAL_INT` box | `tests/phase2/slot_layer.c`: every canonicalizing writer of §2.1, driven with every boundary value, must give tag 0xFFFA inside ±2^47 and a box outside. No program row can see this plant (§2.1: everything compares by value), so the round-0 claim that `N1.canon-eq` would turn red is withdrawn; that row now pins only semantics | +| 2 | a NaN stored raw in a slot | `slot_from_f64` without the canonical branch | row `N12.nan-payload-tags` (`f64_from_bits` of every tag-spelling payload into a variable, list, dict and buffer; `type of` each is "num", under ASan in the plant build) | +| 3 | a writer accepting a non-finite value | the refusal inside `eigs_json_encode_value` deleted; separately inside the store encoder | rows `N13.json-refuses-inf` and `N13.store-refuses-nan` (raise `value`, and nothing written: the store row re-reads the record count) | +| 4 | `+` wrapping on overflow | the SMI+boxed add path uses wrapping `uint64` add | row `A.add-overflow-raises` (`9223372036854775807 + 1` raises `value` naming `+`) | +| 5 | `-INT64_MIN` wrapping | `CASE(NEG)`'s int arm without the INT64_MIN check | row `N4b.neg-min-raises` | +| 6 | `num_type of 3.0` returning "int" | `num_type` answers "int" for an integral float | row `N7.num-type-float` | +| 7 | a v6 tape accepted | the version check accepts 6 | row `T1.v6-refused` (the committed v6 fixture must exit nonzero naming version 6) | + +Each plant is transverse (§100): deleting its named check's assertion must +let the plant through. That is shown once per plant during calibration by +running the plant against a copy of the check with its assertion removed. + +The tier-witness and leaf-differential plants (§9.2's calibration) belong to +`phase2_check.sh`'s own calibration, not to this list: they test the +instrument, not the semantics. + +As built in R1.1, the tool holds the plants whose named checks exist: plant +1 (`slot_from_int` tags an int that fits as a heap pointer; its named line +is `slot-layer-test: FAIL slot_from_int(0): bits ...`). Plants 2-7 join the +table in the commits that land their rows, and its output then reads +`plants=7`. Its transverse run deletes exactly the named assertion +(`if (s.u != smi(v).u)` becomes `if (0)`) and requires the plant to pass. + +The R1.1 fix round added three instrument plants on the slot test, each +with its own transverse run: `arm-refusal` (`NUM_CMP`'s int refusal +deleted; named line `slot-layer-test: FAIL arm lt with an int: got ...`), +`stderr-noise` (the population scan writes a line to stderr; named line +`slot-layer: FAIL: the population scan wrote to stderr`) and +`stdout-noise` (the scan prints a line outside its grammar; named line +`slot-layer: FAIL: the population scan printed lines outside its +grammar`). The tool also fails a plant whose check writes to stderr. Like +the calibration, the plants are a `tools/selftests.txt` row (`phase2_plants.sh +--selftest`), keyed on the checks they name (the slot test, its runner and +the population scan), so they run when those change, never per suite or CI +run (docs/PRINCIPLES.md 5). + +### 9.4 The per-builtin behaviour table (R1.6's gate; replaces the int-fuzz class check) + +The table exercises every numeric builtin with a valid call. Integer +results can come from non-numeric inputs (`len`, `ord`, `index_of`, +`buf_len`, `scan_ints`, `read_bytes`); other calls need a valid handle or +shape (`shared_size`). Feeding every builtin a bare int cannot classify +those results. + +**R1.6a amendments**, pre-registered before migration and recorded with the +orchestrator's rulings in `tests/phase2/builtins/{BAR,RULINGS}.md`: + +- **(a) Migration changes compare `.branch` with `.pre`, the R1.5 baseline.** + `changes_vs_pre` is the vacuity guard; `changes_vs_tag` remains an observed + comparison. The original release bar recorded 202 differences from + v0.46.0 among 206 rows. After ruling R1.6a-D1 adds `read_text`, the updated + pre-registered bar records 203 among 207. These are historical tag + comparisons: the tag has no `num_type`, so comparing only with `.tag` + would make the migration check vacuous. The remaining 25 rows use matched + `server`, `zlib` or `server-db` builds, with PostgreSQL for db probes + (ruling A3). + The original `.pre` records came from `4ac31e67`; R1.6a checks them against + its start, `1fd4422c`, and records the permitted #1668 file-failure or + file-replay changes. The new `read_text` row's in-scope `.pre` is recorded + on `1fd4422c`, where #1841/#1668 already landed, so its `changes_vs_pre` + is `no`. Its `.tag` remains the v0.46.0 observation. The current explicit + baseline instruction supersedes BAR.md's handoff parenthetical suggesting + that `.tag` also be recorded on `1fd4422c`. +- **(b) Probes and expectations are files.** `builtins/.eigs` is a + program; `.branch`, `.pre` and `.tag` are exact stdout followed by a + final `rc=` line. Stderr is retained for diagnostics and is never + compared. `cases.tsv` names secondary cases, hook and stdin fixtures; + `rowmeta.tsv` records each builtin's profile and any earlier closure. + Ruling R1.6a-D2 makes probes print predicates over OS-selected ports, + handles, clocks and paths instead of those variable values themselves. + The deterministic injected-clock witness still asserts its exact payload. +- **(c) Landed sub-rounds are declared.** + `tests/phase2/builtins_landed.txt` holds completed `R1.6a`..`R1.6z` + tokens, one per line (comments and blank lines allowed). Each later + builder adds its round in the completing commit. Empty in R1.6a means + every current row must still equal `.pre`. A row's round follows §8's + rule applied to the file defining its inventory C function. + +`tests/phase2/builtins/builtins.tsv` is checked by +`tools/phase2_builtins.sh `, which calls the bar's +single table checker and runner. Its checks are: + +1. **Membership, both directions.** Rows equal the inventory's `## Builtins` + list and are nonempty; `class`, `loss` and `q7` equal §6, parsed with the + inventory's own table parser; the defining C file determines `round`. +2. **Class consistency of `.branch`.** An int-preserving result shows + `"int"`; a float-producing result shows `"float"`; an int-requiring + call raises `value` on a fraction and succeeds with an integral float. + Every Q7 class named in the row is exercised. Where stdout cannot + identify that property, a machine-readable probe header names the + exercised path and the checker validates it. Boundary hooks assert + delivered PCM, mixer gain/volume, an injected clock or injected events. +3. **Run.** A landed row equals `.branch`; every other row equals `.pre`. + The tag equals `.tag`. Each run gets a fresh `EIGS_PROBE_TMP`, headless + graphics and audio as in the suite, the recorded stdin, hooks and + capabilities. Non-release profiles use `TAG_BIN_SERVER/ZLIB/DB` and + `BRANCH_BIN_SERVER/ZLIB/DB`; db rows require `DATABASE_URL`. A missing + capability or a timeout fails, rather than supplying an expectation. +4. **Vacuity.** `changes_vs_pre` is `yes` exactly when `.branch` differs + from `.pre`. Every LOSS, int-requiring and Q7 row needs `yes`, except an + individually recorded `closed_in` ruling (A2). That earlier closure is + checked by requiring `.pre == .branch` on all its cases. No blanket + waiver is allowed; `.branch` files are never edited by a builder. + +A passing run prints exactly one +`phase2-builtins: PASS rows= landed= migrated= unchanged=` +line; a failure names the row and reason. `make_table.py --final` rejects +pending profile records. The same checker enforces the complete `.branch` +pathname/content set against the approved git object pinned in `make_table.py`, +read with `git ls-tree` and `git show`. A missing pin fails; the phase2 workflow +fetches it explicitly. Every changed, added or deleted path needs an exact +amendment in `tests/phase2/builtins_changes.md`, with its new SHA-256 (or +`DELETED`), orchestrator ruling and reason. Accepted amendments print their +ruling id; unlisted differences and stale amendments fail by path. + +**Calibration**, once when the gate is written or changed: a `len` result +planted as `make_float(count)` turns its row red; declaring R1.6b landed +on the R1.6a build turns every b row with changes red; deleting a row and +adding a stale row fail membership in both directions; editing `class` +fails; making a LOSS row's `.branch` equal `.pre` is VACUOUS. The bar's +own plants additionally pin its fixture, profile, hook and stdin checks. +After the matched profile records are complete, `determinism.sh` records +all cases three times (including a pinned, lowered-priority run) and +compares every `.pre` and `.tag`; any unstable record fails (R1.6a-D2). +`read_text` uses the `1fd4422c` pre binary for each recording, while every +tag recording continues to use v0.46.0. + +The int-fuzz sweep, if kept, is only a crash sweep: each builtin fed ints +in numeric slots must raise or return, never crash. + +### 9.5 `tools/phase2_lib_diff.sh ` (#1848, from R1.3) + +A real-valued stdlib formula can compute in int64 through an int literal of +its own and an int argument: `rest_energy of 103` (an int speed of light) +and `poisson_pmf of [21, 5.0]` (an int `k!` denominator) raised where main +answered. A grep cannot find the second kind, so this is a differential. +- **Population:** every tracked `lib/*.eigs` module's import namespace (its + `fn` keys), checked against the modules' column-0 `define` lines in both + directions. A function is called once, through its own module, or through + a module that re-exports it when its own cannot be imported alone (`ui`'s + `ui_w_*` fragments). `lib/pkg.eigs` is the one declared non-library module + (importing it runs the `--pkg` CLI); any other unexamined module fails. +- **Same input:** both binaries run a copy of the branch's `lib/` under a + scratch project root, so only the runtime differs. +- **Calls, the stated range:** every int n in [-3, 110]; arity 1 calls f(n), + arity k >= 2 the diagonal and, per position, n there with 1 elsewhere. +- **Verdict:** a call is listed when main completes it and the branch raises + or never reaches it. A listed function must be integer-domain and declared + in `tests/phase2/lib_int_domain.txt` with its reason (decision (A)); a + declaration no call needs is stale. Both fail. +- At R1.3: 889 functions, 281,331 calls; `probability.factorial` is the one + declaration. `rest_energy`, `poisson_pmf`/`poisson_cdf` and + `fmt_num`/`fmt_percent` were fixed (a float constant, a float `k!` + denominator, a float scale factor). The fixed `poisson_pmf` is + byte-identical to main on 888 points and within 1.3e-13 of SciPy's + `poisson.pmf` (the oracle). Calibration: the int `SPEED_OF_LIGHT` planted + back lists `rest_energy` UNDECLARED; a declaration for `permutations` is + STALE. +- **R1.4, N4 successful value changes.** The original OK/RAISE differential + cannot see a changed successful remainder. The same tool now derives a + conservative modulo-user/named-caller closure from the tracked module + source (including private helpers), and compares JSON result values for + its exported functions, ignoring only numeric-kind differences. Every + changed function needs an `N4:` reason in + `tests/phase2/lib_floor_mod.txt`; an unused declaration is STALE. The + integer-domain raise declarations remain independent and unchanged. + Negative boundary probes (-7 and -719469) reach weekday and civil-era + boundaries missed by [-3, 110]. Typed probes cover the meta-interpreter's + source/AST entry points, accepted negative numeric checksum buffers, and + supervision's negative heartbeat observer feed as well as its return. + The closure is conservative by name, not a full resolver; the release + census reviews dynamic dispatch and caller domains separately. Successful + values outside that closure remain outside this extra N4 comparison. +- **Range boundary:** ints at most 110 keep a polynomial of degree 9 in the + caller's own ints inside int64, so a listed call points at the library. + A literal that overflows only for larger arguments is not seen. + +--- + +## 10. The Lua differential tool (`tools/phase2_lua_diff.py`, round 1) + +**Oracle.** +`/home/jon/src/InauguralSystems/EigenScriptEcosystem/EigenLua/.ref/bin/lua` +(PUC-Rio 5.4.9, pristine). +- Checked at start with `EigenLua/tools/build_ref.sh --check-not-compat + `. The binary argument is required; the bare form fails with + `$2: unbound variable`. It exited 0 on this box. +- A failed check stops the run as an instrument failure (exit 2), never as + a verdict. + +**Cases.** Expressions over operands drawn from the edge set {0, ±1, ±2^31, +±(2^47 - 1), ±2^47 (the SMI boundary), ±2^53, ±(2^53 + 1), ±2^62, +INT64_MIN, INT64_MAX, 0.5, -0.0, 1e308, 2^63 as a float}. Operators: +- arithmetic `+ - * / // %`; +- unary `-`; +- bitwise `& | ~ << >>`; +- comparisons `== < <=`; +- `math.abs`, `math.floor`, `math.ceil`, `math.max`, `math.min` against + `abs`/`floor`/`ceil`/`max`/`min`; +- `math.type` against `num_type`. + +Translation: Lua binary `~` is EigenScript `^`, Lua `^` is `pow of`, and +Lua unary `~` is `~`. + +String operands are excluded: Lua coerces `"10" + 1` and EigenScript does +not. That divergence is documented and not exercised. + +**Comparison by value, never by text.** +- Each side prints its result in a canonical machine form. Lua uses + `math.type(x)` plus `string.format("%d" | "%a", x)`. EigenScript uses + `num_type` plus `str` for ints, and `f64_bits` once round 2 lands it; + until then, float text parsed to a double by Python, whose float parsing + is correctly rounded. +- Kind and value must match. +- A raise on both sides compares as `RAISE` (for example Lua `n//0` "attempt + to perform 'n//0'" against EigenScript `value`). + +**Deliberate divergences**, each a rule with its own counter, never a skip: + +| rule | Lua 5.4.9 (probed) | EigenScript | how the tool checks it | +|---|---|---|---| +| (A) | `math.maxinteger + 1` gives `-9223372036854775808` (wraps) | raises `value` naming the operator | the tool computes the exact result in Python (arbitrary precision); when Lua's int result differs from it, EigenScript must raise | +| N6 | prints `%.14g` (`0.3`, `1e+15`, `-nan`) | Python `repr` (`0.30000000000000004`, `1000000000000000.0`, `nan`) | values compared by bits; EigenScript's text is checked against Python `repr` separately (Python is the N6 oracle, not Lua) | +| (B) | `type(3)` is "number", `math.type(3)` is "integer", `math.type("3")` is nil | `type of 3` is "num", `num_type of 3` is "int", `num_type of "3"` is null | name mapping number to num, integer to int, nil to null | +| N4b | `-mininteger`, `math.abs(mininteger)` and `mininteger//-1` all give mininteger | each raises `value` | expected RAISE; `mininteger % -1` is 0 on both (no divergence) | + +**Output:** `examined=N agree=N rule_A=N rule_N6=N rule_B=N rule_N4b=N +disagree=0`. It exits 1 on any unexplained disagreement or on examined=0. + +**Calibration plants:** +- flip rule (A) to expect wrapping: red on the overflow cases; +- compare by text instead of value: red on the N6 cases; +- a fake `lua` that prints `float` for an int: red. + +--- + +## 11. Decided questions (Q1-Q8) + +Q1-Q6 were open in round 0. They were decided on 2026-10-14 by the +standard answer, each verified by probe, in +[#1758 (comment)](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6171744498). +The owner may overrule any of them; until then this design follows them. + +- **Q1, N5's scope: DECIDED.** The parameter's meaning decides, not the C + type. + - Integer-valued parameters (indices, counts, sizes, byte values, + handles, ports, seeds) follow N5: `1.5` raises `value`. Lua: + `string.sub("abc", 1.5)` raises "number has no integer representation". + - Continuous parameters (gfx coordinates and sizes, colour channels, + durations, audio gain) accept floats; where the C API needs an int, the + conversion rounds to nearest, replacing truncation toward zero (SDL3's + float coordinates, LÖVE's numeric drawing API). + - Applied: §6's rows (16 moved from other to int-requiring; byte + consumers no longer wrap), `eigs_byte_arg`, `eigs_int_arg_range`, and + the Q7 readers (§2.6), R1.6 (§8). +- **Q2, an int stored into float storage: DECIDED.** Convert with IEEE + round-to-nearest, documented as the container's contract (Python + `float(2**53+1)`, numpy, JS `Float64Array`). Applied: `buf_set`, + `buf_from_list`, `copy_into`, `set_at` on a buffer, `stream_write`; the + inventory's `buffer_read_num` sites keep their token (§2.3a). +- **Q3, the embed API: DECIDED, Lua's C API.** `eigs_value_as_num(int)` + converts (lossy above 2^53, like `lua_tonumber`); add + `eigs_value_new_int`, `eigs_value_as_int` (succeeds only for an exact + int64, with a success flag, like `lua_tointegerx`) and `eigs_value_is_int` + (like `lua_isinteger`); `EIGS_TYPE_NUM` covers both kinds (like + `LUA_TNUMBER`). Applied: R1.8; the 48 `eigs_value_as_num` sites keep + their token (§2.3a). +- **Q4, `round`: DECIDED.** Keep half away from zero (C and Rust `round`); + the result is an int when it fits int64 and a float otherwise, like Lua's + `math.floor`/`math.ceil` (`math.floor(1e300)` stays `1e+300`). Applied: + §6 (`round` int-preserving, LOSS), R1.6; the Q7 readers use the same + tie rule. +- **Q5, `why is x` for an int binding: DECIDED.** An int when Δv fits in + int64, otherwise a float. A readout never raises; (A) applies to + operators, not to observer readouts. Applied: round 3 (O4). +- **Q6, `//=`: DECIDED.** Add it beside `/=`. Applied: R1.4. + +- **Q7, a finite continuous parameter outside the C range: DECIDED** + ([#1758 comment 6174302316](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6174302316)). + It follows the quantity it represents: + - geometry (coordinates and sizes): clip in float space against the + drawable area, then round to nearest. Saturating each argument + separately is wrong: `gfx_rect of [-3e9, 0, 6e9, 10]` would cover half + the screen instead of all of it; + - bounded quantities (colour channels, gain, volume): clamp to the domain; + - unbounded magnitudes the C call cannot honour (durations, sleeps): raise + `value`, naming the builtin (Python's `time.sleep(3e12)` raises + `OverflowError`). + - Applied: the builtin table's validated `q7` column (§6); the readers + `eigs_geom_shape`, `eigs_bounded_arg`, `eigs_magnitude_arg` (§2.6), + landing in R1.6. Two classifications this design made inside the + decided categories: a window size (`gfx_open`) and a measurement's + scale (`gfx_text_width`/`height`) have no drawable area to clip against, + so they are magnitudes. +- **Q8, NaN in an ordering: DECIDED** (same comment). IEEE 754-2019 + `totalOrder`: the canonical (positive, quiet) NaN sorts after +inf, + deterministically, in `sort` and `sort_by` (numpy's `sort` and Rust's + `f64::total_cmp` give the same order); `min` and `max` propagate NaN + (IEEE 754-2019 `minimum`/`maximum`). Lua and Python produce a garbage + order here (probed for #1758: Lua's `table.sort` left a list holding NaNs + unsorted without an error). Applied: R2.2, when NaN first becomes + reachable; rows `N11.sort-total-order` and `N11.min-max-nan`. + + +## 12. R1.2 verification notes + +The fix round merged `origin/main` at `540f0bcf` before measuring. It keeps +`VAL_DICT = 7` after main's removal of `VAL_JSON_RAW`, with `VAL_INT` still +appended. Main's taped `audio_noise` is included: the merged replay gate +reported `258 programs record+replay; 21 at the documented boundary; +0 nondeterministic; 1 ledgered`, confirming the retired `test_audio` row. + +**Float cost.** The former three-probe report missed for-in and boxed-index +costs. `tools/phase2_cost.sh BASE CAND` now derives its in-repo workloads +from `codspeed.yml`, deduplicates tier aliases and measures every workload +with the JIT on and off. The CodSpeed list now includes all tracked +`bench/*.eigs` and `tests/bench_*.eigs` files. A Git-derived coverage check +rejects missing or stale entries; unsupported commands also fail before +measurement. The three planted config faults were rejected (3/3), and the +CodSpeed target check reported 19 declared targets with its 7/7 self-test. The declared regression threshold is +0.3% Ir; +any result above +0.1% needs attribution. A failed program, sanitizer binary, +missing instruction count or different result fails the tool; only printed +elapsed milliseconds are normalized. Logs retain each run's stdout, stderr +and cachegrind data. This is the narrow, externally anchored check required +by principle 5, Checks are anchored outside the system, narrow, and few. + +Same-environment release builds (gcc 13.3.0, default Makefile -O2 flags), +Valgrind 3.26.0.codspeed7, cache simulation disabled. Both columns compare +against the fresh `540f0bcf` build. The unfixed `a49ebe58` binary failed +with `workloads=15 rows=30 over_0.3pct=17 FAIL`; the corrected binary passed +with `workloads=15 rows=30 over_0.3pct=0 PASS`. Every corrected delta is +negative, so no positive delta above +0.1% needs attribution. + +| workload | unfixed JIT | corrected JIT | unfixed interpreter | corrected interpreter | +|---|---:|---:|---:|---:| +| `bench/alloc_heavy.eigs` | -0.7750% | -0.8015% | -1.3411% | -1.3247% | +| `bench/conservative_observed_loop.eigs` | +2.4924% | -1.9758% | +1.2051% | -1.7020% | +| `bench/conservative_unobserved_loop.eigs` | +3.1284% | -2.4944% | +1.3736% | -2.1230% | +| `bench/dict_ops.eigs` | +0.7736% | -0.5208% | +0.7736% | -0.2752% | +| `bench/observed_loop.eigs` | +4.6862% | -3.5071% | +1.7607% | -2.7203% | +| `bench/scalar_loop.eigs` | +4.1131% | -3.1679% | +1.5824% | -2.5250% | +| `bench/string_build.eigs` | +1.0707% | -0.0827% | +0.8484% | -0.0920% | +| `bench/unobserved_armed_loop.eigs` | -0.5739% | -0.5734% | -2.6672% | -2.5973% | +| `bench/unobserved_loop.eigs` | +4.6856% | -3.5065% | +1.7599% | -2.7201% | +| `tests/bench_buf_mix.eigs` | -0.4679% | -0.4750% | -1.3133% | -1.6209% | +| `tests/bench_buf_resample.eigs` | +0.3307% | -0.1916% | -0.6375% | -1.0595% | +| `tests/bench_dmg_shape.eigs` | -0.1762% | -0.1807% | -1.5526% | -1.9744% | +| `tests/bench_idxset.eigs` | +0.2827% | -0.4619% | +0.6823% | -0.5958% | +| `tests/bench_pcm_codec.eigs` | -0.4691% | -0.4698% | -0.8728% | -1.3577% | +| `tests/bench_perf.eigs` | +0.8874% | -0.9616% | -0.1238% | -1.4599% | + +Function counts and disassembly localized the remaining cost after merely +moving the four refusals: numeric views and their cleanup checks still +classified ints on float paths, and iterator metadata was read through +unnecessary facades. The corrected iterator uses the checked float reader +once per metadata slot and borrows its reference-valued iterable directly; +int metadata still reaches the named R1.3 refusal after a failed float read. +`slot_view` and valid-view cleanup now exit before int checks. Index +validation and reference acquisition remain inline. No int producer was +introduced, and the sort-key pair layout was retained. + +**Integer readers and refusals.** The supplied critic harness was rerun over +all 341 supplied scenario blocks, in the interpreter, default JIT and forced +OSR tiers, with float, SMI and boxed-int inputs. Across 6,138 transcript and +exit-status files, results and statuses were unchanged from the critic's +R1.2 binary. The only 198 byte differences were heap addresses in the named +boxed-slot refusal diagnostic; comparisons retained the diagnostic, slot +tag, SIGABRT status and every other output byte. There were no unexpected +crashes. The separate C checks reported `checks=409 fails=0`, including +`sizeof(SortByPair)=16`, union payload offset 0, kind offset 8 and index +offset 12. + +A focused iterator probe used the state `[[42], 0.0, 1.0]`: the two float +controls yielded 42; replacing either metadata slot with an SMI or boxed +int reached its named R1.3 refusal in both executors. Result: +`cases=10 float_controls=2 int_refusals=8 failures=0`. +A separate malformed-iterable differential agreed with the original R1.2 +binary in four JIT-helper cases: two int-index refusals and two snapshot +bails, with no failures. This preserves the helper's early-return order. + +**Earlier gate calibration.** The original R1.2 calibration inserted a +`VAL_NUM` token in `builtin_usleep` (token hits=1 and compile exit 2), +removed the JSON encoder's `VAL_INT` arm (compile exit 2 under +`-Werror=switch`), and removed the boxed `slot_int_value` reader (five +assertion failures). These are historical one-time fault checks, not +additional reruns in this fix round. Restored slot coverage remains +33 sites, 165 calls and 396 matrix cells with no failures. + +**Builds.** Default release builds at -O0, -O1 and -O2 each exited 0 with +zero warnings. The final -O2 rebuild was byte-identical to the measured +candidate (`04c10293ab1b94b92ac5ae56063655038c3b47b8a245ab3d763ca0978682614b`). + +**Completed suites.** Full release: `4289/4289 passed, 0 failed, 9 skipped`. +Full ASan with `detect_leaks=1` and no virtual-memory limit: +`4281/4281 passed, 0 failed, 8 skipped`; LeakSanitizer nonzero-exit +tally 0 and sanitizer error reports 0. Both child-script ledgers reported +0 nonzero exits. `make jit-smoke`: `JIT smoke: all cases passed.` + +**GUI conditions.** An initial native-X11 JIT differential run omitted the +graphics memory cap and reported two extra OBS rows, `ui_canvas_events` +and `ui_dock`. Both examples are among the nine nonterminating GUI +comparisons already tracked by [#1823](https://github.com/InauguralSystems/EigenScript/issues/1823). +A focused check on fresh main and the candidate used X11 and dummy SDL, +REF and OBS, and a 1,500,000 KiB virtual-memory cap: all 16 runs agreed +with empty output and exit 124. These are timeout agreements, not completed +GUI comparisons. The existing ledger was retained. + +A process-wide 1,500,000 KiB JIT cap changed `handles_full` on both main +and the candidate: uncapped REF/OBS each reported `spawned=255 raises=45`, +while the blanket cap caused variable earlier spawn failures. The final +JIT/replay invocations therefore apply the existing corpus graphics-source +selector to each program, exec the same release binary, and leave nongraphics +thread fixtures uncapped. Strict graphics checks use their existing child +limit. The corpus graphics ceiling is now 1,500,000 KiB too; a restricted +real-VM probe failed its ceiling assertion at the previous 2 GiB setting +and passed at the machine setting (1/1 capped program), with the expected +restricted exit 3 in both calibration runs. The temporary probes were removed. + +**JIT differential.** With graphics-only caps and dummy SDL, the unchanged +ledger passed: `258 programs x {jit, osr} vs the interpreter; suite-env +completed=15; OBS examined=647 denied=12; 32 arms adjudicated; 2 ledgered`. +The nine interactive timeout agreements in #1823 remain a coverage limitation. + +**Replay differential.** The unchanged fidelity ledger passed under the same +graphics-only resource policy: `258 programs record+replay; 21 at the +documented boundary; 0 nondeterministic; 1 ledgered`. + +**Strict differential.** The real-X11 gate passed. Typed fixed-shape +controls: `declared=139 examined=139 passed=130 failed=0 unavailable=9 +backend_absent=0 pending=0 unrun=0`. All 37 pixel rows were identical with +strict mode off; the 23 wrong-input pixel rows all raised under strict +mode, with silent=0 and misattributed=0. + +**Candidate corpus.** Against fresh main, the full corpus comparison reported +`examined=458 same=452 same_nonzero=42 same_under_replay=4 nondet=1 diff=0 +new=14 deleted=0 crash_both=0 timeout_both=1 conditioned=15 incomplete=0 +undeclared=0 stale=0 ledger_refused=0 unpinned=1 capped=13` and PASS. +The 11 newly merged deliberate-error fixtures account for the increase +from the earlier 31 declared nonzero results to 42. + +**Final Phase 2 gate.** `phase2_check.sh --through R1.2` used the fresh +main baseline and a separately built SMI-trap binary. The trap corpus +reported the same full 458-file summary as the candidate corpus, including +`diff=0 incomplete=0 undeclared=0 stale=0` and 13 graphics-only caps. +Both corpus halves passed; `mechanical smi-trap: PASS` and +`phase2-check: PASS`. The retired-token scan reported `tokens=9 files=89 +hits=0`; inventory `examined=2012 classified=2012 unclassified=0`; int +source `make_int_defs=0 outside_writers=0`; slot matrix +`examined=33 calls=165 matrix_cells=396 arm_rows=32 failures=0`. +The changelog fragment check exited 0 after all fourteen malformed fragment +bullets were repaired. No push or CI run is claimed by these local results. + +### R1.7 rulings and executable text oracle + +R1.7-D2 permits only the mechanical N6 float-text re-render of seven frozen +probes. `tools/phase2_float_amend.py` checks numeric positions and double bits, +source-bound live kinds, exact integer spellings and every other byte. Six +probes change; json_path already has the N6 spelling. Accepted SHA amendments +and earlier authorities are recorded in `tests/phase2/builtins_changes.md`. + +R1.7-D3 distinguishes CHANGE rows from `[R1.7, invariant]` controls. Actual +b6f9db38 already printed the D1 boundary literal with 15..17 round-trip digits; +printing it directly still passes. D1 and exact int text are invariants. +T1 adds 0.1 as a discriminating shortest-text witness. Every change is base-red +and every invariant base-green. Invariants run all declared branch tiers; +the older v0.46 anchor predates their already-landed contract. + +R1.7-D4 keeps the signed-zero program and adds the actual constant-pool C +probe through `tools/phase2_readers.sh`: distinct indices and memcmp-exact +stored signed-zero doubles. A complete dedup-by-value plant restores both +zero-normalizing hash keys and numeric equality, and must fail that probe. + +The actual shared writer is also checked by `tests/phase2/num_text.c`, Python +repr comparisons over binary64 patterns, and the same production function +linked to mini_fmt/mini_strtod. The runtime retains its inherited default +round-to-nearest floating-point environment assumption. + +Cost comparisons opt in to `tools/phase2_cost.sh --n6-text`: finite value bits +and exact integer digits stay equal, changed float tokens must be Python repr, +and nonnumeric bytes/positions stay exact. The default mode stays byte strict. +This cost-only comparison cannot establish a legacy unadorned token's source +kind; the semantic gates and D2 live kind witnesses do. Corpus diffs remain +exact and are attributed individually from observed evidence. diff --git a/docs/llms.txt b/docs/llms.txt index 9a4739ff4..790b22f98 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,5 +1,21 @@ # EigenScript — single-file reference for language models + +Width arithmetic uses explicit wrapping, checked and saturating builtins. +N5 converts integral floats, then checks the named type range without masking. +Width results are ints; checked overflow raises `value`. `u64` uses int64 bit +patterns. `fround` returns a float after double-to-float32-to-double conversion +(ints convert to double first). See [the width builtins](BUILTINS.md#width-arithmetic-n9). + +```eigenscript +print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]] +print of (fround of 0.1) +``` +```output +[-9223372036854775808, 0] +0.10000000149011612 +``` + > EigenScript is a small dynamically-typed language with one distinctive > feature: an **observer** tracks every value's entropy/trend on each > assignment, so convergence, stability, and oscillation are first-class. This @@ -8,6 +24,9 @@ > are where that fails. When unsure, generate, then run the validation ladder at > the end. +Builtin `sort` sorts a list in place; `sort_by` returns a new list using numeric keys. +Both are stable on every platform: equal elements or keys keep their input order. + ## Calls use `of`, never `f(x)` Hosted release includes lazy graphics. The server profile adds HTTP, raw TCP, @@ -108,7 +127,8 @@ not any outer `n`. binding; reading it after the loop is `undefined variable` (a pre-existing parameter/local/module binding is restored instead). Plain `is` in its body binds in the enclosing scope, including an imported module. Comprehension - variables DO leak. + binders are local to the comprehension's filter/result, and catch binders + are local to their handlers; neither creates or overwrites an outer name. ## Reserved and soft keywords (cannot be plain variable names) @@ -183,9 +203,17 @@ sqrt(2) = 1.414213562373095 `W024`) — give each entity its own named binding or a closure per entity. - `converged` on a numeric binding needs a full window whose relative steps are all below the fixed settle deadband, with finite raw-step guards; a constant - therefore converges regardless of its magnitude. Non-numeric bindings use the - entropy classifier. See docs/PREDICATES.md for the routed definitions and the - fixed tolerance's authoritative source. + therefore converges regardless of its magnitude. Non-numeric `converged` + and ordinarily `stable` compare bounded, detached assignment snapshots; + finite alias mutations remain visible on reassignment, and refused or + oversized snapshots leave both false. For an accepted snapshot containing + a NaN buffer, raw IEEE equality keeps `converged` false while `stable` uses + its entropy-window rule after a full bounded comparison window (S10-D1). + Refusal clears that eligibility even if entropy has a full window already. + The real matmul scenario's equality / + stability / convergence is `false` / `true` / `false`, without equality + changing data or math flags. The other predicates and `report` use entropy. + See docs/PREDICATES.md for the routed definitions and fixed tolerance. - Temporal reads: `prev of x` (value before the last assignment), `what is x at 12` / `state_at of 12` (past-line state). Bare `x at 12` is a parse error. A statement spanning several lines is filed under its FIRST @@ -212,24 +240,75 @@ sqrt(2) = 1.414213562373095 `false`, `0`, `null`, `""`, `[]`, `{}` are falsy. - `+` concatenates **strings only**. List concatenation is `append of [xs, v]` (in place) or a comprehension — `xs + ys` is a runtime error. -- Numbers are f64 and finite by construction: overflow saturates at ±1e308. - Integer bit ops are `bit_and`/`&` etc. on int64; exactness ends at 2^53. +- Numbers are signed int64 or finite f64. Decimal integer literals within + int64 are exact; a point/exponent or out-of-range decimal literal gives + float. Hex literals wrap modulo 2^64. `type of` says "num" for both; + `num_type of` says "int"/"float", or null for a non-number. Int +, -, * + raise value on overflow; unary minus and abs raise at INT64_MIN. Mixed + arithmetic converts to float, while cross-kind comparison is exact. + True division `/` produces a float. Counts and indices produce ints; + other builtin result kinds are documented individually. Float overflow + saturates at ±1e308. + Integer bit ops and bit_* builtins accept exact int64 operands and return ints. + Right shifts zero-fill, negative counts reverse direction, and counts outside + (-64,64) give zero (Lua 5.4). A bit-op operand that is not an exact integer (`1.5 | 0`, `1e30 & 1`) raises a `value` error; it is never truncated. Strict mode is the DEFAULT: out-of-domain math (`sqrt of -1`), a NaN result, a wrong-typed builtin argument (`abs of "x"`) and a malformed `json_path` document raise catchable errors. `EIGS_STRICT=0` is the per-run - opt-out that brings the finite stand-ins back (`NaN` collapses to `0`, - `sqrt of -1` is `0`, `abs of "x"` is `0`). - Raw non-finite tensor results receive the same guard whenever an element is - read from a buffer, independent of the consuming operator. Structural buffer - equality, scalar reductions, mixed buffer/list materialization and numeric - byte/sample/device conversions normalize their buffer inputs too and stop at - the first raised read. Numeric bytes truncate and wrap modulo 256 afterward; + opt-out that brings the finite stand-ins back (`NaN` collapses to float `0.0`, + `sqrt of -1` is `0.0`, `abs of "x"` is `0.0`). + Indexing and iteration refuse stored NaN and infinity with `value` in + every strict mode, matching `get_at` (N13). Strict NaN keeps its first + diagnostic and sets `math_flags.invalid`; every execution tier agrees on + the error kind, message and source line. + The other scalar reductions, mixed buffer/list materialization + and numeric byte/sample/device conversions normalize their inputs and stop at + the first raised read. `write_bytes` requires exact bytes in 0..255 + (N5/Q1); other byte converters retain truncation and wrapping at this stage; raw copies, serialization and buffer-only work areas preserve representation. `type_mismatch` error by default; `EIGS_STRICT=0` retains numeric-zero substitution. `str_from_bytes` stops at numeric NUL without inspecting later elements; byte codecs consume the complete list. + +Bitwise operators and `bit_*` builtins return ints, including when an +operand is an integral float. They use Lua 5.4's 64-bit shift rules: `>>` +zero-fills; a negative count shifts in the opposite direction; a count of +64 or more, or -64 or less, gives zero. Each operand must be an exact int64; +a fractional or out-of-range float raises `value`. + +```eigenscript +print of [1 << 64, 1 << -1, -1 >> 1, 8 >> -1, 1 << 63] +print of [bit_shr of [-1, 1], bit_shl of [8, -1], num_type of (3.0 & 1)] +``` +```output +[0, 0, 9223372036854775807, 16, -9223372036854775808] +[9223372036854775807, 4, "int"] +``` + +`//` floors the quotient; `%` has the divisor's sign. `//=` applies the same +operation to its assignment target, like `/=`. Zero divisors raise `value` +for int, float and mixed operands. Two ints produce an int for +`//` and `%`; a float operand produces a float. `/` always produces a float. +Float and mixed `//` use Python’s `divmod` quotient correction: `1.0 // 0.1` +is `9.0`, and `1e-300 // -1e300` is `-1.0`. `//=` uses the same rule. + +```eigenscript +x is -7 +x //= 2 +print of [x, 7 // -2, -7 % 3, 7 % -3, -7.5 % 2] +print of [num_type of (7 // 2), num_type of (7.0 // 2)] +y is 1.0 +y //= 0.1 +print of [1.0 // 0.1, 1 // 0.1, 1e-300 // -1e300, y, num_type of y] +``` +```output +[-4, -4, 2, -2, 0.5] +["int", "float"] +[9.0, 9.0, -1.0, 9.0, "float"] +``` + - Hex integer literals: `0xFF`, `0X10` (digits only). Hex-float forms (`0x1p4`) are parse errors. No `mod` keyword — the operator is `%`. - Precedence: comparisons bind tighter than word-form `not`; `not` binds @@ -237,6 +316,85 @@ sqrt(2) = 1.414213562373095 `not (1 < 2)`. Arithmetic unary `-` and bitwise unary `~` bind more tightly. - f-strings interpolate: `f"step {i}: {report of x}"`. Escapes: `\n \t \r \\ \" \{ \}`. Each `{…}` converts with the builtin `str`, even if you rebind `str`. + +Empty `min`/`max` raises `value` in both call forms (R1.6b-D2); empty +`sum` remains int zero (C2). `sign_extend` reads exact int64 arguments and +raises `value` if its existing subtraction underflows (R1.6b-D1). + +Counts and indices are ints. `floor`, `ceil` and half-away-from-zero `round` +return an int when the result fits int64. `min`/`max` return the winning +argument with its kind; the first argument wins a tie. `num` requires a +complete numeric-literal string (N10). JSON integer text fitting int64 +decodes exactly (N14). + +The [C embedding API](EMBEDDING.md#value-handles) follows Lua’s numeric +accessors: `eigs_value_new_int` constructs an exact int64, `eigs_value_is_int` +tests the stored kind (`lua_isinteger`), and `eigs_value_as_int` reports +whether a number converts exactly to int64 (`lua_tointegerx`). +`eigs_value_as_num` converts either kind to double (`lua_tonumber`); +`EIGS_TYPE_NUM` covers both. + +LSP hover on a numeric literal reports `int` or `float` from the literal’s +kind: `3.0` remains a float even though its value is integral. + +```eigenscript +print of [type of 3, num_type of 3, num_type of 3.0] +print of [9007199254740993, 3.0, -0.0] +print of [num_type of (len of "abc"), num_type of (floor of -2.5)] +print of [num of "9007199254740993", num_type of (num of "3.0")] +print of [num_type of (min of [3, 3.0]), num_type of (min of [3.0, 3])] +print of (json_path of ["{\"n\":9223372036854775807}", "n"]) +try: + min of [] +catch e: + print of [e.kind, (index_of of [e.message, "min"]) >= 0] +try: + print of (num of "12abc") +catch e: + print of e.kind +``` +```output +["num", "int", "float"] +[9007199254740993, 3.0, -0.0] +["int", "int"] +[9007199254740993, "float"] +["int", "float"] +9223372036854775807 +["value", true] +value +``` + +`add`, `subtract` and `multiply` preserve exact ints for scalar operands and +for each pair of list elements, with `value` on int64 overflow. `negative` +preserves int scalar and list elements and raises at INT64_MIN. Buffer +arithmetic remains float. A buffer kernel stops at its first raised result, +releases its partial output and does not read later elements (#1417). +An all-int `sum` returns an exact int, while a +float anywhere in a nested input selects float addition for the entire +reduction. A buffer selects float addition too. An empty list's sum is int +zero, in both call forms. `sum` refuses non-finite buffer elements with a +`value` error in every strict mode (N13). In strict mode, it stops at the first NaN with a "not a number" diagnostic and sets `math_flags.invalid`; later elements are not read (#1417). + +```eigenscript +exact is sum of [9007199254740993, 1] +print of [exact, num_type of exact] +mixed is sum of [9223372036854775807, 1, 0.5] +print of [num_type of mixed, mixed == 9223372036854775808.0] +print of [num_type of (sum of []), num_type of (sum of ([]))] +print of (add of [[9007199254740993, 2], [1, 3]]) +try: + negative of (-9223372036854775807 - 1) +catch e: + print of e.kind +``` +```output +[9007199254740994, "int"] +["float", true] +["int", "int"] +[9007199254740994, 5] +value +``` + ## Numeric vectors: `buffer`, not `list` - `buffer of n` (and `zeros of n`, which returns a **buffer**) is the flat @@ -295,7 +453,7 @@ print of (buf_get of [(recv of c), 0]) # the BUFFER copied ``` ```output [1, 2] -1 +1.0 ``` - The measured kind-by-kind table lives in docs/CONCURRENCY.md and is itself an @@ -342,8 +500,56 @@ match x: `vm_run_bytecode` raises a catchable `value` error naming a rejected chunk descriptor; a valid program may still return `null`. `sandbox_run` reports descriptor rejection in its structured `{ok: false, error: ...}` result. Its optional fourth limit, `max_work`, bounds cumulative bytecode instructions across function calls and callback re-entry (default 10,000,000), independently of loop and allocation limits. This meters VM work, not elapsed time: a blocking native callback must return before the sandbox can stop. +The positions and counts of `char_at`, `substr`, `get_at`, `set_at`, +`copy_into`, `fill` and the `list_*` mutation/slice builtins use exact conversion +(N5). So do handle fields, bytecode ABI/counts/bytes, sandbox limits and numeric +exit codes. Integral floats are accepted; fractions and values outside the +receiving C type raise `value` before indexing or effects. +`recv_timeout` rounds milliseconds half away from zero and raises beyond the +C long range, with no one-year cap. `usleep` rounds microseconds and raises +beyond the C int range. `record_history` returns its previous flag as an int; +nonzero numeric flags still enable history. `task_detach` and sandbox error lines return ints. +`read_bytes` produces int bytes; `write_bytes` requires exact bytes in 0..255 +and returns an int count. File caps and corpus/stream counts follow N5; an +integral stream count outside its cap still raises `limit`. Subprocess exit +statuses are ints, and timeout seconds round to representable poll milliseconds. + +```eigenscript +print of (substr of ["hello", 1.0, 2.0]) +try: + print of (substr of ["hello", 1.5, 2]) +catch e: + print of e.kind +print of (num_type of (record_history of 0.5)) +print of (usleep of 1.5) +try: + usleep of 1e300 +catch e: + print of e.kind +path is mktemp of null +written is write_bytes of [path, [65.0, 66.0]] +bytes is read_bytes of path +print of [num_type of written, num_type of bytes[0], str_from_bytes of bytes] +try: + write_bytes of [path, [65.5]] +catch e: + print of e.kind +remove_file of path +``` +```output +el +value +int +null +value +["int", "int", "AB"] +value +``` + Fixed- or optional-shape builtin argument lists reject surplus outer elements in strict mode with a catchable `type_mismatch` error naming the builtin and maximum width. The check precedes the call's mutation, I/O and tape effects. `EIGS_STRICT=0` keeps each builtin's legacy result, including existing null/error stand-ins. Scalar overloads, lists used as data and genuinely variadic arguments keep their documented meaning. +Runtime-published counts and indices are exact ints (S9-K1): `__loop_iterations__`, live and historical `when` assignment counts, aggregate `what` lengths, trajectory `window`, error `line`, and scheduler trace `seq` and task ids. Scheduler `tick` remains a continuous virtual time. + Sandbox execution does not update shared temporal history: assignment values, names, counts and observer snapshots stay outside that history even when recording is armed. Ordinary tape assignment records still emit. Host and trusted descriptor history recording resumes normally outside the sandbox; sandbox temporal reads remain refused. **Division by zero RAISES** — it is a catchable `value`-kind error, not a @@ -363,6 +569,52 @@ value division by zero ``` +## Stored records + +Store handles carry integer `_store_id` and `_store_gen` fields. Every store operation requires exact, in-range integer handle fields; integral floats convert, while fractional, non-finite or out-of-range numeric fields raise `value` before store access (R1.6f-D1). `store_count` returns an integer. + +`store_get`, `store_delete`, and `store_update` accept string keys or exact +int64 numeric keys. Integral floats convert; fractional or out-of-range floats +raise `value` before lookup or mutation. Numeric keys retain all int64 digits. +Stored decimal integer text within int64 decodes as an int; a decimal point or +exponent selects a float. Buffer payloads remain floats. Store records preserve +int64 digits and use shortest round-trip float text, including `.0` for +integral floats and the sign of `-0.0`, so `3.0` reads back as a float. + +See the [stored-record example](SPEC.md#stored-records). + +PostgreSQL integer columns (`smallint`, `integer`, `bigint`, `oid`) return +exact int64 values through `db_query_value`; `db_query_json` retains their +exact decimal digits (N14, Q-DB253). A `bigint` beyond 2^53 is an int, +including both int64 boundaries. `numeric` remains text, and finite real +columns returned directly remain floats. Float parameters use N6 shortest round-trip text, preserving `.0` and +signed zero. Store records use the same float writer and exact int64 digits +(N14, #1762). The executable DB examples and boundary assertions +in `tests/test_db.eigs` run on the server-db profile with PostgreSQL. + +TCP handles, ports and sent-byte counts are integers. Numeric socket handles, ports, receive counts and outgoing bytes require exact integers (integral floats convert); bytes must be in 0..255. `net_send` validates the entire payload before sending any byte. Poll timeouts are continuous milliseconds rounded to the C int range; an unrepresentable duration raises `value`. + +`http_early_bind` and `http_serve` require an exact integer port in 0..65535; integral floats convert and port zero requests an ephemeral port. Fractional or out-of-range ports raise `value` before binding. `shared_delete` and `shared_size` return integer counts. + +N6 prints each float with Python's shortest round-trip spelling: integral +floats retain `.0`, negative zero is `-0.0`, and scientific notation starts +below `1e-4` or at `1e16`. Ints keep their exact decimal digits. `print`, +`write`, `str`, f-strings and JSON share the number writer. The executed +examples are `tests/phase2/rows/N6.float-repr.eigs` and +`tests/phase2/rows/N6.str-fstring.eigs`. + +The HTTP shared counter `shared_incr` follows numeric `+`: two int operands +return an exact int and int64 overflow raises `value` without changing the +stored counter (A). A missing counter starts at int zero. A float operand +produces a float. The stored operand is its JSON-decoded value; N6 preserves an +integer-valued float as a float by retaining its `.0`. Executable shared-counter examples in +`tests/test_http.eigs` cover exact values, both overflow directions and the +next operation after a raise. + +`http_post` also preserves every digit of JSON integer header values within +int64 before sending them as text (N14, B6). The executed loopback example +is `tests/phase2/builtins/builtins/http_post.eigs`. + ## Before hand-rolling a helper Cooperative task IDs are opaque numeric handles. Do not perform arithmetic on @@ -454,6 +706,97 @@ See docs/TRACE.md and docs/EMBEDDING.md for binding and format compatibility. Replay refuses unreconstructable host effects: `mktemp` raises a catchable filesystem-boundary error before creating a file; ordinary `mktemp` still creates the file and returns its path for later `remove_file`. + +Tensor sizes and indices follow N5: an integral float is accepted and a +fraction raises `value`. `gather` keeps the selected list cell's int/float +kind; buffer elements and numeric tensor calculations are floats. `shape` +returns integer dimension counts. `mean` and `norm` refuse non-finite buffer +cells with `value` in both strict modes. + +```eigenscript +g is gather of [[[1, 2], [3, 4]], [1.0, 0]] +print of [g, num_type of g[0], num_type of (shape of g)[0]] +try: + gather of [[10, 20], 0.5] +catch e: + print of e.kind +``` +```output +[[2, 3], "int", "int"] +value +``` + +Buffer sizes, indices, offsets and counts follow N5. Byte inputs to +`str_from_bytes`, `f64_from_bytes`, `inflate` and `buf_from_pcm16le` must be +exact integers in 0..255; fractions and out-of-range bytes raise `value`. +`buf_len`, `f64_to_bytes` and `inflate` return integer counts or bytes; +buffer storage and scalar buffer reads are floats. `buf_get`, `buf_peak`, +`buf_dot` and the byte/PCM codecs refuse non-finite input cells with `value`. + +Model token ids and generation limits follow N5 and the C int range; +fractional ids or token counts raise `value`. `eigen_generate` returns int +token ids, while `eigen_eval_loss` returns a float. Temperature, top_p and +training learning rates remain float quantities. Model JSON weights stay +float data; the loader status strings are unchanged. + +Audio playback (`audio_play`, `audio_play_loop`, `audio_stream_push`) refuses +stored non-finite buffer samples at the scalar boundary in both strict modes +(N13). Strict NaN preserves the first diagnostic and INVALID flag. Finite +samples clamp to [-1,1], then round PCM16 half away from zero (Q7/Q4). +Channel indices and loop counts follow N5; continuous mixer gain stays +fractional after clamping to [0,4]. + +Q7 magnitude validation also applies before SDL is loaded: + +```eigenscript +try: + gfx_delay of 1e300 +catch e: + print of e.kind +``` +```output +value +``` + +### Stored buffer data boundary + +Buffer kernels can retain non-finite data. Scalar indexing and iteration +raise `value` when that data is read, in every strict mode (N13). + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +try: + print of p[0] +catch e: + print of e.kind +``` +```output +value +``` + +### Buffer equality + +Buffers compare their stored doubles with IEEE equality: NaN differs from +everything, even itself; infinities compare by sign; signed zeros are equal. +Equality never raises or changes math flags, including under strict mode. + +```eigenscript +p is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [p, g, 1e308] +print of (p == p) +print of (p == g) +sgd_update of [p, p, 1.0] +print of (p == p) +``` +```output +true +false +false +``` + Recorded builtins validate deterministic argument refusals before their tape boundary. A builtin call that raises during recording must raise with the same kind and message during strict replay, never return a recorded stand-in. diff --git a/lib/format.eigs b/lib/format.eigs index 5f388bb02..33d56e512 100644 --- a/lib/format.eigs +++ b/lib/format.eigs @@ -18,13 +18,18 @@ define fmt_num(value, decimals) as: return "-" + (fmt_num of [0 - value, decimals]) if decimals == 0: return str of (round of value) - factor is 1 + # A float scale: value * 10^decimals is real-valued, and an int factor + # overflows int64 at 19 decimals (#1848). + factor is 1.0 for i in range of decimals: factor is factor * 10 rounded is round of (value * factor) int_part is floor of (rounded / factor) frac_part is rounded - (int_part * factor) frac_str is str of frac_part + # N6: fixed decimals need digits, without integral float text suffix. + if ends_with of [frac_str, ".0"]: + frac_str is substr of [frac_str, 0, (len of frac_str) - 2] loop while (len of frac_str) < decimals: frac_str is "0" + frac_str return (str of int_part) + "." + frac_str diff --git a/lib/physics.eigs b/lib/physics.eigs index ac91dcca7..021b55b82 100644 --- a/lib/physics.eigs +++ b/lib/physics.eigs @@ -16,7 +16,7 @@ # Fundamental Constants # ============================================================ -SPEED_OF_LIGHT is 299792458 # m/s (exact) +SPEED_OF_LIGHT is 299792458.0 # m/s (exact; a float, so m·c² with an int m stays real, #1848) PLANCK is 6.62607015e-34 # J·s (exact) HBAR is 1.054571817e-34 # J·s (reduced Planck, h/2π) BOLTZMANN is 1.380649e-23 # J/K (exact) diff --git a/lib/probability.eigs b/lib/probability.eigs index 498b33733..1baf4f13e 100644 --- a/lib/probability.eigs +++ b/lib/probability.eigs @@ -71,22 +71,38 @@ define binomial_cdf(n, k, p) as: s is s + binomial_pmf of [n, i, p] return s +# _factorial_real: n! as a FLOAT, for real-valued formulas (#1848). Same domain +# sentinel as factorial (0 for a negative n); its product is a float, so an int +# n past 20 does not raise as `factorial of 21` does (decision (A), #1758). +define _factorial_real(n) as: + if n < 0: + return 0.0 + result is 1.0 + i is 2 + loop while i <= n: + result is result * i + i is i + 1 + return result + # poisson_pmf: P(X=k) = lambda^k * e^(-lambda) / k! +# A probability is real-valued: k! is a float denominator (#1848). define poisson_pmf(k, lam) as: # Preserve the direct calculation (and its exact rounding) whenever all # three intermediates (lambda^k, e^-lambda, k!) are normal doubles. # Otherwise compute log(k!) alongside the exponent, so an intermediate # that overflows or underflows cannot destroy the result. if lam <= 0: - return (pow of [lam, k]) * (exp of (0 - lam)) / (factorial of k) + return (pow of [lam, k]) * (exp of (0 - lam)) / (_factorial_real of k) power_log is k * (log of lam) if k < 171 and lam <= 708.3964185322641 and power_log >= -708.3964185322641 and power_log <= 709.782712893384: - return (pow of [lam, k]) * (exp of (0 - lam)) / (factorial of k) + return (pow of [lam, k]) * (exp of (0 - lam)) / (_factorial_real of k) if k > 170: # Stirling's series avoids accumulating hundreds of rounded logs. - k2 is k * k + # Float factors keep an int k's products in floats, as on main: in + # ints, 1260 * k * k2 * k2 would raise on overflow from k = 1490 (#1758). + k2 is 1.0 * k * k log_factorial is (k + 0.5) * (log of k) - k + 0.9189385332046727 - log_factorial is log_factorial + 1 / (12 * k) - 1 / (360 * k * k2) + 1 / (1260 * k * k2 * k2) + log_factorial is log_factorial + 1 / (12.0 * k) - 1 / (360.0 * k * k2) + 1 / (1260.0 * k * k2 * k2) else: log_factorial is 0 i is 2 diff --git a/lib/validate.eigs b/lib/validate.eigs index aa4451257..1148cdab0 100644 --- a/lib/validate.eigs +++ b/lib/validate.eigs @@ -130,7 +130,6 @@ define is_alpha(value) as: lc is str_lower of value for i in range of (len of lc): c is char_at of [lc, i] - code is num of c # a-z check via lowercase comparison if (not (contains of ["abcdefghijklmnopqrstuvwxyz", c])): return false diff --git a/spec/lean/EigenCore/OracleTests.lean b/spec/lean/EigenCore/OracleTests.lean index 7b2483394..3533b8f90 100644 --- a/spec/lean/EigenCore/OracleTests.lean +++ b/spec/lean/EigenCore/OracleTests.lean @@ -18,7 +18,7 @@ example : recordTwo.stdout == "2\n" := by native_decide example : replayOne == recordOne := by native_decide private def replayAcrossSession : RunResult := run 100 counterexample (.replayStrict - ["B 0 1 1 0 root -", "V 8 0.47.0", "B 0 1 1 0 root -", "N 0 random_int=1"]) + ["B 0 1 1 0 root -", "V 9 0.47.0", "B 0 1 1 0 root -", "N 0 random_int=1"]) example : replayAcrossSession.error == some (.runtime "random_int: replay stream 0 has no matching recorded N value") && diff --git a/spec/lean/EigenCore/Tape.lean b/spec/lean/EigenCore/Tape.lean index bff245e7c..5561e361e 100644 --- a/spec/lean/EigenCore/Tape.lean +++ b/spec/lean/EigenCore/Tape.lean @@ -23,7 +23,7 @@ private def serializeRecord (record : String) : List String := def tapeSerialize (records : List String) : String := let physical := records.flatMap serializeRecord - "V 8 0.47.0\nB 0 1 1 0 root -\n" ++ String.intercalate "\n" physical ++ + "V 9 0.47.0\nB 0 1 1 0 root -\n" ++ String.intercalate "\n" physical ++ (if physical.isEmpty then "" else "\n") private def gatherContinuations (count : Nat) : Nat → String → List String → @@ -61,7 +61,7 @@ private partial def assembleRecords : List String → Except String (List String def tapeParse (text : String) : Except String (List String) := match text.splitOn "\n" with - | header::rest => if header == "V 8 0.47.0" then + | header::rest => if header == "V 9 0.47.0" then assembleRecords (rest.filter (· != "")) else throw "invalid tape header" | _ => throw "empty tape" diff --git a/src/builtins.c b/src/builtins.c index 61bef9476..325aca8d4 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -39,6 +39,14 @@ static Value *builtin_dict_view(Value *dict, const char *key, EigsView *view) { static void wake_channels_for_exit(EigsState *st); +/* N5 followed by the range of the C API receiving the argument. Borrowed + * input; callers stop on the error latch before indexing or doing work. */ +static int builtin_c_int(const Value *v, const char *who) { + int64_t n = 0; + eigs_int_arg_range(v, INT_MIN, INT_MAX, &n, who); + return (int)n; +} + /* #744: the extension ENTRY POINTS, not the extensions' private headers. * This TU calls five registrars and uses no extension type; pulling * ext_db_internal.h for one declaration dragged into the core @@ -161,8 +169,9 @@ Value* builtin_flush(Value *arg) { /* usleep of microseconds — pause execution */ Value* builtin_usleep(Value *arg) { BOOL_REFUSE(arg, "usleep"); - if (!arg || arg->type != VAL_NUM) return make_null(); - int us = (int)eigs_num_arg(arg, __func__); + if (!arg || !val_is_number(arg)) return make_null(); + int64_t us; + if (!eigs_magnitude_arg(arg, INT_MIN, INT_MAX, &us, "usleep")) return make_null(); if (us > 0) { struct timespec deadline; clock_gettime(CLOCK_REALTIME, &deadline); @@ -330,26 +339,12 @@ Value* builtin_text_builder_to_string(Value *arg) { return make_str_owned(xstrdup(arg->data.text_builder.data ? arg->data.text_builder.data : "")); } -/* ==== Bitwise operations ==== - * Semantics: operate on 32-bit two's-complement ints. Shift amounts are - * masked to [0,31] so large/negative shifts are defined behavior, not UB. - * Non-numeric args raise a runtime error (consistent with the strict error - * model used by the arithmetic operators and the '& | ^ << >> ~' operators). */ - -/* The bit_* builtins are the SAME operation as the infix operators - * (& | ^ << >>): int64 two's-complement over the numeric value (vm.c's - * INT_BINOP). They used to be a second, divergent implementation — - * (uint32_t)(int32_t) conversion, UB for inputs >= 2^31 and - * sign-extended results — so `0xEDB88320 & x` worked while - * `bit_and of [0xEDB88320, x]` returned garbage (found by the CRC-32 - * stdlib module, the first consumer needing the full unsigned-32 - * range). Shift counts are masked to 0..63 like the hardware would. */ -/* #1740: an operand that is not an exact int64 raises `value` (Lua 5.4's - * rule, the same as the infix operators). Returns 0 after raising. */ -static int bit_int(double x, int64_t *out, const char *name) { - if (num_to_i64_exact(x, out)) return 1; - rt_error(EK_VALUE, 0, EIGS_NO_INT_REP_FMT, name, x); - return 0; +/* #1758 N8: the bit_* builtins share Lua's exact int64 bit patterns and + * shift-count rules with the operators. No int operand crosses double. */ +/* #1740: an operand without an exact int64 representation raises value. */ +static int bit_int(Value *v, int64_t *out, const char *name) { + *out = eigs_int_arg(v, name); + return !g_has_error; } /* 1 = both operands converted; 0 = wrong shape (caller raises `type`); @@ -358,9 +353,9 @@ static int bit_pair(Value *arg, int64_t *a_out, int64_t *b_out, const char *name if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return 0; Value *va = BUILTIN_LIST_VIEW(arg, 0); Value *vb = BUILTIN_LIST_VIEW(arg, 1); - if (!va || va->type != VAL_NUM || !vb || vb->type != VAL_NUM) return 0; - if (!bit_int(eigs_num_arg(va, __func__), a_out, name) || - !bit_int(eigs_num_arg(vb, __func__), b_out, name)) return -1; + if (!va || !val_is_number(va) || !vb || !val_is_number(vb)) return 0; + if (!bit_int(va, a_out, name) || + !bit_int(vb, b_out, name)) return -1; return 1; } @@ -369,7 +364,7 @@ Value* builtin_bit_and(Value *arg) { int64_t a, b; int ok = bit_pair(arg, &a, &b, "bit_and"); if (ok <= 0) { if (ok == 0) rt_error(EK_TYPE, 0, "bit_and expects [number, number]"); return make_null(); } - return make_num((double)(a & b)); + return make_int(a & b); } Value* builtin_bit_or(Value *arg) { @@ -377,7 +372,7 @@ Value* builtin_bit_or(Value *arg) { int64_t a, b; int ok = bit_pair(arg, &a, &b, "bit_or"); if (ok <= 0) { if (ok == 0) rt_error(EK_TYPE, 0, "bit_or expects [number, number]"); return make_null(); } - return make_num((double)(a | b)); + return make_int(a | b); } Value* builtin_bit_xor(Value *arg) { @@ -385,14 +380,14 @@ Value* builtin_bit_xor(Value *arg) { int64_t a, b; int ok = bit_pair(arg, &a, &b, "bit_xor"); if (ok <= 0) { if (ok == 0) rt_error(EK_TYPE, 0, "bit_xor expects [number, number]"); return make_null(); } - return make_num((double)(a ^ b)); + return make_int(a ^ b); } Value* builtin_bit_not(Value *arg) { - if (!arg || arg->type != VAL_NUM) { rt_error(EK_TYPE, 0, "bit_not expects a number"); return make_null(); } + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "bit_not expects a number"); return make_null(); } int64_t a; - if (!bit_int(eigs_num_arg(arg, __func__), &a, "bit_not")) return make_null(); - return make_num((double)(~a)); + if (!bit_int(arg, &a, "bit_not")) return make_null(); + return make_int(~a); } Value* builtin_bit_shift_left(Value *arg) { @@ -400,7 +395,7 @@ Value* builtin_bit_shift_left(Value *arg) { int64_t a, b; int ok = bit_pair(arg, &a, &b, "bit_shl"); if (ok <= 0) { if (ok == 0) rt_error(EK_TYPE, 0, "bit_shl expects [number, number]"); return make_null(); } - return make_num((double)(int64_t)((uint64_t)a << (b & 63))); /* unsigned: no UB on negative a */ + return make_int(eigs_bit_shift(a, b, 0)); } Value* builtin_bit_shift_right(Value *arg) { @@ -408,10 +403,7 @@ Value* builtin_bit_shift_right(Value *arg) { int64_t a, b; int ok = bit_pair(arg, &a, &b, "bit_shr"); if (ok <= 0) { if (ok == 0) rt_error(EK_TYPE, 0, "bit_shr expects [number, number]"); return make_null(); } - /* Arithmetic shift, like the infix >> on int64 (negative operands - * keep their sign); mask a non-negative operand first for a - * logical u32 shift. */ - return make_num((double)(a >> (b & 63))); + return make_int(eigs_bit_shift(a, b, 1)); } /* monotonic_ns of null — nanoseconds from CLOCK_MONOTONIC */ @@ -419,7 +411,13 @@ Value* builtin_monotonic_ns(Value *arg) { (void)arg; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - TRACE_NONDET_RET("monotonic_ns", make_num((double)ts.tv_sec * 1e9 + (double)ts.tv_nsec)); + int64_t seconds = (int64_t)ts.tv_sec, nanos; + if (__builtin_mul_overflow(seconds, INT64_C(1000000000), &nanos) || + __builtin_add_overflow(nanos, (int64_t)ts.tv_nsec, &nanos)) { + rt_error(EK_VALUE, 0, "monotonic_ns: int64 overflow"); + return make_null(); + } + TRACE_NONDET_RET("monotonic_ns", make_int(nanos)); } /* monotonic_ms of null — milliseconds from CLOCK_MONOTONIC */ @@ -427,7 +425,7 @@ Value* builtin_monotonic_ms(Value *arg) { (void)arg; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - TRACE_NONDET_RET("monotonic_ms", make_num((double)ts.tv_sec * 1e3 + (double)ts.tv_nsec / 1e6)); + TRACE_NONDET_RET("monotonic_ms", make_float((double)ts.tv_sec * 1e3 + (double)ts.tv_nsec / 1e6)); } /* clock_unix of null — seconds since the Unix epoch from CLOCK_REALTIME @@ -438,22 +436,22 @@ Value* builtin_clock_unix(Value *arg) { (void)arg; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - TRACE_NONDET_RET("clock_unix", make_num((double)ts.tv_sec + (double)ts.tv_nsec / 1e9)); + TRACE_NONDET_RET("clock_unix", make_float((double)ts.tv_sec + (double)ts.tv_nsec / 1e9)); } Value* builtin_len(Value *arg) { if (arg->type == VAL_LIST) - return make_num(arg->data.list.count); + return make_int(arg->data.list.count); if (arg->type == VAL_STR) - return make_num(val_str_len(arg)); + return make_int(val_str_len(arg)); if (arg->type == VAL_DICT) { eigs_module_ns_sync(arg); /* #1057 whole-dict reader */ - return make_num(arg->data.dict.count); + return make_int(arg->data.dict.count); } if (arg->type == VAL_BUFFER) - return make_num(arg->data.buffer.count); + return make_int(arg->data.buffer.count); if (arg->type == VAL_TEXT_BUILDER) - return make_num((double)arg->data.text_builder.len); + return make_int(arg->data.text_builder.len); /* #508: a type with no length (number, fn, null, …) used to fold to 0, * silently masking a wrong value. Raise. Callers that legitimately mean * "empty if absent" already guard with `x != null and len of x` — and @@ -463,7 +461,7 @@ Value* builtin_len(Value *arg) { /* fs:CHANNEL the rt_error above has already raised UNCONDITIONALLY — this * is only the C return value the compiler requires. `len` is the reform's * finished state, not a candidate for it. */ - return make_num(0); + return make_int(0); } Value* builtin_str(Value *arg) { @@ -480,62 +478,94 @@ Value* builtin_str(Value *arg) { return v; } -/* Every `return make_num(0)` in this function is fs:ANSWER, and the reason is - * one reason: BUILTINS.md documents `num` as "Convert to number (parse string - * or COERCE)". A coercion's whole contract is that it accepts anything and - * answers, so 0 here is the documented result rather than a stand-in for a - * rejected argument — the opposite of `cos of "hello"`. - * - * This is the sharpest ANSWER case in the file and worth stating plainly: it - * IS reachable as a laundering path (`num of [1,2,3]` is 0, and a typo that - * hands `num` a list scores as a real zero). Making it loud is a change to - * `num`'s documented contract, not a completion of this reform, so it is out - * of scope here and belongs in its own issue if wanted. */ +/* N1/N10/N14: classify signed decimal integer text without a double. + * 1 = exact int64, 0 = not integer text, -1 = integer outside int64. + * Validate the entire token before range-checking (scanners skip 12abc). */ +static int builtin_decimal_int(const char *s, size_t len, int64_t *out) { + size_t first = 0; + int neg = len && s[0] == '-'; + if (len && (s[0] == '-' || s[0] == '+')) first++; + if (first == len) return 0; + for (size_t i = first; i < len; i++) + if (s[i] < '0' || s[i] > '9') return 0; + uint64_t limit = (uint64_t)INT64_MAX + (unsigned)neg, n = 0; + for (size_t i = first; i < len; i++) { + unsigned d = (unsigned)(s[i] - '0'); + if (n > (limit - d) / 10) return -1; + n = n * 10 + d; + } + *out = neg && n ? -1 - (int64_t)(n - 1) : (int64_t)n; + return 1; +} + +static Value *builtin_number_value(EigsNum n) { + return n.is_int ? make_int(n.value.i64) : make_float(n.value.f64); +} + +static int builtin_ascii_space(unsigned char c) { + return c == ' ' || (c >= '\t' && c <= '\r'); +} + +/* N10 (#1758 comment 6304880619): complete lexer-grammar text, with a + * surrounding ASCII space/sign allowance, as Lua tonumber. Hex floats are + * the declared divergence. No partial, empty, inf/nan or separator input. */ Value* builtin_num(Value *arg) { - if (!arg) return make_num(0); /* fs:ANSWER coercion contract, see above */ - if (arg->type == VAL_NUM) return arg; - /* #1637: a bool is not a number, and the coercion contract's 0 for a - * non-string would make `num of true` answer 0 -- a silent wrong value. - * Whether `num of b` should convert is open (#1637 leaves it undecided), - * so it raises in every mode rather than pick an answer. */ + if (!arg) return make_float(0); + if (val_is_number(arg)) return arg; if (arg->type == VAL_BOOL) { rt_error(EK_TYPE, 0, "num: cannot convert a bool to a number (use `if b:` to choose one)"); - return make_num(0); - } - if (arg->type == VAL_STR) { - /* Hex strings are converted HERE, never by strtod — same contract - * as the lexer (#378/#381): glibc's strtod reads hex floats - * ("0x.8" -> 0.5, "0x1p4" -> 16) while the freestanding - * mini_strtod reads 0, a silent profile divergence through the - * string path. Hex is integer-only; conversion stops at the - * first non-hex-digit (matching strtod's partial-parse shape, - * "12abc" -> 12); a prefix with no hex digit converts to 0 - * like any other non-numeric string. */ - const char *p = arg->data.str; - while (*p == ' ' || *p == '\t') p++; - int neg = 0; - if (*p == '+' || *p == '-') { neg = (*p == '-'); p++; } - if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - /* fs:ANSWER "0x" with no hex digit converts to 0 like any other - * non-numeric string — the partial-parse contract this comment - * block documents. */ - if (!isxdigit((unsigned char)p[2])) return make_num(0); - double v = 0; - p += 2; - while (isxdigit((unsigned char)*p)) { - int d = *p <= '9' ? *p - '0' : (*p | 32) - 'a' + 10; - v = v * 16 + d; - p++; - } - return make_num(neg ? -v : v); + return make_null(); + } + if (arg->type != VAL_STR) return make_float(0); /* existing non-text coercion */ + const char *start = arg->data.str, *end = start + val_str_len(arg); + while (start < end && builtin_ascii_space((unsigned char)*start)) start++; + while (end > start && builtin_ascii_space((unsigned char)end[-1])) end--; + const char *p = start; + int neg = p < end && *p == '-'; + if (p < end && (*p == '+' || *p == '-')) p++; + if (end - p >= 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + uint64_t bits = 0; + p += 2; + const char *digits = p; + while (p < end && isxdigit((unsigned char)*p)) { + unsigned d = (unsigned)(*p <= '9' ? *p - '0' : (*p | 32) - 'a' + 10); + bits = bits * UINT64_C(16) + d; + p++; } - /* #971: strtod reads "nan"/"inf" — the in-language route to a NaN. - * EIGS_STRICT=0 collapses to 0 (+ EIGS_MATH_INVALID); strict (the - * default) raises, named. */ - return make_num(num_guard_named(strtod(arg->data.str, NULL), "num")); + if (p == digits || p != end) goto invalid; + if (neg) bits = UINT64_C(0) - bits; + int64_t n = bits <= INT64_MAX ? (int64_t)bits : -1 - (int64_t)(UINT64_MAX - bits); + return make_int(n); + } + int digits = 0; + while (p < end && *p >= '0' && *p <= '9') { p++; digits = 1; } + if (p < end && *p == '.') { + p++; + while (p < end && *p >= '0' && *p <= '9') { p++; digits = 1; } } - if (arg->type == VAL_NULL) return make_num(0); /* fs:ANSWER coercion contract */ - return make_num(0); /* fs:ANSWER coercion contract */ + if (!digits) goto invalid; + if (p < end && (*p == 'e' || *p == 'E')) { + p++; + if (p < end && (*p == '+' || *p == '-')) p++; + const char *exponent = p; + while (p < end && *p >= '0' && *p <= '9') p++; + if (p == exponent) goto invalid; + } + if (p != end) goto invalid; + int64_t n; + if (builtin_decimal_int(start, (size_t)(end - start), &n) == 1) return make_int(n); + /* Isolate the validated span; no locale whitespace or suffix can leak in. */ + size_t len = (size_t)(end - start); + char *text = xmalloc(len + 1); + memcpy(text, start, len); + text[len] = '\0'; + double d = strtod(text, NULL); + free(text); + d = num_guard_named(d, "num"); + return g_has_error ? make_null() : make_float(d); +invalid: + rt_error(EK_VALUE, 0, "num: expected complete numeric-literal text"); + return make_null(); } Value* builtin_append(Value *arg) { @@ -554,7 +584,7 @@ Value* builtin_append(Value *arg) { EigsSlot item = list_slot(arg, 1); if (!target || target->type != VAL_LIST) { rt_error(EK_TYPE, 0, "append: target must be a list (got %s)", - val_type_name(target ? target->type : VAL_NUM)); + val_type_name(target ? target->type : VAL_FLOAT)); return make_null(); } list_append_slot(target, item); @@ -578,9 +608,9 @@ Value* builtin_report(Value *arg) { Value* builtin_get_observer_thresholds(Value *arg) { (void)arg; Value *result = make_list(3); - list_append_owned(result, make_num(g_obs_dh_zero)); - list_append_owned(result, make_num(g_obs_dh_small)); - list_append_owned(result, make_num(g_obs_h_low)); + list_append_owned(result, make_float(g_obs_dh_zero)); + list_append_owned(result, make_float(g_obs_dh_small)); + list_append_owned(result, make_float(g_obs_h_low)); return result; } @@ -607,13 +637,14 @@ Value* builtin_get_observer_thresholds(Value *arg) { Value* builtin_exit(Value *arg) { STRICT_LIST_MAX(arg, 1, "exit"); int code = 0; - if (arg && arg->type == VAL_NUM) { - code = (int)eigs_num_arg(arg, __func__); + if (arg && val_is_number(arg)) { + code = builtin_c_int(arg, "exit"); } else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 1 && BUILTIN_LIST_VIEW(arg, 0) && - BUILTIN_LIST_VIEW(arg, 0)->type == VAL_NUM) { - code = (int)eigs_list_num(arg, 0, __func__); + val_is_number(BUILTIN_LIST_VIEW(arg, 0))) { + code = builtin_c_int(BUILTIN_LIST_VIEW(arg, 0), "exit"); } + if (g_has_error) return make_null(); g_exit_code = code; g_exit_requested = 1; /* this thread's unwind: uncatchable */ eigs_state_request_exit(eigs_current_state(), code); @@ -625,15 +656,12 @@ Value* builtin_exit(Value *arg) { /* set_exit_code of N — set the status used if the state completes gracefully. * Like Node.js process.exitCode, this does not stop execution. Last store wins. */ Value* builtin_set_exit_code(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_VALUE, 0, "set_exit_code requires an integer in 0..255"); return make_null(); } - double num = eigs_num_arg(arg, __func__); - if (num < 0 || num > 255 || num != (double)(int)num) { - rt_error(EK_VALUE, 0, "set_exit_code requires an integer in 0..255 (got %g)", num); - return make_null(); - } + int64_t num; + if (!eigs_int_arg_range(arg, 0, 255, &num, "set_exit_code")) return make_null(); eigs_state_set_graceful_exit_code(eigs_current_state(), (int)num); return make_null(); } @@ -768,9 +796,9 @@ Value* builtin_observe(Value *arg) { (void)arg; Value *list = make_list(4); list_append_owned(list, make_str("equilibrium")); - list_append_owned(list, make_num(0.0)); - list_append_owned(list, make_num(0.0)); - list_append_owned(list, make_num(0.0)); + list_append_owned(list, make_float(0.0)); + list_append_owned(list, make_float(0.0)); + list_append_owned(list, make_float(0.0)); return list; } @@ -852,7 +880,8 @@ Value* builtin_math_flags(Value *arg) { Value* builtin_type(Value *arg) { if (!arg) return make_str("none"); switch (arg->type) { - case VAL_NUM: return make_str("num"); + case VAL_FLOAT: return make_str("num"); + case VAL_INT: return make_str("num"); /* #1758 (B): one number type at the surface */ case VAL_STR: return make_str("str"); case VAL_LIST: return make_str("list"); case VAL_FN: return make_str("fn"); @@ -867,6 +896,14 @@ Value* builtin_type(Value *arg) { return make_str("none"); } +/* #1758 N7: inspect numeric representation without splitting surface type + * "num". Every other value, including bool, answers null. */ +Value* builtin_num_type(Value *arg) { + if (arg && arg->type == VAL_INT) return make_str("int"); + if (arg && arg->type == VAL_FLOAT) return make_str("float"); + return make_null(); +} + /* Bound JSON nesting depth: each array/object descent is a C recursion, so * untrusted input like "[[[[...]]]]" would otherwise exhaust the C stack and * crash (SIGSEGV). 200 is far beyond any legitimate document. Shared by the @@ -899,14 +936,20 @@ static int eigs_json_encode_value(Value *v, strbuf *out, int depth) { if ((v->type == VAL_LIST || v->type == VAL_DICT) && depth >= JSON_ENCODE_MAX_DEPTH) return -1; switch (v->type) { - case VAL_NUM: { + case VAL_FLOAT: { /* #875: one shared rule with `str of` — exact integers to 2^53 * bare, otherwise the shortest round-tripping form. The local * %d fast path stopped at 2^31 and handed everything above it * to %.15g, so integer IDs between 2^31 and 2^53 came back as a * different number. */ char nb[32]; - eigs_num_text(nb, sizeof(nb), eigs_num_arg(v, __func__)); + eigs_num_text(nb, sizeof(nb), eigs_float_arg(v, __func__)); + strbuf_append(out, nb); + break; + } + case VAL_INT: { /* #1758 R1.2a: an int encodes as its exact digits */ + char nb[24]; + eigs_int_text(nb, sizeof(nb), VAL_I64_RAW(v)); strbuf_append(out, nb); break; } @@ -978,7 +1021,14 @@ static int eigs_json_encode_value(Value *v, strbuf *out, int depth) { strbuf_append_char(out, '}'); break; } - default: + /* #1758 R1.2a: the `default:` that answered "null" is gone, so a new + * ValType is a build error here (-Werror=switch) instead of a silent + * null. These are the types it covered, unchanged: null, fn and + * builtin (also caught above), and a buffer. */ + case VAL_NULL: + case VAL_FN: + case VAL_BUILTIN: + case VAL_BUFFER: strbuf_append(out, "null"); break; } @@ -1264,14 +1314,14 @@ static Value* eigs_json_parse_number(const char *s, int *pos) { if (s[p] == '-') p++; if (!isdigit((unsigned char)s[p])) { /* fs:CHANNEL failure is signalled out-of-band via g_json_parse_err; the 0 is a placeholder the caller never reads */ - g_json_parse_err = 1; *pos = p; return make_num(0); + g_json_parse_err = 1; *pos = p; return make_float(0); } while (isdigit((unsigned char)s[p])) p++; if (s[p] == '.') { p++; if (!isdigit((unsigned char)s[p])) { /* "1." — frac needs digits */ /* fs:CHANNEL failure is signalled out-of-band via g_json_parse_err; the 0 is a placeholder the caller never reads */ - g_json_parse_err = 1; *pos = p; return make_num(0); + g_json_parse_err = 1; *pos = p; return make_float(0); } while (isdigit((unsigned char)s[p])) p++; } @@ -1280,10 +1330,15 @@ static Value* eigs_json_parse_number(const char *s, int *pos) { if (s[p] == '+' || s[p] == '-') p++; if (!isdigit((unsigned char)s[p])) { /* "1e", "1e+" — exp needs digits */ /* fs:CHANNEL failure is signalled out-of-band via g_json_parse_err; the 0 is a placeholder the caller never reads */ - g_json_parse_err = 1; *pos = p; return make_num(0); + g_json_parse_err = 1; *pos = p; return make_float(0); } while (isdigit((unsigned char)s[p])) p++; } + int64_t integer; + if (builtin_decimal_int(s + start, (size_t)(p - start), &integer) == 1) { + *pos = p; + return make_int(integer); + } char *endp; double d = strtod(s + start, &endp); if (endp != s + p) { @@ -1299,7 +1354,7 @@ static Value* eigs_json_parse_number(const char *s, int *pos) { free(iso); } *pos = p; - return make_num(d); + return make_float(d); } /* JSON_MAX_DEPTH is defined above the encoder, which shares it (#730). @@ -1470,9 +1525,10 @@ Value* builtin_json_build(Value *arg) { free(key); strbuf_append_n(&out, ": ", 2); Value *val = BUILTIN_LIST_VIEW(arg, i + 1); - if (val->type == VAL_NUM) { + if (val_is_number(val)) { char nb[32]; /* #875: the shared rule */ - eigs_num_text(nb, sizeof(nb), eigs_num_arg(val, __func__)); + if (val->type == VAL_INT) eigs_int_text(nb, sizeof(nb), VAL_I64_RAW(val)); + else eigs_num_text(nb, sizeof(nb), eigs_float_arg(val, "json_build")); strbuf_append(&out, nb); } else if (val->type == VAL_NULL) { strbuf_append(&out, "null"); @@ -1634,59 +1690,20 @@ Value* builtin_scan_ints(Value *arg) { line_leading = 0; const char *start = p; - int neg = 0; - if (*p == '-' || *p == '+') { - neg = (*p == '-'); - p++; - } - - int digits = 0; - double value = 0.0; - while (*p && isdigit((unsigned char)*p)) { - value = value * 10.0 + (double)(*p - '0'); - p++; - digits++; - } - - int valid = digits > 0; - while (*p && !isspace((unsigned char)*p)) { - valid = 0; - p++; - } - - if (valid) { - if (neg) value = -value; - list_append_owned(out, make_num(value)); - } else if (p == start) { - p++; + while (*p && !isspace((unsigned char)*p)) p++; + int64_t value = 0; + int valid = builtin_decimal_int(start, (size_t)(p - start), &value); + if (valid < 0) { + rt_error(EK_VALUE, 0, "scan_ints: integer token '%.*s' is outside int64", (int)(p - start), start); + val_decref(out); + return make_null(); } + if (valid) list_append_owned(out, make_int(value)); } return out; } -static int scan_integer_token_value(const char *start, size_t len, double *out_value) { - if (!start || len == 0) return 0; - - size_t i = 0; - int neg = 0; - if (start[i] == '-' || start[i] == '+') { - neg = (start[i] == '-'); - i++; - if (i >= len) return 0; - } - - double value = 0.0; - for (; i < len; i++) { - unsigned char ch = (unsigned char)start[i]; - if (!isdigit(ch)) return 0; - value = value * 10.0 + (double)(ch - '0'); - } - - if (neg) value = -value; - if (out_value) *out_value = value; - return 1; -} /* scan_int_tokens of text * scan_int_tokens of [text, comment_marker] @@ -1765,17 +1782,23 @@ Value* builtin_scan_int_tokens(Value *arg) { memcpy(token, start, len); token[len] = '\0'; - double int_value = 0.0; - int is_int = scan_integer_token_value(start, len, &int_value); + int64_t int_value = 0; + int is_int = builtin_decimal_int(start, len, &int_value); + if (is_int < 0) { + rt_error(EK_VALUE, 0, "scan_int_tokens: integer token '%s' is outside int64", token); + free(token); + val_decref(out); + return make_null(); + } Value *row = make_list(7); list_append_owned(row, make_str(token)); - list_append_owned(row, make_num((double)token_line)); - list_append_owned(row, make_num((double)token_col)); - list_append_owned(row, make_num((double)(start - base))); - list_append_owned(row, make_num((double)(p - base))); - list_append_owned(row, make_num((double)is_int)); - list_append_owned(row, make_num(int_value)); + list_append_owned(row, make_int(token_line)); + list_append_owned(row, make_int(token_col)); + list_append_owned(row, make_int((start - base))); + list_append_owned(row, make_int((p - base))); + list_append_owned(row, make_int(is_int)); + list_append_owned(row, make_int(int_value)); list_append_owned(out, row); /* adopt the freshly-built row */ free(token); } @@ -1878,9 +1901,10 @@ Value* builtin_char_at(Value *arg) { ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "char_at", "[string, index]", make_str("")); Value *str_val = BUILTIN_LIST_VIEW(arg, 0); Value *idx_val = BUILTIN_LIST_VIEW(arg, 1); - ARG_GUARD(!str_val || str_val->type != VAL_STR || !idx_val || idx_val->type != VAL_NUM, + ARG_GUARD(!str_val || str_val->type != VAL_STR || !idx_val || !val_is_number(idx_val), "char_at", "[string, number]", make_str("")); - int idx = (int)eigs_num_arg(idx_val, __func__); + int idx = builtin_c_int(idx_val, "char_at"); + if (g_has_error) return make_null(); int len = val_str_len(str_val); if (idx < 0) idx += len; /* fs:ANSWER an index outside the string has no character; "" is the @@ -1914,11 +1938,12 @@ Value* builtin_substr(Value *arg) { Value *start_val = BUILTIN_LIST_VIEW(arg, 1); Value *len_val = BUILTIN_LIST_VIEW(arg, 2); ARG_GUARD(!str_val || str_val->type != VAL_STR, "substr", "a string as its first argument", make_str("")); - ARG_GUARD(!start_val || start_val->type != VAL_NUM, "substr", "a number as its start", make_str("")); - ARG_GUARD(!len_val || len_val->type != VAL_NUM, "substr", "a number as its length", make_str("")); + ARG_GUARD(!start_val || !val_is_number(start_val), "substr", "a number as its start", make_str("")); + ARG_GUARD(!len_val || !val_is_number(len_val), "substr", "a number as its length", make_str("")); int slen = val_str_len(str_val); - int start = (int)eigs_num_arg(start_val, __func__); - int rlen = (int)eigs_num_arg(len_val, __func__); + int start = builtin_c_int(start_val, "substr"); + int rlen = builtin_c_int(len_val, "substr"); + if (g_has_error) return make_null(); /* #504: a negative start counts from the end, matching char_at and the * [] operator (was a flat clamp-to-0 — inconsistent). A start still * negative after the adjustment (before the string) clamps to 0. */ @@ -1948,20 +1973,20 @@ Value* builtin_substr(Value *arg) { Value* builtin_index_of(Value *arg) { STRICT_LIST_MAX(arg, 2, "index_of"); ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, - "index_of", "[haystack, needle]", make_num(-1)); + "index_of", "[haystack, needle]", make_int(-1)); Value *h = BUILTIN_LIST_VIEW(arg, 0), *n = BUILTIN_LIST_VIEW(arg, 1); /* #1008: the -1 for non-string operands is a deliberate choice (#316 — * folding to "" gave a false positive at index 0), so ARG_GUARD is * exactly the right shape: the answer is preserved with the flag off and * the mistake becomes loud with it on. */ ARG_GUARD(!h || h->type != VAL_STR || !n || n->type != VAL_STR, - "index_of", "two strings", make_num(-1)); + "index_of", "two strings", make_int(-1)); const char *p = strstr(h->data.str, n->data.str); /* fs:ANSWER the needle is genuinely not in the haystack — -1 is * index_of's documented miss, and the one -1 here that is a RESULT * rather than a rejected argument. */ - if (!p) return make_num(-1); - return make_num((double)(p - h->data.str)); + if (!p) return make_int(-1); + return make_int((p - h->data.str)); } /* ================================================================ @@ -1969,105 +1994,151 @@ Value* builtin_index_of(Value *arg) { * ================================================================ */ Value* builtin_sin(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "sin", "a number", make_num(0)); - return make_num(sin(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "sin", "a number", make_float(0)); + return make_float(sin(eigs_float_arg(arg, __func__))); } Value* builtin_cos(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "cos", "a number", make_num(0)); - return make_num(cos(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "cos", "a number", make_float(0)); + return make_float(cos(eigs_float_arg(arg, __func__))); } Value* builtin_tan(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "tan", "a number", make_num(0)); - return make_num(tan(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "tan", "a number", make_float(0)); + return make_float(tan(eigs_float_arg(arg, __func__))); } Value* builtin_asin(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "asin", "a number", make_num(0)); - double x = eigs_num_arg(arg, __func__); + ARG_GUARD(!arg || !val_is_number(arg), "asin", "a number", make_float(0)); + double x = eigs_float_arg(arg, __func__); /* #865/#971: an out-of-domain argument is clamped (invalid bit records it), * unless strict math is on, in which case it raises. */ if (x < -1.0 || x > 1.0) { /* fs:STRICT already loud under strict (#977); the soft 0 is the flag-off path */ - if (g_strict) { rt_error(EK_VALUE, 0, "asin: argument out of domain [-1, 1]"); return make_num(0); } + if (g_strict) { rt_error(EK_VALUE, 0, "asin: argument out of domain [-1, 1]"); return make_float(0); } g_math_flags |= EIGS_MATH_INVALID; x = (x < -1.0) ? -1.0 : 1.0; } - return make_num(asin(x)); + return make_float(asin(x)); } Value* builtin_acos(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "acos", "a number", make_num(0)); - double x = eigs_num_arg(arg, __func__); + ARG_GUARD(!arg || !val_is_number(arg), "acos", "a number", make_float(0)); + double x = eigs_float_arg(arg, __func__); /* #865/#971: an out-of-domain argument is clamped (invalid bit records it), * unless strict math is on, in which case it raises. */ if (x < -1.0 || x > 1.0) { /* fs:STRICT already loud under strict (#977); the soft 0 is the flag-off path */ - if (g_strict) { rt_error(EK_VALUE, 0, "acos: argument out of domain [-1, 1]"); return make_num(0); } + if (g_strict) { rt_error(EK_VALUE, 0, "acos: argument out of domain [-1, 1]"); return make_float(0); } g_math_flags |= EIGS_MATH_INVALID; x = (x < -1.0) ? -1.0 : 1.0; } - return make_num(acos(x)); + return make_float(acos(x)); } Value* builtin_atan(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "atan", "a number", make_num(0)); - return make_num(atan(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "atan", "a number", make_float(0)); + return make_float(atan(eigs_float_arg(arg, __func__))); } Value* builtin_atan2(Value *arg) { STRICT_LIST_MAX(arg, 2, "atan2"); - ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "atan2", "[y, x]", make_num(0)); + ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "atan2", "[y, x]", make_float(0)); Value *y = BUILTIN_LIST_VIEW(arg, 0); Value *x = BUILTIN_LIST_VIEW(arg, 1); - ARG_GUARD(!y || y->type != VAL_NUM || !x || x->type != VAL_NUM, "atan2", "two numbers", make_num(0)); - return make_num(atan2(eigs_num_arg(y, __func__), eigs_num_arg(x, __func__))); + ARG_GUARD(!y || !val_is_number(y) || !x || !val_is_number(x), "atan2", "two numbers", make_float(0)); + return make_float(atan2(eigs_float_arg(y, __func__), eigs_float_arg(x, __func__))); } Value* builtin_floor(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "floor", "a number", make_num(0)); - return make_num(floor(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "floor", "a number", make_float(0)); + EigsNum n = eigs_number_arg(arg, "floor"); + if (g_has_error) return make_null(); + if (n.is_int) return make_int(n.value.i64); + double result = floor(n.value.f64); + int64_t integer; + return num_to_i64_exact(result, &integer) ? make_int(integer) : make_float(result); } Value* builtin_ceil(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "ceil", "a number", make_num(0)); - return make_num(ceil(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "ceil", "a number", make_float(0)); + EigsNum n = eigs_number_arg(arg, "ceil"); + if (g_has_error) return make_null(); + if (n.is_int) return make_int(n.value.i64); + double result = ceil(n.value.f64); + int64_t integer; + return num_to_i64_exact(result, &integer) ? make_int(integer) : make_float(result); } Value* builtin_round(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "round", "a number", make_num(0)); - return make_num(round(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "round", "a number", make_float(0)); + EigsNum n = eigs_number_arg(arg, "round"); + if (g_has_error) return make_null(); + if (n.is_int) return make_int(n.value.i64); + double result = round(n.value.f64); + int64_t integer; + return num_to_i64_exact(result, &integer) ? make_int(integer) : make_float(result); } Value* builtin_abs(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "abs", "a number", make_num(0)); - return make_num(fabs(eigs_num_arg(arg, __func__))); + ARG_GUARD(!arg || !val_is_number(arg), "abs", "a number", make_float(0)); + EigsNum n = eigs_number_arg(arg, "abs"); + if (g_has_error) return make_null(); + if (n.is_int) { + if (n.value.i64 == INT64_MIN) { + rt_error(EK_VALUE, 0, "abs: int64 overflow"); + return make_null(); + } + return make_int(n.value.i64 < 0 ? -n.value.i64 : n.value.i64); + } + return make_float(fabs(n.value.f64)); } /* #317: min/max are N-ary reductions over a flat list of numbers — the old * code silently read only items[0..1] (`max of [1,2,3]` was 2) and returned 0 * for a 1-element list. A single bare number returns itself (mirrors `sum`); - * an empty list or any non-number element keeps the old 0 fallback rather - * than inventing a partial answer. */ + * empty input raises (R1.6b-D2); a non-number element retains its existing + * strict type error / non-strict zero fallback. */ static Value* minmax_reduce(Value *arg, int want_max) { - if (arg && arg->type == VAL_NUM) return make_num(eigs_num_arg(arg, __func__)); - /* Split from the empty-list case below: #317 chose the 0 fallback for BOTH - * a wrong type and an empty list in one condition, and only the first half - * is a type mistake. Splitting keeps the non-strict answer identical while - * letting strict speak about the half it should. */ - ARG_GUARD(!arg || arg->type != VAL_LIST, want_max ? "max" : "min", "a number or a list of numbers", make_num(0)); - /* fs:EMPTY no elements to reduce over; #317 chose 0 rather than inventing a - * partial answer, and an empty list is not a type mistake. */ - if (arg->data.list.count < 1) return make_num(0); - double best = 0.0; + const char *who = want_max ? "max" : "min"; + if (arg && val_is_number(arg)) { + EigsNum n = eigs_number_arg(arg, who); + return g_has_error ? make_null() : builtin_number_value(n); + } + /* Keep the public guard names literal for the source/probe census. */ + if (want_max) { + ARG_GUARD(!arg || arg->type != VAL_LIST, "max", "a number or a list of numbers", make_float(0)); + } else { + ARG_GUARD(!arg || arg->type != VAL_LIST, "min", "a number or a list of numbers", make_float(0)); + } + /* R1.6b-D2: no winner exists for an empty reduction, in either call + * form and either strict mode. C2's integer identity belongs to sum. */ + if (arg->data.list.count < 1) { + rt_error(EK_VALUE, 0, "%s: expected at least one number", who); + return make_null(); + } + EigsNum best = {0}; for (int i = 0; i < arg->data.list.count; i++) { Value *v = BUILTIN_LIST_VIEW(arg, i); - ARG_GUARD(!v || v->type != VAL_NUM, want_max ? "max" : "min", "every element to be a number", make_num(0)); - if (i == 0 || (want_max ? eigs_num_arg(v, __func__) > best : eigs_num_arg(v, __func__) < best)) - best = eigs_num_arg(v, __func__); + /* Keep the public guard names literal for the source/probe census. */ + if (want_max) { + ARG_GUARD(!v || !val_is_number(v), "max", "every element to be a number", make_float(0)); + } else { + ARG_GUARD(!v || !val_is_number(v), "min", "every element to be a number", make_float(0)); + } + EigsNum n = eigs_number_arg(v, who); + if (g_has_error) return make_null(); + int cmp = 0; + if (i) { + if (n.is_int && best.is_int) + cmp = (n.value.i64 > best.value.i64) - (n.value.i64 < best.value.i64); + else if (n.is_int) cmp = eigs_cmp_int_float(n.value.i64, best.value.f64); + else if (best.is_int) cmp = -eigs_cmp_int_float(best.value.i64, n.value.f64); + else cmp = (n.value.f64 > best.value.f64) - (n.value.f64 < best.value.f64); + } + if (!i || (want_max ? cmp > 0 : cmp < 0)) best = n; } - return make_num(best); + return builtin_number_value(best); } Value* builtin_min(Value *arg) { return minmax_reduce(arg, 0); } @@ -2076,7 +2147,7 @@ Value* builtin_max(Value *arg) { return minmax_reduce(arg, 1); } Value* builtin_pi(Value *arg) { (void)arg; - return make_num(3.14159265358979323846); + return make_float(3.14159265358979323846); } /* ================================================================ @@ -2132,7 +2203,7 @@ Value* builtin_random(Value *arg) { /* Seed is part of the live source so a replay take / fail-loud raise * does not race g_random_seeded (#1142 TSan on two workers under * EIGS_REPLAY). */ - TRACE_NONDET_RET("random", make_num(eigs_random_double())); + TRACE_NONDET_RET("random", make_float(eigs_random_double())); } /* random_int of [lo, hi] → integer in [lo, hi] inclusive */ @@ -2141,47 +2212,41 @@ Value* builtin_random_int(Value *arg) { /* #971 Phase D: a malformed range answered 0 — a number in nobody's * range. Taped shape: the soft half still records/replays as before. */ ARG_GUARD_TAPED(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, - "random_int", "[lo, hi]", make_num(0)); + "random_int", "[lo, hi]", make_float(0)); Value *lo = BUILTIN_LIST_VIEW(arg, 0); Value *hi = BUILTIN_LIST_VIEW(arg, 1); - ARG_GUARD_TAPED(!lo || lo->type != VAL_NUM || !hi || hi->type != VAL_NUM, - "random_int", "numeric bounds", make_num(0)); - /* Range-check as doubles before any integer cast — a double outside the - * int64_t range (or non-finite) makes the cast itself UB (#698 fixed the - * same cast-before-range-check class in value_to_string). */ - double lo_d = eigs_num_arg(lo, __func__); - double hi_d = eigs_num_arg(hi, __func__); - if (!isfinite(lo_d) || !isfinite(hi_d) || - lo_d < -9223372036854775808.0 || hi_d < -9223372036854775808.0 || - lo_d >= 9223372036854775808.0 || hi_d >= 9223372036854775808.0) { - rt_error(EK_VALUE, 0, - "random_int: bounds must be finite and within int64 range"); - return make_null(); - } - int64_t lo_i = (int64_t)lo_d; - int64_t hi_i = (int64_t)hi_d; - if (hi_i < lo_i) TRACE_NONDET_RET("random_int", make_num(lo_i)); - /* Span in uint64_t: a wide-but-legal int64 range (e.g. INT64_MIN..0) would - * overflow a signed int64 subtraction before the rejection below. Cannot - * wrap uint64: the double guard above keeps hi_d < 2^63, and the largest - * double below 2^63 is 2^63 - 1024, so hi_i - lo_i + 1 <= 2^64 - 1023. */ + ARG_GUARD_TAPED(!lo || !val_is_number(lo) || !hi || !val_is_number(hi), + "random_int", "numeric bounds", make_float(0)); + int64_t lo_i = eigs_int_arg(lo, "random_int"); + int64_t hi_i = eigs_int_arg(hi, "random_int"); + if (g_has_error) return make_null(); + if (hi_i < lo_i) TRACE_NONDET_RET("random_int", make_int(lo_i)); + /* Unsigned subtraction handles all int64 bounds; span zero is 2^64. */ uint64_t span = (uint64_t)hi_i - (uint64_t)lo_i + 1; - if (span > (uint64_t)INT32_MAX + 1) { + if (span == 0 || span > (uint64_t)INT32_MAX + 1) { rt_error(EK_VALUE, 0, "random_int: range span %llu exceeds 2^31 (lrand48 supplies 31 bits)", (unsigned long long)span); return make_null(); } - TRACE_NONDET_RET("random_int", make_num(lo_i + (eigs_random_long() % (int64_t)span))); + TRACE_NONDET_RET("random_int", make_int(lo_i + (eigs_random_long() % (int64_t)span))); } -/* seed_random of n → seeds the RNG, returns true */ -Value* builtin_seed_random(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "seed_random", "a number", make_bool(0)); +/* The one way to seed the shared stream: `seed_random` and the CLI's + * EIGS_SEED_RANDOM (#2120, src/main.c) both come through here. */ +void eigs_seed_random(long seed) { pthread_mutex_lock(&g_random_lock); - srand48((long)eigs_num_arg(arg, __func__)); + srand48(seed); __atomic_store_n(&g_random_seeded, 1, __ATOMIC_RELEASE); pthread_mutex_unlock(&g_random_lock); +} + +/* seed_random of n → seeds the RNG, returns true */ +Value* builtin_seed_random(Value *arg) { + ARG_GUARD(!arg || !val_is_number(arg), "seed_random", "a number", make_bool(0)); + int64_t seed = eigs_int_arg(arg, "seed_random"); + if (g_has_error) return make_null(); + eigs_seed_random((long)seed); return make_bool(1); } @@ -2407,9 +2472,10 @@ Value* builtin_json_path(Value *arg) { val_decref(root); return r; } - if (current->type == VAL_NUM) { + if (val_is_number(current)) { char buf[64]; - eigs_num_text(buf, sizeof(buf), eigs_num_arg(current, __func__)); /* #875 */ + if (current->type == VAL_INT) eigs_int_text(buf, sizeof(buf), VAL_I64_RAW(current)); + else eigs_num_text(buf, sizeof(buf), eigs_float_arg(current, "json_path")); /* #875 */ val_decref(root); return make_str(buf); } @@ -2518,8 +2584,9 @@ void tokenlist_user_spelling(TokenList *tl) { Value* builtin_token_name(Value *arg) { /* #971 Phase D: "?" is the documented answer for an UNKNOWN id (below); * for a non-number it was laundering a type mistake into that answer. */ - ARG_GUARD(!arg || arg->type != VAL_NUM, "token_name", "a token id", make_str("?")); - int id = (int)eigs_num_arg(arg, __func__); + ARG_GUARD(!arg || !val_is_number(arg), "token_name", "a token id", make_str("?")); + int id = builtin_c_int(arg, "token_name"); + if (g_has_error) return make_null(); static const char *names[] = { "NUM", "STR", "IDENT", "IS", "OF", "DEFINE", "AS", @@ -2555,8 +2622,10 @@ Value* builtin_token_name(Value *arg) { * the dict snapshot it returns is independent of subsequent program state. */ Value* builtin_state_at(Value *arg) { BOOL_REFUSE(arg, "state_at"); - if (!arg || arg->type != VAL_NUM) return make_null(); - Value *d = trace_state_at((int)eigs_num_arg(arg, __func__)); + if (!arg || !val_is_number(arg)) return make_null(); + int line = builtin_c_int(arg, "state_at"); + if (g_has_error) return make_null(); + Value *d = trace_state_at(line); return d ? d : make_null(); } @@ -2602,16 +2671,17 @@ Value* builtin_secure_equals(Value *arg) { * else raises too — the old silent empty-string return for >127 hid every * high-byte construction bug (#435). */ Value* builtin_chr(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "chr requires a number"); return make_null(); } - double num = eigs_num_arg(arg, __func__); - if (num != (double)(long long)num || num < 1 || num > 255) { + int64_t num = eigs_int_arg(arg, "chr"); + if (g_has_error) return make_null(); + if (num < 1 || num > 255) { if (num == 0) rt_error(EK_VALUE, 0, "chr of 0: strings are NUL-terminated and cannot hold a NUL byte (use a buffer for binary with NULs)"); else - rt_error(EK_VALUE, 0, "chr requires an integer byte in 1..255 (got %g); for a codepoint use utf8_encode (lib/utf8.eigs)", num); + rt_error(EK_VALUE, 0, "chr requires an integer byte in 1..255 (got %lld); for a codepoint use utf8_encode (lib/utf8.eigs)", (long long)num); return make_null(); } char buf[2] = { (char)(int)num, '\0' }; @@ -2621,29 +2691,26 @@ Value* builtin_chr(Value *arg) { /* ==== BUILTIN: hex ==== */ /* hex of n → uppercase hex string, minimal digits ("0" for 0). * hex of [n, nibbles] → zero-padded to at least `nibbles` digits. - * n must be a non-negative integer (exact up to 2^53); anything else + * n must be a non-negative int64 integer (N5); anything else * raises — hex of a fraction or a negative has no single right answer * and silently picking one is the #316/#368 tolerance class. * Demanded by the emulator repos (DMG GAP-DMG-010): addresses/opcodes/ * registers all want hex diagnostics and every consumer hand-rolled it. */ Value* builtin_hex(Value *arg) { STRICT_LIST_MAX(arg, 2, "hex"); - double num; - long long width = 0; - if (arg && arg->type == VAL_NUM) { - num = eigs_num_arg(arg, __func__); - } else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2 && - BUILTIN_LIST_VIEW(arg, 0) && BUILTIN_LIST_VIEW(arg, 0)->type == VAL_NUM && - BUILTIN_LIST_VIEW(arg, 1) && BUILTIN_LIST_VIEW(arg, 1)->type == VAL_NUM) { - num = eigs_list_num(arg, 0, __func__); - width = (long long)eigs_list_num(arg, 1, __func__); - if (g_has_error) return make_null(); + int64_t num, width = 0; + if (arg && val_is_number(arg)) { + num = eigs_int_arg(arg, "hex"); + } else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { + num = eigs_list_int(arg, 0, "hex"); + width = eigs_list_int(arg, 1, "hex"); } else { rt_error(EK_TYPE, 0, "hex requires a number or [number, nibbles]"); return make_null(); } - if (num < 0 || num != (double)(long long)num || num > 9007199254740992.0) { - rt_error(EK_VALUE, 0, "hex requires a non-negative integer (got %g)", num); + if (g_has_error) return make_null(); + if (num < 0) { + rt_error(EK_VALUE, 0, "hex requires a non-negative integer"); return make_null(); } if (width < 0) width = 0; @@ -2663,11 +2730,11 @@ Value* builtin_ord(Value *arg) { * the same rule that #1008 applied to sum/mean/norm. Non-strict is * byte-identical: both halves still answer -1. */ ARG_GUARD(!arg || arg->type != VAL_STR || !arg->data.str, - "ord", "a string", make_num(-1)); + "ord", "a string", make_float(-1)); /* fs:ANSWER an empty string has no first byte, so -1 is ord's documented * result for it, not a rejected argument. */ - if (arg->data.str[0] == '\0') return make_num(-1); - return make_num((double)(unsigned char)arg->data.str[0]); + if (arg->data.str[0] == '\0') return make_int(-1); + return make_int((unsigned char)arg->data.str[0]); } /* ==== BUILTIN: try_parse ==== */ @@ -2784,7 +2851,7 @@ static const char *vm_desc_abi_error(Value *desc, char *buf, size_t buflen) { if (!desc || desc->type != VAL_LIST || desc->data.list.count < 1) return "chunk descriptor must be a list"; Value *rev = BUILTIN_LIST_VIEW(desc, 0); - if (!rev || rev->type != VAL_NUM) { + if (!rev || !val_is_number(rev)) { snprintf(buf, buflen, "chunk descriptor carries no bytecode ABI revision (element 0 " "must be the number %d, found %s) — this producer predates the " @@ -2792,12 +2859,14 @@ static const char *vm_desc_abi_error(Value *desc, char *buf, size_t buflen) { EIGS_BYTECODE_ABI, rev ? val_type_name(rev->type) : "nothing"); return buf; } - if ((int)eigs_num_arg(rev, __func__) != EIGS_BYTECODE_ABI) { + int revision = builtin_c_int(rev, "vm_run_bytecode"); + if (g_has_error) return "bytecode ABI revision must be an integer"; + if (revision != EIGS_BYTECODE_ABI) { snprintf(buf, buflen, "chunk was produced for bytecode ABI revision %d, but this " "runtime speaks revision %d — regenerate it with a matching " "producer", - (int)eigs_num_arg(rev, __func__), EIGS_BYTECODE_ABI); + revision, EIGS_BYTECODE_ABI); return buf; } return NULL; @@ -2893,7 +2962,8 @@ static void desc_leave(DescVerify *ctx) { static Value *desc_isolate_const(DescVerify *ctx, Value *v) { if (!v || !desc_spend(ctx, 1)) return NULL; switch (v->type) { - case VAL_NUM: + case VAL_FLOAT: + case VAL_INT: /* #1758 R1.2a: an immutable scalar, like a float */ case VAL_STR: case VAL_NULL: case VAL_BOOL: @@ -3010,12 +3080,14 @@ static EigsChunk *vm_build_chunk_desc_body(Value *desc, int off, int sandbox_mod Value *b = BUILTIN_LIST_VIEW(code, i); /* #1637: a non-number code element (a bool) is refused, never * emitted as byte 0 (OP_CONST's opcode). */ - if (!b || b->type != VAL_NUM) { + if (!b || !val_is_number(b)) { chunk_free(chunk); vm_desc_error(why, whyn, "descriptor code must be a list of byte numbers"); return NULL; } - chunk_emit(chunk, (uint8_t)((int)eigs_num_arg(b, __func__) & 0xFF), 1); + uint8_t byte = eigs_byte_arg(b, "vm_run_bytecode"); + if (g_has_error) { chunk_free(chunk); return NULL; } + chunk_emit(chunk, byte, 1); } /* Positional: the code stream indexes this pool by position, so neither * the dedup collapse nor a skipped NULL may shift an entry (#721). A hole @@ -3071,8 +3143,9 @@ static EigsChunk *vm_build_chunk_desc_body(Value *desc, int off, int sandbox_mod vm_desc_error(why, whyn, "descriptor param_count must be a number"); return NULL; } - int param_count = (n >= 4 && d[3] && d[3]->type == VAL_NUM) - ? (int)eigs_num_arg(d[3], __func__) : 0; + int param_count = (n >= 4 && d[3] && val_is_number(d[3])) + ? builtin_c_int(d[3], "vm_run_bytecode") : 0; + if (g_has_error) { chunk_free(chunk); return NULL; } chunk->param_count = param_count; chunk->first_default = param_count; /* no defaults */ @@ -3165,12 +3238,14 @@ static EigsChunk *vm_build_chunk_desc(Value *desc, int off, int sandbox_mode, Value* builtin_vm_run_bytecode(Value *arg) { char abibuf[256]; const char *abi_err = vm_desc_abi_error(arg, abibuf, sizeof abibuf); + if (g_has_error) return make_null(); if (abi_err) { rt_error(EK_VALUE, 0, "%s", abi_err); return make_null(); } char why[192]; EigsChunk *chunk = vm_build_chunk_desc(arg, 1, 0, why, sizeof why); if (!chunk) { - rt_error(EK_VALUE, 0, "invalid chunk descriptor: %s", - why[0] ? why : "verification failed"); + if (!g_has_error) + rt_error(EK_VALUE, 0, "invalid chunk descriptor: %s", + why[0] ? why : "verification failed"); return make_null(); } /* #915: ARM the observer for what this descriptor itself does, exactly as @@ -3246,6 +3321,84 @@ static const char *SANDBOX_ALLOW[] = { "zeros_like", "fill", "leaky_relu", "relu", "softmax", "log_softmax", "sgd_update", "sgd_update_cols", "sgd_update_rows", "numerical_grad", "numerical_grad_cols", "numerical_grad_rows", + /* N9 pure width arithmetic */ + "i8_wrapping_add", + "i8_wrapping_sub", + "i8_wrapping_mul", + "i8_checked_add", + "i8_checked_sub", + "i8_checked_mul", + "i8_saturating_add", + "i8_saturating_sub", + "i8_saturating_mul", + "i16_wrapping_add", + "i16_wrapping_sub", + "i16_wrapping_mul", + "i16_checked_add", + "i16_checked_sub", + "i16_checked_mul", + "i16_saturating_add", + "i16_saturating_sub", + "i16_saturating_mul", + "i32_wrapping_add", + "i32_wrapping_sub", + "i32_wrapping_mul", + "i32_checked_add", + "i32_checked_sub", + "i32_checked_mul", + "i32_saturating_add", + "i32_saturating_sub", + "i32_saturating_mul", + "i64_wrapping_add", + "i64_wrapping_sub", + "i64_wrapping_mul", + "i64_checked_add", + "i64_checked_sub", + "i64_checked_mul", + "i64_saturating_add", + "i64_saturating_sub", + "i64_saturating_mul", + "u8_wrapping_add", + "u8_wrapping_sub", + "u8_wrapping_mul", + "u8_checked_add", + "u8_checked_sub", + "u8_checked_mul", + "u8_saturating_add", + "u8_saturating_sub", + "u8_saturating_mul", + "u16_wrapping_add", + "u16_wrapping_sub", + "u16_wrapping_mul", + "u16_checked_add", + "u16_checked_sub", + "u16_checked_mul", + "u16_saturating_add", + "u16_saturating_sub", + "u16_saturating_mul", + "u32_wrapping_add", + "u32_wrapping_sub", + "u32_wrapping_mul", + "u32_checked_add", + "u32_checked_sub", + "u32_checked_mul", + "u32_saturating_add", + "u32_saturating_sub", + "u32_saturating_mul", + "u64_wrapping_add", + "u64_wrapping_sub", + "u64_wrapping_mul", + "u64_checked_add", + "u64_checked_sub", + "u64_checked_mul", + "u64_saturating_add", + "u64_saturating_sub", + "u64_saturating_mul", + "u64_compare", + "u64_div", + "u64_rem", + "u64_to_string", + "fround", /* bit ops */ "bit_and", "bit_not", "bit_or", "bit_shl", "bit_shr", "bit_xor", /* list / sequence (operate on the value handed in) */ @@ -3274,7 +3427,7 @@ static const char *SANDBOX_ALLOW[] = { /* path string manipulation (no fs access) */ "path_base", "path_dir", "path_ext", "path_join", /* type / value utilities */ - "type", "coalesce", "num_copy", "secure_equals", + "type", "num_type", "coalesce", "num_copy", "secure_equals", /* observer reads touch only the sandbox's own values, never globals. * record_history is intentionally not here. * state_at is also excluded: temporal history belongs to the host thread, @@ -3390,28 +3543,35 @@ Value* builtin_sandbox_run(Value *arg) { const char *source = source_form ? desc->data.str : NULL; int max_iter = 1000000; if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2 && - BUILTIN_LIST_VIEW(arg, 1) && BUILTIN_LIST_VIEW(arg, 1)->type == VAL_NUM) - max_iter = (int)eigs_list_num(arg, 1, __func__); + BUILTIN_LIST_VIEW(arg, 1) && val_is_number(BUILTIN_LIST_VIEW(arg, 1))) + max_iter = builtin_c_int(BUILTIN_LIST_VIEW(arg, 1), "sandbox_run"); + if (g_has_error) return make_null(); /* #292: optional max_bytes (3rd element) — the allocation budget for this * run. Default 256 MiB: ample for the short generated snippets grade() * runs, small enough that even a few of them can't thrash a 4 GB box. */ size_t max_bytes = (size_t)256 * 1024 * 1024; if (arg && arg->type == VAL_LIST && arg->data.list.count >= 3 && - BUILTIN_LIST_VIEW(arg, 2) && BUILTIN_LIST_VIEW(arg, 2)->type == VAL_NUM && - eigs_list_num(arg, 2, __func__) > 0) - max_bytes = (size_t)eigs_list_num(arg, 2, __func__); + BUILTIN_LIST_VIEW(arg, 2) && val_is_number(BUILTIN_LIST_VIEW(arg, 2))) { + int64_t requested = eigs_list_int(arg, 2, "sandbox_run"); + if (g_has_error) return make_null(); + if (requested > 0) { + if ((uint64_t)requested > SIZE_MAX) { + rt_error(EK_VALUE, 0, "sandbox_run: byte limit exceeds size_t"); + return make_null(); + } + max_bytes = (size_t)requested; + } + } /* VM instructions, cumulative across callback re-entry and function * frames. This is intentionally distinct from the compatibility loop * limit. Ten million keeps normal generated programs comfortable while * placing a finite ceiling on straight-line/call-heavy computation. */ uint64_t max_work = UINT64_C(10000000); if (arg && arg->type == VAL_LIST && arg->data.list.count >= 4 && - BUILTIN_LIST_VIEW(arg, 3) && BUILTIN_LIST_VIEW(arg, 3)->type == VAL_NUM) { - double requested = eigs_list_num(arg, 3, __func__); - /* A double rounds UINT64_MAX up to 2^64, so use a strict bound; - * accepting equality and casting it would itself be undefined. */ - if (isfinite(requested) && requested >= 1.0 && - requested < 18446744073709551616.0) + BUILTIN_LIST_VIEW(arg, 3) && val_is_number(BUILTIN_LIST_VIEW(arg, 3))) { + int64_t requested = eigs_list_int(arg, 3, "sandbox_run"); + if (g_has_error) return make_null(); + if (requested >= 1) max_work = (uint64_t)requested; } @@ -3427,10 +3587,27 @@ Value* builtin_sandbox_run(Value *arg) { * gets its own message — a grading ladder must be able to tell "your * producer is stale" from "your bytecode is malformed" without re-running. */ char abibuf[256]; + /* A descriptor is guest data: numeric validation failures join the + * existing structured descriptor rejection. Optional HOST limits above + * still raise to the caller (N5, Q-SANDBOX). Suppress an uncaught print + * until this boundary has consumed a numeric reader's error latch. */ + int validation_try_depth = g_try_depth; + int validation_latched = g_sandbox_error_latched; + int validation_pending = g_error_print_pending; + g_try_depth++; const char *abi_err = source_form ? NULL : vm_desc_abi_error(desc, abibuf, sizeof abibuf); EigsChunk *chunk = source_form ? NULL : (abi_err ? NULL : vm_build_chunk_desc(desc, 1, 1, NULL, 0)); + g_try_depth = validation_try_depth; + if (g_has_error && g_error_kind == EK_VALUE) { + snprintf(abibuf, sizeof abibuf, "%.255s", g_error_raw); + abi_err = abibuf; + g_has_error = 0; + g_sandbox_error_latched = validation_latched; + g_error_print_pending = validation_pending; + eigs_clear_error_value(); + } if (chunk && chunk_reads_shared_temporal(chunk)) { chunk_free(chunk); chunk = NULL; @@ -3451,7 +3628,7 @@ Value* builtin_sandbox_run(Value *arg) { dict_set_owned(ev, "kind", make_str(err_kind_name(EK_VALUE))); dict_set_owned(ev, "message", make_str(abi_err ? abi_err : "invalid chunk descriptor")); - dict_set_owned(ev, "line", make_num(0)); + dict_set_owned(ev, "line", make_int(0)); dict_set_owned(ev, "file", make_str("")); dict_set_owned(out, "error", ev); return sandbox_finish_run(out); @@ -3714,7 +3891,7 @@ Value* builtin_sandbox_run(Value *arg) { "loop budget exhausted (max_iterations=%d): partial run", loop_max); dict_set_owned(ev, "message", make_str(capmsg)); - dict_set_owned(ev, "line", make_num(0)); + dict_set_owned(ev, "line", make_int(0)); dict_set_owned(ev, "file", make_str("")); dict_set_owned(out, "error", ev); } @@ -3727,7 +3904,7 @@ Value* builtin_sandbox_run(Value *arg) { dict_set_owned(ev, "kind", make_str(err_kind_name((ErrKind)g_sandbox_refusal_kind))); dict_set_owned(ev, "message", make_str(g_sandbox_refusal_msg)); - dict_set_owned(ev, "line", make_num((double)g_sandbox_refusal_line)); + dict_set_owned(ev, "line", make_int(g_sandbox_refusal_line)); dict_set_owned(ev, "file", make_str(g_sandbox_refusal_file)); dict_set_owned(out, "error", ev); if (g_has_error) { @@ -3742,7 +3919,7 @@ Value* builtin_sandbox_run(Value *arg) { Value *ev = make_dict(4); dict_set_owned(ev, "kind", make_str(err_kind_name((ErrKind)g_error_kind))); dict_set_owned(ev, "message", make_str(g_error_raw)); - dict_set_owned(ev, "line", make_num((double)g_error_line)); + dict_set_owned(ev, "line", make_int(g_error_line)); dict_set_owned(ev, "file", make_str(g_error_file)); dict_set_owned(out, "error", ev); g_has_error = 0; @@ -3777,7 +3954,7 @@ Value* builtin_sandbox_run(Value *arg) { "budget); refusing to cross the boundary" : "sandbox result contains a callable; " "functions cannot cross the sandbox boundary")); - dict_set_owned(ev, "line", make_num(0)); + dict_set_owned(ev, "line", make_int(0)); dict_set_owned(ev, "file", make_str("")); dict_set_owned(out, "error", ev); } @@ -3806,18 +3983,20 @@ Value* builtin_sandbox_run(Value *arg) { * compiles a temporal query; a self-hosted compiler (whose output runs via * vm_run_bytecode) calls this to do the same. Returns the previous setting. */ Value* builtin_record_history(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "record_history requires a number flag (nonzero=on, 0=off), got %s", arg ? val_type_name(arg->type) : "null"); return make_null(); } int prev = g_trace_hist; - int on = (eigs_num_arg(arg, __func__) != 0.0) ? 1 : 0; + double flag = eigs_float_arg(arg, "record_history"); + if (g_has_error) return make_null(); + int on = flag != 0.0; /* #827: no name to narrow on — a self-hosted compiler calling this is * standing in for the whole-program arming, so it gets the wildcard. */ if (on) { trace_arm_history_all(); trace_flag_store(g_trace_obs_hist_storage, 1); } else trace_history_disable(); - return make_num((double)prev); + return make_int(prev); } /* ==== BUILTIN: tensor_save ==== */ @@ -3845,12 +4024,13 @@ Value* builtin_copy_into(Value *arg) { Value *dest = list_get_ref(arg, 0); Value *offv = BUILTIN_LIST_VIEW(arg, 1); Value *src = BUILTIN_LIST_VIEW(arg, 2); - if (!offv || offv->type != VAL_NUM) { + if (!offv || !val_is_number(offv)) { rt_error(EK_TYPE, 0, "copy_into: offset must be a number, got %s (the order is [dest, offset, src])", offv ? val_type_name(offv->type) : "null"); return make_null(); } - int offset = (int)eigs_num_arg(offv, __func__); + int offset = builtin_c_int(offv, "copy_into"); + if (g_has_error) return make_null(); if (offset < 0) { rt_error(EK_INDEX, 0, "copy_into: offset %d is negative", offset); return make_null(); } if (dest && dest->type == VAL_BUFFER) { /* buffer destination: numbers from a buffer or a list of numbers */ @@ -3862,12 +4042,14 @@ Value* builtin_copy_into(Value *arg) { if (src && src->type == VAL_LIST) { for (int i = 0; i < src->data.list.count && offset + i < dest->data.buffer.count; i++) { Value *it = BUILTIN_LIST_VIEW(src, i); - if (!it || it->type != VAL_NUM) { + if (!it || !val_is_number(it)) { rt_error(EK_TYPE, 0, "cannot store %s in a buffer (copy_into element %d; buffers hold numbers)", it ? val_type_name(it->type) : "null", i); return make_null(); } - dest->data.buffer.data[offset + i] = eigs_num_arg(it, __func__); + double value = eigs_float_arg(it, "copy_into"); + if (g_has_error) return make_null(); + dest->data.buffer.data[offset + i] = value; } return dest; /* borrowed, like the list path below */ } @@ -3900,11 +4082,12 @@ Value* builtin_list_slice(Value *arg) { Value *end_v = BUILTIN_LIST_VIEW(arg, 2); BOOL_REFUSE(start_v, "list_slice"); BOOL_REFUSE(end_v, "list_slice"); - if (!start_v || start_v->type != VAL_NUM || !end_v || end_v->type != VAL_NUM) + if (!start_v || !val_is_number(start_v) || !end_v || !val_is_number(end_v)) return make_null(); int n = list->data.list.count; - int start = (int)eigs_num_arg(start_v, __func__); - int end = (int)eigs_num_arg(end_v, __func__); + int start = builtin_c_int(start_v, "list_slice"); + int end = builtin_c_int(end_v, "list_slice"); + if (g_has_error) return make_null(); if (start < 0) start += n; if (end < 0) end += n; if (start < 0) start = 0; @@ -3926,8 +4109,9 @@ Value* builtin_list_slice(Value *arg) { */ Value* builtin_num_copy(Value *arg) { BOOL_REFUSE(arg, "num_copy"); - if (!arg || arg->type != VAL_NUM) return make_null(); - return make_num(eigs_num_arg(arg, __func__)); + if (!arg || !val_is_number(arg)) return make_null(); + EigsNum n = eigs_number_arg(arg, "num_copy"); + return g_has_error ? make_null() : builtin_number_value(n); } /* ==== BUILTIN: concat ==== */ @@ -3956,12 +4140,10 @@ Value* builtin_concat(Value *arg) { * range of [start, end, step] → [start, start+step, ...] while < end (or > end if step < 0) */ /* A bound outside int range was a UB cast (libFuzzer run 5). No such * range could be materialized anyway, so it raises. */ -static int range_bound(double d, int *out) { - if (!(d >= (double)INT_MIN && d <= (double)INT_MAX)) { - rt_error(EK_VALUE, 0, "range: bound %g is outside the int range", d); - return 0; - } - *out = (int)d; +static int range_bound(const Value *v, int *out) { + int64_t n; + if (!eigs_int_arg_range(v, INT_MIN, INT_MAX, &n, "range")) return 0; + *out = (int)n; return 1; } @@ -3971,16 +4153,16 @@ Value* builtin_range(Value *arg) { if (!arg) return make_list(0); - if (arg->type == VAL_NUM) { + if (val_is_number(arg)) { /* range of n */ - if (!range_bound(eigs_num_arg(arg, __func__), &end)) return make_list(0); + if (!range_bound(arg, &end)) return make_list(0); } else if (arg->type == VAL_LIST) { int argc = arg->data.list.count; /* #497: every provided bound must be numeric. A non-number used to * be silently treated as 0 (or fold the whole call to []) — a * silent-wrong loop count. */ for (int i = 0; i < argc && i < 3; i++) { - if (BUILTIN_LIST_VIEW(arg, i)->type != VAL_NUM) { + if (!val_is_number(BUILTIN_LIST_VIEW(arg, i))) { rt_error(EK_TYPE, 0, "range: argument %d must be a number, got %s", i, val_type_name(BUILTIN_LIST_VIEW(arg, i)->type)); @@ -3988,14 +4170,14 @@ Value* builtin_range(Value *arg) { } } if (argc >= 1) { - if (!range_bound(eigs_list_num(arg, 0, __func__), &start)) return make_list(0); + if (!range_bound(BUILTIN_LIST_VIEW(arg, 0), &start)) return make_list(0); } if (argc >= 2) { - if (!range_bound(eigs_list_num(arg, 1, __func__), &end)) return make_list(0); + if (!range_bound(BUILTIN_LIST_VIEW(arg, 1), &end)) return make_list(0); } else { end = start; start = 0; } /* single-element list: treat as range of n */ if (argc >= 3) { - if (!range_bound(eigs_list_num(arg, 2, __func__), &step)) return make_list(0); + if (!range_bound(BUILTIN_LIST_VIEW(arg, 2), &step)) return make_list(0); /* The Euler-like update that feeds step can never produce * exactly zero — it trades the zero singularity for infinity. * This bound catches the infinity side: a step that would @@ -4037,9 +4219,12 @@ Value* builtin_range(Value *arg) { if (!sandbox_charge((size_t)count * (sizeof(Value) + sizeof(Value *)))) return make_list(0); Value *result = make_list(count); - /* Index by k: `i += step` overflowed int near INT_MAX. */ - for (int k = 0; k < count; k++) { - Value *v = make_num((double)((int64_t)start + (int64_t)k * step)); + /* Keep the induction value in int64: int bounds/step and the 1M cap + * also keep the final increment in range. Count down without a second + * increasing index in this allocation loop. */ + int64_t value = start; + for (int remaining = count; remaining > 0; --remaining, value += step) { + Value *v = make_int(value); list_append(result, v); val_decref(v); } @@ -4055,7 +4240,7 @@ Value* builtin_fill(Value *arg) { return make_list(0); } BOOL_REFUSE(BUILTIN_LIST_VIEW(arg, 0), "fill"); /* the count; the value may be anything */ - int count = (int)eigs_list_num(arg, 0, __func__); + int count = builtin_c_int(BUILTIN_LIST_VIEW(arg, 0), "fill"); if (g_has_error) return make_null(); /* #1637: a non-number slot raised */ EigsSlot val = list_slot(arg, 1); if (count < 0) count = 0; @@ -4077,15 +4262,16 @@ Value* builtin_fill(Value *arg) { * operator, which raises index_range. These helpers raise the same kinds. */ static int at_index(Value *idx_val, int count, const char *what, int *out) { - if (!idx_val || idx_val->type != VAL_NUM) { + if (!idx_val || !val_is_number(idx_val)) { rt_error(EK_VALUE, 0, "%s index must be an integer", what); return 0; } - int idx = (int)eigs_num_arg(idx_val, __func__); + int idx = builtin_c_int(idx_val, what); + if (g_has_error) return 0; if (idx < 0) idx += count; /* #312: negative counts from end */ if (idx < 0 || idx >= count) { rt_error(EK_INDEX, 0, "index %d out of range (list length %d)", - (int)eigs_num_arg(idx_val, __func__), count); + builtin_c_int(idx_val, what), count); return 0; } *out = idx; @@ -4095,17 +4281,18 @@ static int at_index(Value *idx_val, int count, const char *what, /* #1093: the buffer twin of at_index — same negative-from-the-end rule, but * the diagnostic `b[i]` and `buf_get` already use for a buffer, so `set_at of * [buf, 9, v]` and `buf[9]` do not report the same fault two different ways. */ -static int buf_at_index(Value *idx_val, int count, int *out) { - if (!idx_val || idx_val->type != VAL_NUM) { +static int buf_at_index(Value *idx_val, int count, int *out, const char *who) { + if (!idx_val || !val_is_number(idx_val)) { rt_error(EK_VALUE, 0, "buffer index must be a number, got %s", val_type_name(idx_val ? idx_val->type : VAL_NULL)); return 0; } - int idx = (int)eigs_num_arg(idx_val, __func__); + int idx = builtin_c_int(idx_val, who); + if (g_has_error) return 0; if (idx < 0) idx += count; if (idx < 0 || idx >= count) { rt_error(EK_INDEX, 0, "buffer index %d out of range (length %d)", - (int)eigs_num_arg(idx_val, __func__), count); + builtin_c_int(idx_val, who), count); return 0; } *out = idx; @@ -4131,26 +4318,28 @@ Value* builtin_set_at(Value *arg) { if (argc == 3) { int idx; if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 1), buf->data.buffer.count, - &idx)) return make_null(); + &idx, "set_at")) return make_null(); off = idx; } else if (argc == 4 && buf->data.buffer.rows > 0) { int row, col; if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 1), buf->data.buffer.rows, - &row)) return make_null(); + &row, "set_at")) return make_null(); if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 2), buf->data.buffer.cols, - &col)) return make_null(); + &col, "set_at")) return make_null(); off = (int64_t)row * buf->data.buffer.cols + col; } else { rt_error(EK_TYPE, 0, "set_at: [buffer, row, col, value] needs a " "shaped buffer (see reshape)"); return make_null(); } - if (!val || val->type != VAL_NUM) { + if (!val || !val_is_number(val)) { rt_error(EK_TYPE, 0, "cannot store %s in a buffer (buffers hold numbers)", val_type_name(val ? val->type : VAL_NULL)); return make_null(); } - buf->data.buffer.data[off] = eigs_num_arg(val, __func__); + double value = eigs_float_arg(val, "set_at"); + if (g_has_error) return make_null(); + buf->data.buffer.data[off] = value; /* Direct child of the arg vector — the borrow protocol (#720, * vm_borrow_compensate) compensates at the call site, exactly as for * the list path's `return list`. */ @@ -4210,16 +4399,16 @@ Value* builtin_get_at(Value *arg) { if (argc == 2) { int idx; if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 1), buf->data.buffer.count, - &idx)) return make_null(); - return make_num(buffer_read_num(buf, idx)); + &idx, "get_at")) return make_null(); + return make_float(buffer_read_f64(buf, idx)); } if (argc == 3 && buf->data.buffer.rows > 0) { int row, col; if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 1), buf->data.buffer.rows, - &row)) return make_null(); + &row, "get_at")) return make_null(); if (!buf_at_index(BUILTIN_LIST_VIEW(arg, 2), buf->data.buffer.cols, - &col)) return make_null(); - return make_num(buffer_read_num(buf, (int64_t)row * buf->data.buffer.cols + col)); + &col, "get_at")) return make_null(); + return make_float(buffer_read_f64(buf, (int64_t)row * buf->data.buffer.cols + col)); } rt_error(EK_TYPE, 0, "get_at: [buffer, row, col] needs a shaped buffer " "(see reshape)"); @@ -4604,14 +4793,14 @@ Value* builtin_spawn(Value *arg) { return make_null(); } Value *d = make_dict(8); - dict_set_owned(d, "_handle_id", make_num((double)hid)); + dict_set_owned(d, "_handle_id", make_int(hid)); /* #1146 (2): the slot GENERATION travels with the handle. Ids recycle * round-robin over 255 slots, so without this a handle held across 255 * spawn/join cycles named a slot a different thread now owned — and * joining it returned THAT thread's value while the real one returned * null, at exit status 0. */ - dict_set_owned(d, "_handle_gen", make_num((double)hgen)); - dict_set_owned(d, "done", make_num(0)); + dict_set_owned(d, "_handle_gen", make_int(hgen)); + dict_set_owned(d, "done", make_int(0)); return d; } @@ -4636,12 +4825,15 @@ Value* builtin_thread_join(Value *arg) { } Value *hv = BUILTIN_DICT_VIEW(arg, "_handle_id"); Value *gv = BUILTIN_DICT_VIEW(arg, "_handle_gen"); - if (!hv || hv->type != VAL_NUM) { + if (!hv || !val_is_number(hv)) { rt_error(EK_TYPE, 0, "thread_join requires a thread handle"); return make_null(); } - int hid = (int)eigs_num_arg(hv, __func__); - uint32_t hgen = (gv && gv->type == VAL_NUM) ? (uint32_t)eigs_num_arg(gv, __func__) : 0; + int hid = builtin_c_int(hv, "thread_join"); + int64_t gen = 0; + if (gv && val_is_number(gv)) eigs_int_arg_range(gv, 0, UINT32_MAX, &gen, "thread_join"); + if (g_has_error) return make_null(); + uint32_t hgen = (uint32_t)gen; int why = HANDLE_CLAIM_GONE; ThreadHandle *h = (ThreadHandle*)handle_claim(hid, hgen, HANDLE_THREAD, &why); if (!h) { @@ -4739,23 +4931,25 @@ static void wake_channels_for_exit(EigsState *st) { * `_channel_gen` is forged or stripped is reachable right now and must be * refused rather than served. `out_id` is set even on failure so the refusal * can name the slot. */ -static Channel* get_channel_why(Value *v, int *why, int *out_id) { +static Channel* get_channel_why(Value *v, int *why, int *out_id, const char *who) { *why = HANDLE_CLAIM_BADVALUE; *out_id = 0; if (!v || v->type != VAL_DICT) return NULL; Value *cv = BUILTIN_DICT_VIEW(v, "_channel_id"); - if (!cv || cv->type != VAL_NUM) return NULL; - *out_id = (int)eigs_num_arg(cv, __func__); + if (!cv || !val_is_number(cv)) return NULL; + *out_id = builtin_c_int(cv, who); Value *gv = BUILTIN_DICT_VIEW(v, "_channel_gen"); - uint32_t gen = (gv && gv->type == VAL_NUM) ? (uint32_t)eigs_num_arg(gv, __func__) : 0; - return (Channel*)handle_lookup(*out_id, gen, HANDLE_CHANNEL, why); + int64_t gen = 0; + if (gv && val_is_number(gv)) eigs_int_arg_range(gv, 0, UINT32_MAX, &gen, who); + if (g_has_error) return NULL; + return (Channel*)handle_lookup(*out_id, (uint32_t)gen, HANDLE_CHANNEL, why); } /* Non-raising form. ONE caller by design: `channel_closed`, whose documented * ANSWER for an unknown/reclaimed channel is 1 (#971 Phase D), not an error. */ static Channel* get_channel(Value *v) { int why = 0, id = 0; - return get_channel_why(v, &why, &id); + return get_channel_why(v, &why, &id, "channel_closed"); } /* ROUND 2 (#1146 G2/G3): resolve or RAISE, in the shared wording. Every @@ -4764,8 +4958,8 @@ static Channel* get_channel(Value *v) { * tell a stale handle from one that was never valid. */ static Channel* channel_arg(Value *v, const char *who) { int why = 0, id = 0; - Channel *ch = get_channel_why(v, &why, &id); - if (!ch) handle_raise_unresolved(who, "channel", id, why, "closed"); + Channel *ch = get_channel_why(v, &why, &id, who); + if (!ch && !g_has_error) handle_raise_unresolved(who, "channel", id, why, "closed"); return ch; } @@ -4812,8 +5006,8 @@ Value* builtin_channel(Value *arg) { return make_null(); } Value *d = make_dict(8); - dict_set_owned(d, "_channel_id", make_num((double)hid)); - dict_set_owned(d, "_channel_gen", make_num((double)cgen)); + dict_set_owned(d, "_channel_id", make_int(hid)); + dict_set_owned(d, "_channel_gen", make_int(cgen)); return d; } @@ -4901,10 +5095,8 @@ Value* builtin_try_recv(Value *arg) { return val ? val : make_null(); } -/* recv_timeout of [channel, ms] — bounded wait. Returns the value if one - * arrives (or is already buffered) before the deadline, else null. ms is - * interpreted as milliseconds; fractional values are honored (ns precision - * on Linux). Negative ms degenerates to a try_recv. */ +/* recv_timeout of [channel, ms] — milliseconds rounded half away from zero + * (Q7 magnitude), within the wait call's long range. Negative ms polls. */ Value* builtin_recv_timeout(Value *arg) { STRICT_LIST_MAX(arg, 2, "recv_timeout"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) { @@ -4913,29 +5105,21 @@ Value* builtin_recv_timeout(Value *arg) { } Channel *ch = channel_arg(BUILTIN_LIST_VIEW(arg, 0), "recv_timeout"); if (!ch) return make_null(); - if (replay_blocks("recv_timeout", "subprocess/concurrency")) return make_null(); /* #1072: after validation */ Value *ms_v = BUILTIN_LIST_VIEW(arg, 1); - if (ms_v->type != VAL_NUM) { + if (!val_is_number(ms_v)) { rt_error(EK_TYPE, 0, "recv_timeout: ms must be a number"); return make_null(); } - double ms = eigs_num_arg(ms_v, __func__); - /* Sanitize ms before the (long) cast (#151). NaN, +inf, or any value - * above ~LONG_MAX is undefined behavior when cast to long, and in - * practice produced a garbage deadline that fired immediately or - * waited essentially forever. Cap at one year of ms (3.15e10) — well - * below LONG_MAX on 32-bit time_t hosts and far beyond any plausible - * channel timeout. NaN degenerates to try_recv (ms=0). */ - if (isnan(ms)) ms = 0.0; - else if (ms < 0.0) ms = 0.0; - else if (ms > 3.15e10) ms = 3.15e10; + int64_t ms; + if (!eigs_magnitude_arg(ms_v, LONG_MIN, LONG_MAX, &ms, "recv_timeout")) return make_null(); + if (ms < 0) ms = 0; + if (replay_blocks("recv_timeout", "subprocess/concurrency")) return make_null(); /* all refusals precede replay */ struct timespec deadline; clock_gettime(EIGS_CHANNEL_CLOCK, &deadline); long whole_ms = (long)ms; - long extra_ns = (long)((ms - (double)whole_ms) * 1e6); deadline.tv_sec += whole_ms / 1000; - deadline.tv_nsec += (whole_ms % 1000) * 1000000L + extra_ns; + deadline.tv_nsec += (whole_ms % 1000) * 1000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec += deadline.tv_nsec / 1000000000L; deadline.tv_nsec %= 1000000000L; @@ -5145,7 +5329,7 @@ Value* builtin_task_spawn(Value *arg) { t->id = id; t->hgen = tgen; task_sched_on_spawn(id); /* enqueue + arm the scheduler */ - return make_num(task_handle_pack(id, tgen)); + return make_int(task_handle_pack(id, tgen)); } /* Resolve an EigenScript-visible packed task id to the scheduler's raw slot. @@ -5155,8 +5339,11 @@ Value* builtin_task_spawn(Value *arg) { static Task *task_handle_resolve(Value *arg, const char *who, int *out_id) { int id = 0; uint32_t gen = 0; - if (!arg || arg->type != VAL_NUM || - !task_handle_unpack(eigs_num_arg(arg, __func__), &id, &gen)) return NULL; + if (!arg || !val_is_number(arg)) return NULL; + int64_t packed = eigs_int_arg(arg, who); + if (g_has_error || packed <= 0 || packed > INT64_C(9007199254740992)) return NULL; + /* The packed handle API is a double, but every allowed id is exact. */ + if (!task_handle_unpack((double)packed, &id, &gen)) return NULL; int why = HANDLE_CLAIM_GONE; Task *t = (Task *)handle_lookup(id, gen, HANDLE_TASK, &why); if (!t && (why == HANDLE_CLAIM_STALE || why == HANDLE_CLAIM_TYPE)) @@ -5207,7 +5394,9 @@ Value* builtin_task_yield(Value *arg) { * returns immediately. Joining an unknown id, self, or with no scheduler * returns null. Same nesting restriction as task_yield. */ Value* builtin_task_join(Value *arg) { - if (!arg || arg->type != VAL_NUM || !g_task_sched) return make_null(); + if (!arg || !val_is_number(arg)) return make_null(); + (void)eigs_int_arg(arg, "task_join"); + if (g_has_error || !g_task_sched) return make_null(); int target = 0; Task *t = task_handle_resolve(arg, "task_join", &target); if (!t) return make_null(); @@ -5252,15 +5441,16 @@ Value* builtin_task_send(Value *arg) { * trigger, and the differential's probe (which spawns a task first) could * not see it. `task_kill` and `stream_write` already order it this way. * Non-strict is unchanged: both halves answer 0. */ - ARG_GUARD(!idv || idv->type != VAL_NUM, "task_send", "a task id (number)", make_bool(0)); + ARG_GUARD(!idv || !val_is_number(idv), "task_send", "a task id (number)", make_bool(0)); /* fs:ANSWER no scheduler means there is no task to deliver to, which is * the dead-letter drop this function's contract documents ("0 if * dropped") — split out of the arity guard above, which is a mistake. */ - if (!g_task_sched) return make_bool(0); + int64_t packed = eigs_int_arg(idv, "task_send"); + if (g_has_error || !g_task_sched) return make_bool(0); int target = 0; /* Main is the reserved public id 0 and has no handle-table generation, * but it does have a scheduler mailbox (reply-to-supervisor pattern). */ - if (eigs_num_arg(idv, __func__) != 0 && + if (packed != 0 && !task_handle_resolve(idv, "task_send", &target)) return make_bool(0); Value *copy = val_clone_for_send(BUILTIN_LIST_VIEW(arg, 1)); /* share-nothing */ int sent = task_deliver(target, copy); @@ -5283,10 +5473,11 @@ Value* builtin_task_recv(Value *arg) { * and suspended slice, wake any joiner with an `interrupt` error, mark it * dead. Returns 1 if killed, 0 for a bad/self/finished target. */ Value* builtin_task_kill(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "task_kill", "a task id (number)", make_bool(0)); + ARG_GUARD(!arg || !val_is_number(arg), "task_kill", "a task id (number)", make_bool(0)); /* fs:ANSWER no scheduler means no such target; 0 is the documented * "bad/self/finished target" answer, split from the type guard above. */ - if (!g_task_sched) return make_bool(0); + (void)eigs_int_arg(arg, "task_kill"); + if (g_has_error || !g_task_sched) return make_bool(0); int target = 0; if (!task_handle_resolve(arg, "task_kill", &target)) return make_bool(0); return make_bool(task_do_kill(target)); @@ -5295,7 +5486,7 @@ Value* builtin_task_kill(Value *arg) { /* task_alive of id → true while the task is READY/RUNNING/SUSPENDED, else false * (DONE, DEAD, or an unknown id). */ Value* builtin_task_alive(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "task_alive", "a task id (number)", make_bool(0)); + ARG_GUARD(!arg || !val_is_number(arg), "task_alive", "a task id (number)", make_bool(0)); Task *t = task_handle_resolve(arg, "task_alive", NULL); /* fs:ANSWER an unknown id is NOT alive; 0 is this function's documented * answer. Four lines above, an identical `return make_bool(0)` is a type @@ -5317,13 +5508,14 @@ Value* builtin_task_alive(Value *arg) { Value* builtin_task_sleep(Value *arg) { /* #1637: the type check comes before the no-scheduler short-circuit, so * `task_sleep of true` raises with or without a scheduler. */ - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "task_sleep requires a number of ticks"); return make_null(); } - if (!g_task_sched) return make_null(); /* no scheduler: nothing to wait for */ + double ticks = eigs_float_arg(arg, "task_sleep"); + if (g_has_error || !g_task_sched) return make_null(); /* no scheduler: nothing to wait for */ if (no_yield_forbidden("task_sleep")) return make_null(); /* #488 */ - task_request_sleep(eigs_num_arg(arg, __func__)); + task_request_sleep(ticks); return make_null(); /* placeholder: execution resumes when the clock wakes it */ } @@ -5355,7 +5547,7 @@ Value* builtin_must_not_yield(Value *arg) { * state, so it records no tape nondet. */ Value* builtin_task_self(Value *arg) { (void)arg; - return make_num(task_current_id()); + return make_int(task_current_id()); } /* task_detach of id -> 1 (0 for main/unknown). Marks the task fire-and-forget @@ -5369,15 +5561,15 @@ Value* builtin_task_self(Value *arg) { * uncatchable; task.c sched_fatal_stop). Joining a reaped id returns null, * like any unknown id. Deterministic; no tape participation. */ Value* builtin_task_detach(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "task_detach requires a task id (a number)"); return make_null(); } int target = 0; - if (!task_handle_resolve(arg, "task_detach", &target)) return make_num(0); + if (!task_handle_resolve(arg, "task_detach", &target)) return make_int(0); int detached = task_do_detach(target); if (detached < 0) return make_null(); /* #1736: the stop is already raised */ - return make_num((double)detached); + return make_int(detached); } /* task_sched_seed of n — install a scheduling seed. By default tasks run FIFO @@ -5388,11 +5580,13 @@ Value* builtin_task_detach(Value *arg) { * simulation tester uses to search interleavings. Typically called once at * program start. Returns null. */ Value* builtin_task_sched_seed(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "task_sched_seed requires a number (the seed)"); return make_null(); } - task_sched_set_seed(eigs_num_arg(arg, __func__)); + int64_t seed = eigs_int_arg(arg, "task_sched_seed"); + if (g_has_error) return make_null(); + task_sched_set_seed(seed); return make_null(); } @@ -5541,11 +5735,11 @@ Value* builtin_nearest_in_range(Value *arg) { Value *entities = BUILTIN_LIST_VIEW(arg, 0); if (!entities || entities->type != VAL_LIST) return make_null(); BOOL_REFUSE(arg, "nearest_in_range"); /* x, y, range, world: numbers */ - double px = eigs_list_num(arg, 1, __func__); - double py = eigs_list_num(arg, 2, __func__); - double range = eigs_list_num(arg, 3, __func__); - double ww = eigs_list_num(arg, 4, __func__); - double wh = eigs_list_num(arg, 5, __func__); + double px = eigs_list_float(arg, 1, __func__); + double py = eigs_list_float(arg, 2, __func__); + double range = eigs_list_float(arg, 3, __func__); + double ww = eigs_list_float(arg, 4, __func__); + double wh = eigs_list_float(arg, 5, __func__); if (g_has_error) return make_null(); /* #1637: a non-number slot raised */ const char *px_key = "px", *py_key = "py", *active_key = "active"; if (arg->data.list.count >= 7 && BUILTIN_LIST_VIEW(arg, 6)->type == VAL_STR) @@ -5602,7 +5796,7 @@ Value* builtin_nearest_in_range(Value *arg) { } /* #1637: "active" is a bool (false skips); the older 1/0 number * still reads as before. */ - if (av && ((av->type == VAL_NUM && eigs_num_arg(av, __func__) != 1.0) + if (av && ((val_is_number(av) && eigs_float_arg(av, __func__) != 1.0) || (av->type == VAL_BOOL && !av->data.boolean))) continue; Value *ex; @@ -5626,13 +5820,14 @@ Value* builtin_nearest_in_range(Value *arg) { /* #1637 round 4: a bool coordinate raises (any other non-number * keeps the documented skip). */ if ((ex && ex->type == VAL_BOOL) || (ey && ey->type == VAL_BOOL)) { - eigs_num_arg_slow((ex && ex->type == VAL_BOOL) ? ex : ey, "nearest_in_range"); + eigs_float_arg((ex && ex->type == VAL_BOOL) ? ex : ey, "nearest_in_range"); return make_null(); } - if (!ex || !ey || ex->type != VAL_NUM || ey->type != VAL_NUM) continue; + if (!ex || !ey || !val_is_number(ex) || !val_is_number(ey)) continue; - double dx = eigs_num_arg(ex, __func__) - px; - double dy = eigs_num_arg(ey, __func__) - py; + double dx = eigs_float_arg(ex, __func__) - px; + double dy = eigs_float_arg(ey, __func__) - py; + if (g_has_error) return make_null(); double hw = ww * 0.5, hh = wh * 0.5; if (dx > hw) dx -= ww; else if (dx < -hw) dx += ww; if (dy > hh) dy -= wh; else if (dy < -hh) dy += wh; @@ -5646,10 +5841,10 @@ Value* builtin_nearest_in_range(Value *arg) { } if (best_idx < 0) return make_null(); Value *result = make_dict(8); - dict_set_owned(result, "index", make_num(best_idx)); - dict_set_owned(result, "dist", make_num(sqrt(best_sq))); - dict_set_owned(result, "dx", make_num(best_dx)); - dict_set_owned(result, "dy", make_num(best_dy)); + dict_set_owned(result, "index", make_int(best_idx)); + dict_set_owned(result, "dist", make_float(sqrt(best_sq))); + dict_set_owned(result, "dx", make_float(best_dx)); + dict_set_owned(result, "dy", make_float(best_dy)); return result; } @@ -5674,9 +5869,9 @@ Value* builtin_nearest_in_range_all(Value *arg) { Value *entities = BUILTIN_LIST_VIEW(arg, 0); if (!entities || entities->type != VAL_LIST) return make_null(); BOOL_REFUSE(arg, "nearest_in_range_all"); /* range, world: numbers */ - double range = eigs_list_num(arg, 1, __func__); - double ww = eigs_list_num(arg, 2, __func__); - double wh = eigs_list_num(arg, 3, __func__); + double range = eigs_list_float(arg, 1, __func__); + double ww = eigs_list_float(arg, 2, __func__); + double wh = eigs_list_float(arg, 3, __func__); if (g_has_error) return make_null(); /* #1637: a non-number slot raised */ const char *px_key = "px", *py_key = "py", *active_key = "active"; if (arg->data.list.count >= 5 && BUILTIN_LIST_VIEW(arg, 4)->type == VAL_STR) @@ -5731,7 +5926,7 @@ Value* builtin_nearest_in_range_all(Value *arg) { } /* active default: 1 (matches single-call behavior where missing/non-num * is treated as active). Explicit 0/non-1 value flips to inactive. */ - active_arr[i] = (av && ((av->type == VAL_NUM && eigs_num_arg(av, __func__) != 1.0) + active_arr[i] = (av && ((val_is_number(av) && eigs_float_arg(av, __func__) != 1.0) || (av->type == VAL_BOOL && !av->data.boolean))) ? 0 : 1; /* #1637 */ Value *ex; @@ -5751,15 +5946,16 @@ Value* builtin_nearest_in_range_all(Value *arg) { ey = (idx >= 0) ? slot_view(dict_value_slot(e, idx), &ey_view) : NULL; } if ((ex && ex->type == VAL_BOOL) || (ey && ey->type == VAL_BOOL)) { /* #1637 round 4 */ - eigs_num_arg_slow((ex && ex->type == VAL_BOOL) ? ex : ey, "nearest_in_range_all"); + eigs_float_arg((ex && ex->type == VAL_BOOL) ? ex : ey, "nearest_in_range_all"); break; } - if (!ex || !ey || ex->type != VAL_NUM || ey->type != VAL_NUM) { + if (!ex || !ey || !val_is_number(ex) || !val_is_number(ey)) { valid_arr[i] = 0; continue; } - px_arr[i] = eigs_num_arg(ex, __func__); - py_arr[i] = eigs_num_arg(ey, __func__); + px_arr[i] = eigs_float_arg(ex, __func__); + py_arr[i] = eigs_float_arg(ey, __func__); + if (g_has_error) break; valid_arr[i] = 1; } if (g_has_error) { /* #1637 round 4: a bool coordinate raised above */ @@ -5800,10 +5996,10 @@ Value* builtin_nearest_in_range_all(Value *arg) { list_append_owned(result, make_null()); } else { Value *r = make_dict(8); - dict_set_owned(r, "index", make_num(best_idx)); - dict_set_owned(r, "dist", make_num(sqrt(best_sq))); - dict_set_owned(r, "dx", make_num(best_dx)); - dict_set_owned(r, "dy", make_num(best_dy)); + dict_set_owned(r, "index", make_int(best_idx)); + dict_set_owned(r, "dist", make_float(sqrt(best_sq))); + dict_set_owned(r, "dx", make_float(best_dx)); + dict_set_owned(r, "dy", make_float(best_dy)); list_append_owned(result, r); /* adopt the freshly-built dict */ } } @@ -5826,24 +6022,22 @@ Value* builtin_nearest_in_range_all(Value *arg) { * E.g. sign_extend of [0xFF, 8] → -1 */ Value* builtin_sign_extend(Value *arg) { STRICT_LIST_MAX(arg, 2, "sign_extend"); - ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "sign_extend", "[val, bits]", make_num(0)); - /* The ELEMENT type check below is new, and it is the one place in this - * change where the non-strict result is not byte-identical to before. - * It was not fail-softness — it was a union type-pun: both reads below - * took `eigs_num_arg(&(), __func__)` off items that were never checked, so - * `sign_extend of ["x", 8]` reinterpreted a `char *` as a `double` and - * sign-extended whatever that produced. There is no previous behaviour - * to preserve, because the previous behaviour was undefined; 0 is the - * same stand-in every sibling here uses. Filed as its own issue. */ + ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "sign_extend", "[val, bits]", make_float(0)); Value *v0 = BUILTIN_LIST_VIEW(arg, 0), *v1 = BUILTIN_LIST_VIEW(arg, 1); - ARG_GUARD(!v0 || v0->type != VAL_NUM || !v1 || v1->type != VAL_NUM, "sign_extend", "two numbers", make_num(0)); - double val = eigs_num_arg(v0, __func__); - int bits = (int)eigs_num_arg(v1, __func__); - if (bits <= 0 || bits > 32) return make_num(val); - int64_t mask = 1LL << (bits - 1); - if ((int64_t)val & mask) - return make_num((double)((int64_t)val - (1LL << bits))); - return make_num(val); + ARG_GUARD(!v0 || !val_is_number(v0) || !v1 || !val_is_number(v1), "sign_extend", "two numbers", make_float(0)); + int64_t val = eigs_list_int(arg, 0, "sign_extend"); + int64_t bits = eigs_list_int(arg, 1, "sign_extend"); + if (g_has_error) return make_null(); + if (bits <= 0 || bits > 32) return make_int(val); + if ((uint64_t)val & (UINT64_C(1) << (bits - 1))) { + int64_t result; + if (__builtin_sub_overflow(val, INT64_C(1) << bits, &result)) { + rt_error(EK_VALUE, 0, "sign_extend: int64 overflow"); + return make_null(); + } + return make_int(result); + } + return make_int(val); } /* list_truncate of [list, new_len] — shrink list in-place to new_len items. @@ -5860,11 +6054,12 @@ Value* builtin_list_truncate(Value *arg) { rt_error(EK_TYPE, 0, "list_truncate: first argument must be a list"); return make_null(); } - if (!len_val || len_val->type != VAL_NUM) { + if (!len_val || !val_is_number(len_val)) { rt_error(EK_TYPE, 0, "list_truncate: new_len must be a number"); return make_null(); } - int new_len = (int)eigs_num_arg(len_val, __func__); + int new_len = builtin_c_int(len_val, "list_truncate"); + if (g_has_error) return make_null(); /* #503: a negative new_len used to silently empty the list (an * undocumented soft clamp to 0). Raise instead. */ if (new_len < 0) { @@ -5890,8 +6085,9 @@ Value* builtin_list_remove_at(Value *arg) { if (!list || list->type != VAL_LIST) return make_null(); /* #1637: a non-number index raises under EIGS_STRICT (a bool in every * mode) instead of silently doing nothing. */ - ARG_GUARD(!idx_val || idx_val->type != VAL_NUM, "list_remove_at", "a number index", list); - int idx = (int)eigs_num_arg(idx_val, __func__); + ARG_GUARD(!idx_val || !val_is_number(idx_val), "list_remove_at", "a number index", list); + int idx = builtin_c_int(idx_val, "list_remove_at"); + if (g_has_error) return make_null(); if (idx < 0 || idx >= list->data.list.count) return list; slot_decref(list_slot(list, idx)); int tail = list->data.list.count - idx - 1; @@ -5912,8 +6108,9 @@ Value* builtin_list_insert_at(Value *arg) { if (!list || list->type != VAL_LIST) return make_null(); /* #1637: a non-number index raises under EIGS_STRICT (a bool in every * mode) instead of silently doing nothing. */ - ARG_GUARD(!idx_val || idx_val->type != VAL_NUM, "list_insert_at", "a number index", list); - int idx = (int)eigs_num_arg(idx_val, __func__); + ARG_GUARD(!idx_val || !val_is_number(idx_val), "list_insert_at", "a number index", list); + int idx = builtin_c_int(idx_val, "list_insert_at"); + if (g_has_error) return make_null(); int count = list->data.list.count; if (idx < 0 || idx > count) return list; if (idx == count) { @@ -5944,21 +6141,21 @@ Value* builtin_list_insert_at(Value *arg) { Value* builtin_list_index_of(Value *arg) { STRICT_LIST_MAX(arg, 2, "list_index_of"); ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, - "list_index_of", "[list, value]", make_num(-1)); + "list_index_of", "[list, value]", make_int(-1)); Value *list = list_get_ref(arg, 0); Value *needle = BUILTIN_LIST_VIEW(arg, 1); ARG_GUARD(!list || list->type != VAL_LIST, - "list_index_of", "a list as its first argument", make_num(-1)); + "list_index_of", "a list as its first argument", make_int(-1)); for (int i = 0; i < list->data.list.count; i++) { /* #1637: the `==` comparison, raising on bool vs num like `==`. */ int equal = values_equal_op(BUILTIN_LIST_VIEW(list, i), needle, "list_index_of"); /* #1417: structural buffer comparison can raise on a strict NaN. */ if (g_has_error) return make_null(); - if (equal) return make_num((double)i); + if (equal) return make_int(i); } /* fs:ANSWER the whole list was scanned and nothing equalled the needle — * -1 is list_index_of's documented miss. */ - return make_num(-1); + return make_int(-1); } /* list_contains of [list, value] — true if any element structurally equals @@ -5985,14 +6182,22 @@ Value* builtin_list_contains(Value *arg) { /* sort_by of [list, key_fn] — sort list by numeric keys from key_fn. * Evaluates key_fn once per element, then qsorts by key (ascending). * Stable tiebreak by original index. Returns a NEW sorted list. */ -typedef struct { double key; int index; } SortByPair; +typedef struct { union { double key; int64_t int_key; }; int is_int; int index; } SortByPair; static int sort_by_pair_cmp(const void *a, const void *b) { - double da = ((const SortByPair *)a)->key; - double db = ((const SortByPair *)b)->key; - if (da < db) return -1; - if (da > db) return 1; - return ((const SortByPair *)a)->index - ((const SortByPair *)b)->index; + const SortByPair *pa = a, *pb = b; + int cmp; + if (pa->is_int && pb->is_int) + cmp = (pa->int_key > pb->int_key) - (pa->int_key < pb->int_key); + else if (pa->is_int) + cmp = eigs_cmp_int_float(pa->int_key, pb->key); + else if (pb->is_int) { + cmp = eigs_cmp_int_float(pb->int_key, pa->key); + if (cmp != 2) cmp = -cmp; + } else + cmp = (pa->key > pb->key) - (pa->key < pb->key); + if (cmp != 0 && cmp != 2) return cmp; + return pa->index - pb->index; } Value* builtin_sort_by(Value *arg) { @@ -6021,7 +6226,7 @@ Value* builtin_sort_by(Value *arg) { } /* #501: a non-numeric key used to coerce to 0.0 — a silent wrong * order. Raise instead (mirrors `sort` #368). */ - if (!kv || kv->type != VAL_NUM) { + if (!kv || !val_is_number(kv)) { const char *kt = kv ? val_type_name(kv->type) : "null"; if (kv) val_decref(kv); free(pairs); @@ -6030,7 +6235,9 @@ Value* builtin_sort_by(Value *arg) { "gave %s)", i, kt); return make_null(); } - pairs[i].key = eigs_num_arg(kv, __func__); + pairs[i].is_int = kv->type == VAL_INT; + if (pairs[i].is_int) pairs[i].int_key = VAL_I64_RAW(kv); + else pairs[i].key = eigs_float_arg(kv, __func__); pairs[i].index = i; val_decref(kv); } @@ -6049,8 +6256,20 @@ Value* builtin_sort_by(Value *arg) { * with libc-dependent element order on top (qsort gives no stability * guarantee for all-equal elements). Record sorting is sort_by's job. */ static int sort_cmp_num(const void *a, const void *b) { - double da = eigs_slot_num(*(const EigsSlot *)a, __func__); - double db = eigs_slot_num(*(const EigsSlot *)b, __func__); + EIGS_VIEW(av); EIGS_VIEW(bv); + Value *va = slot_view(*(const EigsSlot *)a, &av); + Value *vb = slot_view(*(const EigsSlot *)b, &bv); + if (va->type == VAL_INT && vb->type == VAL_INT) + return (VAL_I64_RAW(va) > VAL_I64_RAW(vb)) - (VAL_I64_RAW(va) < VAL_I64_RAW(vb)); + if (va->type == VAL_INT) { + int cmp = eigs_cmp_int_float(VAL_I64_RAW(va), eigs_float_arg(vb, __func__)); + return cmp == 2 ? 0 : cmp; + } + if (vb->type == VAL_INT) { + int cmp = eigs_cmp_int_float(VAL_I64_RAW(vb), eigs_float_arg(va, __func__)); + return cmp == 2 ? 0 : -cmp; + } + double da = eigs_float_arg(va, __func__), db = eigs_float_arg(vb, __func__); return (da > db) - (da < db); } @@ -6068,14 +6287,15 @@ Value* builtin_sort(Value *arg) { : VAL_NULL; for (int i = 0; i < arg->data.list.count; i++) { Value *v = BUILTIN_LIST_VIEW(arg, i); - if (!v || v->type != t || (t != VAL_NUM && t != VAL_STR)) { + int numeric = (t == VAL_FLOAT || t == VAL_INT); + if (!v || (numeric ? !val_is_number(v) : t != VAL_STR || v->type != VAL_STR)) { rt_error(EK_TYPE, 0, "sort requires all numbers or all strings " "(element %d is %s); use sort_by for records", i, v ? val_type_name(v->type) : "null"); return make_null(); } } - list_sort(arg, t == VAL_NUM ? sort_cmp_num : sort_cmp_str); + list_sort(arg, (t == VAL_FLOAT || t == VAL_INT) ? sort_cmp_num : sort_cmp_str); return arg; } @@ -6096,24 +6316,25 @@ Value* builtin_dispatch(Value *arg) { /* Key validation mirrors OP_DISPATCH (#353): a non-number key used to * reinterpret whatever union member the value carried as a double. */ - if (!key_v || key_v->type != VAL_NUM) { + if (!key_v || !val_is_number(key_v)) { rt_error(EK_TYPE, 0, "dispatch: key must be a number"); return make_null(); } - double key_d = eigs_num_arg(key_v, __func__); - int key; - /* Same order as native OP_DISPATCH, through the same predicate (vm.h): - * a fractional key is a value error; an integral one outside int range - * is index_range. */ - if (!vm_index_is_int(key_d, &key)) { + EigsNum key_n = eigs_number_arg(key_v, "dispatch"); + if (g_has_error) return make_null(); + int64_t key_i; + if (key_n.is_int) key_i = key_n.value.i64; + else if (!num_to_i64_exact(key_n.value.f64, &key_i)) { rt_error(EK_VALUE, 0, "dispatch key must be an integer, got %g", - key_d); + key_n.value.f64); return make_null(); } - if (key_d < (double)INT_MIN || key_d > (double)INT_MAX) { - rt_error(EK_INDEX, 0, "dispatch key %g out of range", key_d); + /* #1849: preserve the index-range diagnostic after N5's exact read. */ + if (key_i < INT_MIN || key_i > INT_MAX) { + rt_error(EK_INDEX, 0, "dispatch key %lld out of range", (long long)key_i); return make_null(); } + int key = (int)key_i; if (!table || table->type != VAL_LIST) { rt_error(EK_TYPE, 0, "dispatch: table must be a list"); @@ -6131,13 +6352,18 @@ Value* builtin_dispatch(Value *arg) { /* free_val CONSUMES a reference, and fn_arg is a child of our own * arg vector, which still points at it — lend it a ref of our own * making rather than the arg vector's (#720). */ - /* #1665: a numeric fn_arg is a stack view in THIS frame. A builtin - * that returns its argument (`num`) would hand our caller a pointer - * into a dead frame, so a number goes in as a fresh box we own. */ - if (fn_arg && fn_arg->type == VAL_NUM) { - Value *boxed = make_num(eigs_num_arg(fn_arg, __func__)); + /* #1665: an immediate numeric fn_arg is a stack view in THIS frame. + * Lift it before calling a builtin that returns its argument (`num`). + * HEAP numbers stay shared and follow the direct-child borrow rule. */ + if (fn_arg && val_is_number(fn_arg)) { + Value *boxed = list_get_owned(arg, 2); Value *res = eigs_call_builtin(fn->data.builtin, boxed); - if (res == boxed) return boxed; /* our ref transfers */ + if (res == boxed) { + /* A HEAP number can alias our direct child. Return that + * borrow for the outer scan; only fresh boxes transfer. */ + if (boxed == fn_arg) val_decref(boxed); + return res; + } val_decref(boxed); return res ? res : make_null(); } @@ -6211,21 +6437,21 @@ Value* builtin_dispatch(Value *arg) { * the result still respects EigenScript's no-NaN/Inf invariant. */ static Value* builtin_dot(Value *arg) { STRICT_LIST_MAX(arg, 2, "dot"); - ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "dot", "[a, b]", make_num(0)); + ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "dot", "[a, b]", make_float(0)); Value *a = BUILTIN_LIST_VIEW(arg, 0); Value *b = BUILTIN_LIST_VIEW(arg, 1); - ARG_GUARD(!a || !b || a->type != VAL_BUFFER || b->type != VAL_BUFFER, "dot", "two buffers", make_num(0)); + ARG_GUARD(!a || !b || a->type != VAL_BUFFER || b->type != VAL_BUFFER, "dot", "two buffers", make_float(0)); int n = a->data.buffer.count; if (b->data.buffer.count < n) n = b->data.buffer.count; double s = 0.0; for (int i = 0; i < n; i++) { - double av = buffer_read_num(a, i); + double av = buffer_read_f64(a, i); if (g_has_error) return make_null(); - double bv = buffer_read_num(b, i); + double bv = buffer_read_f64(b, i); if (g_has_error) return make_null(); s = num_guard(s + num_guard(av * bv)); } - return make_num(s); + return make_float(s); } #if EIGS_BORROW_GUARD @@ -6246,7 +6472,7 @@ static Value* builtin_dot(Value *arg) { * BA_POS(i) is element i of the argument list (`f of [x, true]` is position * 1; a one-argument builtin given a list sees the list's elements here). * The gate scans the first EIGS_BOOL_GATE_SCAN positions; beyond that the - * builtin's own typed reads (eigs_num_arg family, ARG_GUARD) decide. + * builtin's own typed readers and ARG_GUARD decide. * tests/test_bool_fuzz.sh is the oracle: it puts true and false in every * slot of every builtin and checks this table against its own reviewed * any-value list (tests/bool_fuzz_anyvalue.txt). */ @@ -6259,6 +6485,7 @@ static const struct { const char *name; unsigned mask; const char *why; } k_bool {"write", BA_ANY, "renders any value"}, {"str", BA_ANY, "converts any value to text (\"true\"/\"false\")"}, {"type", BA_ANY, "names any value's type"}, + {"num_type", BA_ANY, "names a numeric kind or returns null for any other value"}, {"json_encode", BA_ANY, "JSON has true/false"}, {"observe", BA_ANY, "the observer measures bools (entropy 0/1)"}, {"classify", BA_ANY, "the observer classifies any value"}, @@ -6375,6 +6602,85 @@ int eigs_bool_gate_slow(BuiltinFn fn, const Value *arg) { void register_builtins(Env *env) { /* ---- Core language builtins (always available) ---- */ + /* #1758 N9: width operations and unsigned bit-pattern utilities. */ + env_set_local_owned(env, "i8_wrapping_add", make_builtin(builtin_i8_wrapping_add)); + env_set_local_owned(env, "i8_wrapping_sub", make_builtin(builtin_i8_wrapping_sub)); + env_set_local_owned(env, "i8_wrapping_mul", make_builtin(builtin_i8_wrapping_mul)); + env_set_local_owned(env, "i8_checked_add", make_builtin(builtin_i8_checked_add)); + env_set_local_owned(env, "i8_checked_sub", make_builtin(builtin_i8_checked_sub)); + env_set_local_owned(env, "i8_checked_mul", make_builtin(builtin_i8_checked_mul)); + env_set_local_owned(env, "i8_saturating_add", make_builtin(builtin_i8_saturating_add)); + env_set_local_owned(env, "i8_saturating_sub", make_builtin(builtin_i8_saturating_sub)); + env_set_local_owned(env, "i8_saturating_mul", make_builtin(builtin_i8_saturating_mul)); + env_set_local_owned(env, "i16_wrapping_add", make_builtin(builtin_i16_wrapping_add)); + env_set_local_owned(env, "i16_wrapping_sub", make_builtin(builtin_i16_wrapping_sub)); + env_set_local_owned(env, "i16_wrapping_mul", make_builtin(builtin_i16_wrapping_mul)); + env_set_local_owned(env, "i16_checked_add", make_builtin(builtin_i16_checked_add)); + env_set_local_owned(env, "i16_checked_sub", make_builtin(builtin_i16_checked_sub)); + env_set_local_owned(env, "i16_checked_mul", make_builtin(builtin_i16_checked_mul)); + env_set_local_owned(env, "i16_saturating_add", make_builtin(builtin_i16_saturating_add)); + env_set_local_owned(env, "i16_saturating_sub", make_builtin(builtin_i16_saturating_sub)); + env_set_local_owned(env, "i16_saturating_mul", make_builtin(builtin_i16_saturating_mul)); + env_set_local_owned(env, "i32_wrapping_add", make_builtin(builtin_i32_wrapping_add)); + env_set_local_owned(env, "i32_wrapping_sub", make_builtin(builtin_i32_wrapping_sub)); + env_set_local_owned(env, "i32_wrapping_mul", make_builtin(builtin_i32_wrapping_mul)); + env_set_local_owned(env, "i32_checked_add", make_builtin(builtin_i32_checked_add)); + env_set_local_owned(env, "i32_checked_sub", make_builtin(builtin_i32_checked_sub)); + env_set_local_owned(env, "i32_checked_mul", make_builtin(builtin_i32_checked_mul)); + env_set_local_owned(env, "i32_saturating_add", make_builtin(builtin_i32_saturating_add)); + env_set_local_owned(env, "i32_saturating_sub", make_builtin(builtin_i32_saturating_sub)); + env_set_local_owned(env, "i32_saturating_mul", make_builtin(builtin_i32_saturating_mul)); + env_set_local_owned(env, "i64_wrapping_add", make_builtin(builtin_i64_wrapping_add)); + env_set_local_owned(env, "i64_wrapping_sub", make_builtin(builtin_i64_wrapping_sub)); + env_set_local_owned(env, "i64_wrapping_mul", make_builtin(builtin_i64_wrapping_mul)); + env_set_local_owned(env, "i64_checked_add", make_builtin(builtin_i64_checked_add)); + env_set_local_owned(env, "i64_checked_sub", make_builtin(builtin_i64_checked_sub)); + env_set_local_owned(env, "i64_checked_mul", make_builtin(builtin_i64_checked_mul)); + env_set_local_owned(env, "i64_saturating_add", make_builtin(builtin_i64_saturating_add)); + env_set_local_owned(env, "i64_saturating_sub", make_builtin(builtin_i64_saturating_sub)); + env_set_local_owned(env, "i64_saturating_mul", make_builtin(builtin_i64_saturating_mul)); + env_set_local_owned(env, "u8_wrapping_add", make_builtin(builtin_u8_wrapping_add)); + env_set_local_owned(env, "u8_wrapping_sub", make_builtin(builtin_u8_wrapping_sub)); + env_set_local_owned(env, "u8_wrapping_mul", make_builtin(builtin_u8_wrapping_mul)); + env_set_local_owned(env, "u8_checked_add", make_builtin(builtin_u8_checked_add)); + env_set_local_owned(env, "u8_checked_sub", make_builtin(builtin_u8_checked_sub)); + env_set_local_owned(env, "u8_checked_mul", make_builtin(builtin_u8_checked_mul)); + env_set_local_owned(env, "u8_saturating_add", make_builtin(builtin_u8_saturating_add)); + env_set_local_owned(env, "u8_saturating_sub", make_builtin(builtin_u8_saturating_sub)); + env_set_local_owned(env, "u8_saturating_mul", make_builtin(builtin_u8_saturating_mul)); + env_set_local_owned(env, "u16_wrapping_add", make_builtin(builtin_u16_wrapping_add)); + env_set_local_owned(env, "u16_wrapping_sub", make_builtin(builtin_u16_wrapping_sub)); + env_set_local_owned(env, "u16_wrapping_mul", make_builtin(builtin_u16_wrapping_mul)); + env_set_local_owned(env, "u16_checked_add", make_builtin(builtin_u16_checked_add)); + env_set_local_owned(env, "u16_checked_sub", make_builtin(builtin_u16_checked_sub)); + env_set_local_owned(env, "u16_checked_mul", make_builtin(builtin_u16_checked_mul)); + env_set_local_owned(env, "u16_saturating_add", make_builtin(builtin_u16_saturating_add)); + env_set_local_owned(env, "u16_saturating_sub", make_builtin(builtin_u16_saturating_sub)); + env_set_local_owned(env, "u16_saturating_mul", make_builtin(builtin_u16_saturating_mul)); + env_set_local_owned(env, "u32_wrapping_add", make_builtin(builtin_u32_wrapping_add)); + env_set_local_owned(env, "u32_wrapping_sub", make_builtin(builtin_u32_wrapping_sub)); + env_set_local_owned(env, "u32_wrapping_mul", make_builtin(builtin_u32_wrapping_mul)); + env_set_local_owned(env, "u32_checked_add", make_builtin(builtin_u32_checked_add)); + env_set_local_owned(env, "u32_checked_sub", make_builtin(builtin_u32_checked_sub)); + env_set_local_owned(env, "u32_checked_mul", make_builtin(builtin_u32_checked_mul)); + env_set_local_owned(env, "u32_saturating_add", make_builtin(builtin_u32_saturating_add)); + env_set_local_owned(env, "u32_saturating_sub", make_builtin(builtin_u32_saturating_sub)); + env_set_local_owned(env, "u32_saturating_mul", make_builtin(builtin_u32_saturating_mul)); + env_set_local_owned(env, "u64_wrapping_add", make_builtin(builtin_u64_wrapping_add)); + env_set_local_owned(env, "u64_wrapping_sub", make_builtin(builtin_u64_wrapping_sub)); + env_set_local_owned(env, "u64_wrapping_mul", make_builtin(builtin_u64_wrapping_mul)); + env_set_local_owned(env, "u64_checked_add", make_builtin(builtin_u64_checked_add)); + env_set_local_owned(env, "u64_checked_sub", make_builtin(builtin_u64_checked_sub)); + env_set_local_owned(env, "u64_checked_mul", make_builtin(builtin_u64_checked_mul)); + env_set_local_owned(env, "u64_saturating_add", make_builtin(builtin_u64_saturating_add)); + env_set_local_owned(env, "u64_saturating_sub", make_builtin(builtin_u64_saturating_sub)); + env_set_local_owned(env, "u64_saturating_mul", make_builtin(builtin_u64_saturating_mul)); + env_set_local_owned(env, "u64_compare", make_builtin(builtin_u64_compare)); + env_set_local_owned(env, "u64_div", make_builtin(builtin_u64_div)); + env_set_local_owned(env, "u64_rem", make_builtin(builtin_u64_rem)); + env_set_local_owned(env, "u64_to_string", make_builtin(builtin_u64_to_string)); + env_set_local_owned(env, "fround", make_builtin(builtin_fround)); + env_set_local_owned(env, "print", make_builtin(builtin_print)); env_set_local_owned(env, "write", make_builtin(builtin_write)); env_set_local_owned(env, "flush", make_builtin(builtin_flush)); @@ -6413,6 +6719,7 @@ void register_builtins(Env *env) { env_set_local_owned(env, "observe", make_builtin(builtin_observe)); env_set_local_owned(env, "classify", make_builtin(builtin_classify)); env_set_local_owned(env, "type", make_builtin(builtin_type)); + env_set_local_owned(env, "num_type", make_builtin(builtin_num_type)); env_set_local_owned(env, "math_flags", make_builtin(builtin_math_flags)); /* #865 */ env_set_local_owned(env, "json_encode", make_builtin(builtin_json_encode)); env_set_local_owned(env, "json_decode", make_builtin(builtin_json_decode)); diff --git a/src/builtins_buf.c b/src/builtins_buf.c index c7b1a33d1..c4eb2ef9c 100644 --- a/src/builtins_buf.c +++ b/src/builtins_buf.c @@ -32,10 +32,12 @@ Value* builtin_buffer(Value *arg) { /* buffer of [rows, cols] -> shaped 2-D buffer (flat double[rows*cols]) */ if (arg && arg->type == VAL_LIST && arg->data.list.count == 2 && - list_elem_type(arg, 0) == VAL_NUM && - list_elem_type(arg, 1) == VAL_NUM) { - int r = (int)eigs_list_num(arg, 0, __func__); - int c = (int)eigs_list_num(arg, 1, __func__); + (list_elem_type(arg, 0) == VAL_FLOAT || list_elem_type(arg, 0) == VAL_INT) && + (list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT)) { + int64_t r64, c64; + if (!eigs_list_int_range(arg, 0, INT_MIN, INT_MAX, &r64, "buffer") || + !eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &c64, "buffer")) return make_null(); + int r = (int)r64, c = (int)c64; if (r < 0) r = 0; if (c < 0) c = 0; long total = (long)r * (long)c; @@ -53,8 +55,12 @@ Value* builtin_buffer(Value *arg) { int count = 0; /* #971 Phase D: a non-number size (or a malformed [rows, cols]) made an * EMPTY buffer — a plausible object with nothing in it. */ - STRICT_REQUIRE(!arg || arg->type != VAL_NUM, "buffer", "a size or [rows, cols]"); - if (arg && arg->type == VAL_NUM) count = (int)eigs_num_arg(arg, __func__); + STRICT_REQUIRE(!arg || !val_is_number(arg), "buffer", "a size or [rows, cols]"); + if (arg && val_is_number(arg)) { + int64_t n; + if (!eigs_int_arg_range(arg, INT_MIN, INT_MAX, &n, "buffer")) return make_null(); + count = (int)n; + } if (count < 0) count = 0; if (count > EIGS_TENSOR_MAX_ELEMENTS) count = EIGS_TENSOR_MAX_ELEMENTS; if (!sandbox_charge((size_t)count * sizeof(double))) return make_null(); /* #292 */ @@ -75,10 +81,12 @@ Value* builtin_reshape(Value *arg) { Value *b = list_get_view(arg, 0, &b_view); if (b->type != VAL_BUFFER) return make_null(); BOOL_REFUSE(arg, "reshape"); /* rows/cols; the buffer is not a bool */ - if (list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM) return make_null(); - int r = (int)eigs_list_num(arg, 1, __func__); - int c = (int)eigs_list_num(arg, 2, __func__); + if ((list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT)) return make_null(); + int64_t r64, c64; + if (!eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &r64, "reshape") || + !eigs_list_int_range(arg, 2, INT_MIN, INT_MAX, &c64, "reshape")) return make_null(); + int r = (int)r64, c = (int)c64; if (r < 0 || c < 0 || (long)r * (long)c != (long)b->data.buffer.count) return make_null(); /* Same buffer chokepoint as buf_from_list — reshape copies the payload. */ if (!sandbox_charge((b->data.buffer.count > 0 ? (size_t)b->data.buffer.count : 1) * sizeof(double))) @@ -102,24 +110,24 @@ Value* builtin_buf_get(Value *arg) { if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) { rt_error(EK_TYPE, 0, "buf_get requires [buffer, index]"); /* fs:CHANNEL the rt_error above already raised */ - return make_num(0); + return make_float(0); } EIGS_VIEW(buf_view); Value *buf = list_get_view(arg, 0, &buf_view); if (!buf || buf->type != VAL_BUFFER) { rt_error(EK_TYPE, 0, "buf_get: first argument must be a buffer"); /* fs:CHANNEL the rt_error above already raised */ - return make_num(0); + return make_float(0); } - int idx = (int)eigs_list_num(arg, 1, __func__); - if (g_has_error) return make_num(0); /* fs:CHANNEL #1637: a non-number index raised */ + int64_t idx = eigs_list_int(arg, 1, "buf_get"); + if (g_has_error) return make_float(0); /* fs:CHANNEL #1637: a non-number index raised */ if (idx < 0 || idx >= buf->data.buffer.count) { - rt_error(EK_INDEX, 0, "buffer index %d out of range (length %d)", - idx, buf->data.buffer.count); + rt_error(EK_INDEX, 0, "buffer index %lld out of range (length %d)", + (long long)idx, buf->data.buffer.count); /* fs:CHANNEL the EK_INDEX rt_error above already raised (#502) */ - return make_num(0); + return make_float(0); } - return make_num(buffer_read_num(buf, idx)); + return make_float(buffer_read_f64(buf, idx)); } /* buf_set of [buf, index, value] — O(1) indexed write */ @@ -138,20 +146,20 @@ Value* builtin_buf_set(Value *arg) { /* #1061: both operands were read through the num union member unchecked * -- a string index or value read garbage bits (the #1007 type-pun class). * Loud, like the `b[i] is v` opcode path. */ - if (list_elem_type(arg, 1) != VAL_NUM) { + if ((list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT)) { rt_error(EK_TYPE, 0, "buf_set: index must be a number, got %s", val_type_name(list_elem_type(arg, 1))); return make_null(); } - if (list_elem_type(arg, 2) != VAL_NUM) { + if ((list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT)) { rt_error(EK_TYPE, 0, "cannot store %s in a buffer (buffers hold numbers)", val_type_name(list_elem_type(arg, 2))); return make_null(); } - int idx = (int)eigs_list_num(arg, 1, __func__); - double val = eigs_list_num(arg, 2, __func__); + int64_t idx = eigs_list_int(arg, 1, "buf_set"); + double val = eigs_list_float(arg, 2, "buf_set"); if (g_has_error) return make_null(); /* #1637: a non-number index raised; nothing written */ if (idx < 0 || idx >= buf->data.buffer.count) { - rt_error(EK_INDEX, 0, "buffer index %d out of range (length %d)", - idx, buf->data.buffer.count); + rt_error(EK_INDEX, 0, "buffer index %lld out of range (length %d)", + (long long)idx, buf->data.buffer.count); return make_null(); } buf->data.buffer.data[idx] = val; @@ -160,8 +168,8 @@ Value* builtin_buf_set(Value *arg) { /* buf_len of buf — return buffer length */ Value* builtin_buf_len(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_BUFFER, "buf_len", "a buffer", make_num(0)); - return make_num(arg->data.buffer.count); + ARG_GUARD(!arg || arg->type != VAL_BUFFER, "buf_len", "a buffer", make_int(0)); + return make_int(arg->data.buffer.count); } /* buf_from_list of list — convert list of numbers to buffer */ @@ -181,8 +189,9 @@ Value* builtin_buf_from_list(Value *arg) { v->data.buffer.data = xcalloc(n > 0 ? n : 1, sizeof(double)); v->refcount = 1; for (int i = 0; i < n; i++) { - if (list_elem_type(arg, i) == VAL_NUM) { - v->data.buffer.data[i] = eigs_slot_num(list_slot(arg, i), __func__); + if ((list_elem_type(arg, i) == VAL_FLOAT || list_elem_type(arg, i) == VAL_INT)) { + v->data.buffer.data[i] = eigs_slot_float(list_slot(arg, i), "buf_from_list"); + if (g_has_error) { val_decref(v); return make_null(); } } else { /* #1061: a non-number element silently stayed 0.0. */ const char *tn = val_type_name(list_elem_type(arg, i)); @@ -203,15 +212,19 @@ static int strict_numeric_byte_list(Value *arg, const char *who, int nul_ends) { EIGS_VIEW(item_view); Value *item = list_get_view(arg, i, &item_view); /* #1637: a bool byte is refused in every strict mode. */ - if (!item || (item->type != VAL_NUM && (g_strict || item->type == VAL_BOOL))) { + if (!item || (!val_is_number(item) && (g_strict || item->type == VAL_BOOL))) { rt_error(EK_TYPE, 0, "%s: expected numeric byte values", who); return 0; } /* EIGS_STRICT=0 legacy: a non-number element (a bool never reaches * here) ends the string as a NUL would -- the documented truncation * the compatibility mode keeps. It is never read as a number. */ - if (nul_ends && (item->type != VAL_NUM - || finite_num_to_byte(eigs_num_arg(item, __func__)) == 0)) break; + if (nul_ends && !val_is_number(item)) break; + if (val_is_number(item)) { + uint8_t byte = eigs_byte_arg(item, who); + if (g_has_error) return 0; + if (nul_ends && byte == 0) break; + } } return 1; } @@ -235,11 +248,13 @@ Value* builtin_str_from_bytes(Value *arg) { char *s = xcalloc((size_t)(n > 0 ? n : 0) + 1, 1); int len = 0; for (int i = 0; i < n; i++) { - double dv = arg->type == VAL_LIST - ? (list_elem_type(arg, i) == VAL_NUM ? eigs_slot_num(list_slot(arg, i), __func__) : 0.0) - : buffer_read_num(arg, i); + uint8_t b = 0; + if (arg->type == VAL_BUFFER) { + if (!buffer_byte_at(arg, i, &b, "str_from_bytes")) { free(s); return make_null(); } + } else if (list_elem_type(arg, i) == VAL_FLOAT || list_elem_type(arg, i) == VAL_INT) { + b = eigs_slot_byte(list_slot(arg, i), "str_from_bytes"); + } if (g_has_error) { free(s); return make_null(); } - int b = finite_num_to_byte(dv); if (b == 0) break; /* C-string terminates at NUL */ s[len++] = (char)b; } @@ -257,14 +272,15 @@ Value* builtin_str_from_bytes(Value *arg) { * explicit shifts, yielding the standard IEEE-754 layout on any platform. */ Value* builtin_f64_to_bytes(Value *arg) { /* #971 Phase D: a non-number encoded as 0.0's eight bytes. */ - STRICT_REQUIRE(!arg || arg->type != VAL_NUM, "f64_to_bytes", "a number"); - double d = (arg && arg->type == VAL_NUM) ? eigs_num_arg(arg, __func__) : 0.0; + STRICT_REQUIRE(!arg || !val_is_number(arg), "f64_to_bytes", "a number"); + double d = (arg && val_is_number(arg)) ? eigs_float_arg(arg, "f64_to_bytes") : 0.0; + if (g_has_error) return make_null(); uint64_t bits; memcpy(&bits, &d, sizeof(bits)); Value *list = make_list(8); for (int i = 0; i < 8; i++) { int shift = 8 * (7 - i); - list_append_owned(list, make_num((double)((bits >> shift) & 0xFFu))); + list_append_owned(list, make_int((int64_t)((bits >> shift) & 0xFFu))); } return list; } @@ -272,16 +288,17 @@ Value* builtin_f64_to_bytes(Value *arg) { /* f64_from_bytes of → the decoded double. * Inverse of f64_to_bytes; reads exactly the first 8 bytes. */ Value* builtin_f64_from_bytes(Value *arg) { - double bytes_in[8] = {0,0,0,0,0,0,0,0}; + uint8_t bytes_in[8] = {0,0,0,0,0,0,0,0}; if (arg && arg->type == VAL_LIST) { int n = arg->data.list.count; if (n < 8) { rt_error(EK_VALUE, 0, "f64_from_bytes: needs 8 bytes, got %d", n); return make_null(); } - for (int i = 0; i < 8; i++) - if (list_elem_type(arg, i) == VAL_NUM) - bytes_in[i] = eigs_slot_num(list_slot(arg, i), __func__); + for (int i = 0; i < 8; i++) { + bytes_in[i] = eigs_slot_elem_byte(list_slot(arg, i), "f64_from_bytes"); + if (g_has_error) return make_null(); + } } else if (arg && arg->type == VAL_BUFFER) { int n = arg->data.buffer.count; if (n < 8) { @@ -289,39 +306,37 @@ Value* builtin_f64_from_bytes(Value *arg) { return make_null(); } for (int i = 0; i < 8; i++) { - bytes_in[i] = buffer_read_num(arg, i); + if (!buffer_byte_at(arg, i, &bytes_in[i], "f64_from_bytes")) return make_null(); if (g_has_error) return make_null(); } } else { - ARG_GUARD(1, "f64_from_bytes", "a list or buffer of 8 byte values", make_num(0)); + ARG_GUARD(1, "f64_from_bytes", "a list or buffer of 8 byte values", make_float(0)); } uint64_t bits = 0; for (int i = 0; i < 8; i++) - bits = (bits << 8) | (uint64_t)finite_num_to_byte(bytes_in[i]); + bits = (bits << 8) | (uint64_t)bytes_in[i]; double d; memcpy(&d, &bits, sizeof(d)); /* #971: eight arbitrary bytes can spell a NaN; collapse (default) or * raise (strict) under this builtin's own name. */ - return make_num(num_guard_named(d, "f64_from_bytes")); + return make_float(num_guard_named(d, "f64_from_bytes")); } /* ---- DEFLATE codec implementation (#684) ---- * Thin wrappers over the system zlib (-lz), gated behind * EIGENSCRIPT_EXT_ZLIB — the same EIGENSCRIPT_EXT_* mechanism the http * variant uses. Default OFF so the minimal build stays zero-dependency; - * compiled without zlib the four names stay registered but raise a + * compiled without zlib the name stays registered but raises a * catchable runtime error, so a script can feature-detect with * try/catch instead of dying on "undefined variable". * * Byte representation mirrors read_bytes/write_bytes exactly: input is - * a list of ints 0-255 (values taken mod 256) or a VAL_BUFFER; output is + * a list of exact bytes 0-255 or a VAL_BUFFER with exact byte cells; output is * always a fresh list of ints 0-255. Strict mode rejects nonnumeric list * elements; compatibility mode retains their numeric-zero conversion. * * The raw codec uses windowBits -15 — the ZIP member format, so - * .xlsx/.ods entries are readable. The wrapped implementation uses windowBits - * 15+32, which auto-detects zlib AND gzip headers — that is what makes - * plain .gz files readable. + * .xlsx/.ods entries are readable. */ #if EIGENSCRIPT_EXT_ZLIB @@ -332,7 +347,7 @@ Value* builtin_f64_from_bytes(Value *arg) { * sandbox_run default allocation budget. */ #define EIGS_INFLATE_MAX_OUT ((unsigned long)256 * 1024 * 1024) -/* Shared argument extraction for the four codecs: accept the byte +/* Argument extraction for inflate: accept the byte * representations write_bytes accepts and copy them into a malloc'd * byte array. Returns 1 on success; on a wrong-shape argument raises * `type` and returns 0. */ @@ -354,11 +369,14 @@ static int zlib_bytes_arg(Value *arg, const char *who, if (!strict_numeric_byte_list(arg, who, 0)) return 0; unsigned char *b = xmalloc((size_t)(n > 0 ? n : 1)); for (int i = 0; i < n; i++) { - double dv = arg->type == VAL_LIST - ? (list_elem_type(arg, i) == VAL_NUM ? eigs_slot_num(list_slot(arg, i), __func__) : 0.0) - : buffer_read_num(arg, i); + uint8_t byte = 0; + if (arg->type == VAL_BUFFER) { + if (!buffer_byte_at(arg, i, &byte, who)) { free(b); return 0; } + } else if (list_elem_type(arg, i) == VAL_FLOAT || list_elem_type(arg, i) == VAL_INT) { + byte = eigs_slot_byte(list_slot(arg, i), who); + } if (g_has_error) { free(b); return 0; } - b[i] = finite_num_to_byte(dv); + b[i] = byte; } *out = b; *out_n = (size_t)n; @@ -379,7 +397,7 @@ static Value *zlib_bytes_result(const unsigned char *buf, unsigned long n) { return make_null(); Value *result = make_list((int)n); for (unsigned long i = 0; i < n; i++) - list_append_owned(result, make_num((double)buf[i])); + list_append_owned(result, make_int(buf[i])); return result; } @@ -470,8 +488,7 @@ static Value *zlib_inflate_impl(const char *who, int window_bits, Value *arg) { return result; } -/* Shared compression core (dual of the decompression implementation). The output buffer is - * deflateBound-sized up front, so a single Z_FINISH pass always fits. */ +/* Raw DEFLATE entry point. */ Value* builtin_inflate(Value *arg) { return zlib_inflate_impl("inflate", -15, arg); } #else /* !EIGENSCRIPT_EXT_ZLIB */ @@ -498,11 +515,12 @@ Value* builtin_inflate(Value *arg) { (void)arg; return zlib_unavailable("in * #490-#512 direction (buf_get/buf_set/set_at) — no silent truncation: * a clamped audio mix is a silently wrong render. */ static int buf_count_arg(const char *who, EigsSlot cnt, long long *out) { - if (slot_type(cnt) != VAL_NUM) { + if ((slot_type(cnt) != VAL_FLOAT && slot_type(cnt) != VAL_INT)) { rt_error(EK_VALUE, 0, "%s: count must be a number", who); return 0; } - long long c = (long long)eigs_slot_num(cnt, __func__); + long long c = eigs_slot_int(cnt, who); + if (g_has_error) return 0; if (c < 0) { rt_error(EK_VALUE, 0, "%s: count must be non-negative (got %lld)", who, c); @@ -512,14 +530,14 @@ static int buf_count_arg(const char *who, EigsSlot cnt, long long *out) { return 1; } -static int buf_num_arg(const char *who, const char *what, EigsSlot s, +static int buf_float_arg(const char *who, const char *what, EigsSlot s, double *out) { - if (slot_type(s) != VAL_NUM) { + if ((slot_type(s) != VAL_FLOAT && slot_type(s) != VAL_INT)) { rt_error(EK_VALUE, 0, "%s: %s must be a number", who, what); return 0; } - *out = eigs_slot_num(s, __func__); - return 1; + *out = eigs_slot_float(s, who); + return !g_has_error; } /* Validate one (buffer, offset, count) window. On success writes the @@ -531,16 +549,17 @@ static int buf_window_arg(const char *who, Value *buf, EigsSlot off, rt_error(EK_TYPE, 0, "%s: expected a buffer", who); return 0; } - if (slot_type(off) != VAL_NUM) { + if ((slot_type(off) != VAL_FLOAT && slot_type(off) != VAL_INT)) { rt_error(EK_VALUE, 0, "%s: offset must be a number", who); return 0; } - long long offset = (long long)eigs_slot_num(off, __func__); + long long offset = eigs_slot_int(off, who); + if (g_has_error) return 0; long long n = buf->data.buffer.count; if (offset < 0 || offset > n - count) { rt_error(EK_INDEX, 0, - "%s: window [%lld, %lld) out of range (length %lld)", - who, offset, offset + count, n); + "%s: window offset %lld count %lld out of range (length %lld)", + who, offset, count, n); return 0; } *out_off = offset; @@ -595,7 +614,7 @@ Value* builtin_buf_mix(Value *arg) { if (!buf_count_arg("buf_mix", list_slot(arg, 4), &count) || !buf_window_arg("buf_mix", dst, list_slot(arg, 2), count, &dst_off) || !buf_window_arg("buf_mix", src, list_slot(arg, 3), count, &src_off) || - !buf_num_arg("buf_mix", "gain", list_slot(arg, 5), &gain)) + !buf_float_arg("buf_mix", "gain", list_slot(arg, 5), &gain)) return make_null(); double *dd = &dst->data.buffer.data[dst_off]; double *sd = &src->data.buffer.data[src_off]; @@ -619,7 +638,7 @@ Value* builtin_buf_scale_range(Value *arg) { double gain; if (!buf_count_arg("buf_scale_range", list_slot(arg, 2), &count) || !buf_window_arg("buf_scale_range", buf, list_slot(arg, 1), count, &off) || - !buf_num_arg("buf_scale_range", "gain", list_slot(arg, 3), &gain)) + !buf_float_arg("buf_scale_range", "gain", list_slot(arg, 3), &gain)) return make_null(); double *d = &buf->data.buffer.data[off]; for (long long i = 0; i < count; i++) @@ -642,7 +661,7 @@ Value* builtin_buf_fill(Value *arg) { double val; if (!buf_count_arg("buf_fill", list_slot(arg, 2), &count) || !buf_window_arg("buf_fill", buf, list_slot(arg, 1), count, &off) || - !buf_num_arg("buf_fill", "value", list_slot(arg, 3), &val)) + !buf_float_arg("buf_fill", "value", list_slot(arg, 3), &val)) return make_null(); double *d = &buf->data.buffer.data[off]; for (long long i = 0; i < count; i++) @@ -657,7 +676,7 @@ Value* builtin_buf_peak(Value *arg) { if (!arg || arg->type != VAL_LIST || arg->data.list.count < 3) { rt_error(EK_TYPE, 0, "buf_peak requires [buffer, off, count]"); /* fs:CHANNEL the rt_error above already raised */ - return make_num(0); + return make_float(0); } EIGS_VIEW(buf_view); Value *buf = list_get_view(arg, 0, &buf_view); @@ -665,15 +684,15 @@ Value* builtin_buf_peak(Value *arg) { if (!buf_count_arg("buf_peak", list_slot(arg, 2), &count) || !buf_window_arg("buf_peak", buf, list_slot(arg, 1), count, &off)) /* fs:CHANNEL buf_count_arg/buf_window_arg raise before returning 0 */ - return make_num(0); + return make_float(0); double m = 0.0; for (long long i = 0; i < count; i++) { - double x = buffer_read_num(buf, off + i); + double x = buffer_read_f64(buf, off + i); if (g_has_error) return make_null(); double a = x < 0 ? -x : x; if (a > m) m = a; } - return make_num(m); + return make_float(m); } /* buf_dot of [a, b, a_off, b_off, count] — windowed dot product: @@ -687,7 +706,7 @@ Value* builtin_buf_dot(Value *arg) { if (!arg || arg->type != VAL_LIST || arg->data.list.count < 5) { rt_error(EK_TYPE, 0, "buf_dot requires [a, b, a_off, b_off, count]"); /* fs:CHANNEL the rt_error above already raised */ - return make_num(0); + return make_float(0); } EIGS_VIEW(a_view); Value *a = list_get_view(arg, 0, &a_view); @@ -698,16 +717,16 @@ Value* builtin_buf_dot(Value *arg) { !buf_window_arg("buf_dot", a, list_slot(arg, 2), count, &a_off) || !buf_window_arg("buf_dot", b, list_slot(arg, 3), count, &b_off)) /* fs:CHANNEL buf_count_arg/buf_window_arg raise before returning 0 */ - return make_num(0); + return make_float(0); double s = 0.0; for (long long i = 0; i < count; i++) { - double av = buffer_read_num(a, a_off + i); + double av = buffer_read_f64(a, a_off + i); if (g_has_error) return make_null(); - double bv = buffer_read_num(b, b_off + i); + double bv = buffer_read_f64(b, b_off + i); if (g_has_error) return make_null(); s = num_guard(s + num_guard(av * bv)); } - return make_num(s); + return make_float(s); } /* ---- Bulk PCM16LE codec kernels (#602) ---- @@ -759,10 +778,11 @@ Value* builtin_buf_from_pcm16le(Value *arg) { if (!out) return make_null(); double *od = out->data.buffer.data; for (long long i = 0; i < count; i++) { - double lo = buffer_read_num(src, off + 2*i); - if (g_has_error) { val_decref(out); return make_null(); } - double hi = buffer_read_num(src, off + 2*i + 1); - if (g_has_error) { val_decref(out); return make_null(); } + uint8_t lo, hi; + if (!buffer_byte_at(src, off + 2*i, &lo, "buf_from_pcm16le") || + !buffer_byte_at(src, off + 2*i + 1, &hi, "buf_from_pcm16le")) { + val_decref(out); return make_null(); + } double v = num_guard(lo + num_guard(256.0 * hi)); if (g_has_error) { val_decref(out); return make_null(); } if (v >= 32768.0) v = num_guard(v - 65536.0); @@ -799,7 +819,7 @@ Value* builtin_buf_to_pcm16le(Value *arg) { if (!out) return make_null(); double *od = out->data.buffer.data; for (long long i = 0; i < count; i++) { - double x = buffer_read_num(src, off + i); + double x = buffer_read_f64(src, off + i); if (g_has_error) { val_decref(out); return make_null(); } if (x < -1.0) x = -1.0; if (x > 1.0) x = 1.0; @@ -832,12 +852,13 @@ Value* builtin_buf_deinterleave(Value *arg) { Value *ch_v = list_get_view(arg, 1, &ch_v_view); EIGS_VIEW(nch_v_view); Value *nch_v = list_get_view(arg, 2, &nch_v_view); - if (!ch_v || ch_v->type != VAL_NUM || !nch_v || nch_v->type != VAL_NUM) { + if (!ch_v || !val_is_number(ch_v) || !nch_v || !val_is_number(nch_v)) { rt_error(EK_VALUE, 0, "buf_deinterleave: channel and nch must be numbers"); return make_null(); } - long long nch = (long long)eigs_num_arg(nch_v, __func__); - long long channel = (long long)eigs_num_arg(ch_v, __func__); + long long nch = eigs_int_arg(nch_v, "buf_deinterleave"); + long long channel = eigs_int_arg(ch_v, "buf_deinterleave"); + if (g_has_error) return make_null(); if (nch < 1) { rt_error(EK_VALUE, 0, "buf_deinterleave: nch must be >= 1 (got %lld)", nch); return make_null(); @@ -848,7 +869,7 @@ Value* builtin_buf_deinterleave(Value *arg) { return make_null(); } long long n = src->data.buffer.count; - long long avail = channel < n ? (n - channel + nch - 1) / nch : 0; + long long avail = channel < n ? 1 + (n - 1 - channel) / nch : 0; long long count = avail; if (arg->data.list.count >= 4 && list_elem_type(arg, 3) != VAL_NULL) { if (!buf_count_arg("buf_deinterleave", list_slot(arg, 3), &count)) diff --git a/src/builtins_host.c b/src/builtins_host.c index 60ba4a6e9..230aa41a4 100644 --- a/src/builtins_host.c +++ b/src/builtins_host.c @@ -22,6 +22,7 @@ #include "builtins_internal.h" #include "fsutil.h" #include "trace.h" +#include #if EIGENSCRIPT_FREESTANDING @@ -34,6 +35,13 @@ void register_host_builtins(Env *env) { (void)env; } #include +/* N5 followed by the range of the receiving hosted C API. */ +static int builtin_host_c_int(const Value *v, const char *who) { + int64_t n = 0; + eigs_int_arg_range(v, INT_MIN, INT_MAX, &n, who); + return (int)n; +} + /* ==== BUILTIN: match — regex match, return list of groups ==== */ /* match of [string, pattern] -> [full_match, group1, ...] or [] */ Value* builtin_match(Value *arg) { @@ -215,14 +223,14 @@ Value* builtin_stream_open(Value *arg) { Value *path_val = list_get_view(arg, 0, &path_val_view); EIGS_VIEW(count_val_view); Value *count_val = list_get_view(arg, 1, &count_val_view); - ARG_GUARD(!path_val || path_val->type != VAL_STR || !count_val || count_val->type != VAL_NUM, + ARG_GUARD(!path_val || path_val->type != VAL_STR || !count_val || !val_is_number(count_val), "stream_open", "[a string path, a number count]", make_bool(0)); - if (eigs_num_arg(count_val, __func__) < 1 || - eigs_num_arg(count_val, __func__) > EIGS_TENSOR_MAX_ELEMENTS || - eigs_num_arg(count_val, __func__) != floor(eigs_num_arg(count_val, __func__))) { + int64_t requested_count = eigs_int_arg(count_val, "stream_open"); + if (g_has_error) return make_bool(0); + if (requested_count < 1 || requested_count > EIGS_TENSOR_MAX_ELEMENTS) { rt_error(EK_LIMIT, 0, - "stream_open: '%s' count %.17g is outside the 1..%d element cap", - path_val->data.str, eigs_num_arg(count_val, __func__), + "stream_open: '%s' count %lld is outside the 1..%d element cap", + path_val->data.str, (long long)requested_count, EIGS_TENSOR_MAX_ELEMENTS); return make_bool(0); } @@ -238,7 +246,7 @@ Value* builtin_stream_open(Value *arg) { * comment over this section documents stream_open as answering 1 — so 0 * is the "could not open" answer, not a type mistake. */ if (!g_stream_file) return make_bool(0); - uint32_t count = (uint32_t)eigs_num_arg(count_val, __func__); + uint32_t count = (uint32_t)requested_count; uint32_t header[4] = { 1, 1, count, 0 }; /* ndim=1, rows=1, cols=count, flags=0 */ if (fwrite(header, sizeof(uint32_t), 4, g_stream_file) != 4) { fclose(g_stream_file); @@ -262,12 +270,13 @@ Value* builtin_stream_write(Value *arg) { * state, and strict must not report it as a type error. Both halves still * answer make_bool(0) with strict off, in either order, so the non-strict * result is unchanged. */ - ARG_GUARD(!arg || arg->type != VAL_NUM, "stream_write", "a number", make_bool(0)); + ARG_GUARD(!arg || !val_is_number(arg), "stream_write", "a number", make_bool(0)); /* fs:ANSWER no stream is open — the state, not the argument (which the * guard above already accepted); 0 is the failure bit paired with the * make_bool(1) success value below. */ if (!g_stream_file) return make_bool(0); - double val = eigs_num_arg(arg, __func__); + double val = eigs_float_arg(arg, "stream_write"); + if (g_has_error) return make_bool(0); if (fwrite(&val, sizeof(double), 1, g_stream_file) != 1) { fclose(g_stream_file); g_stream_file = NULL; @@ -416,11 +425,27 @@ typedef struct { int count; } IdentEntry; +/* Both refusal sites belong only to build_corpus. Numeric preflight may + * precede replay, while malformed nonnumeric shapes retain replay's refusal. */ +static int build_corpus_replay_blocks(void) { + if (!__builtin_expect(g_replay_enabled, 0)) return 0; + rt_error(EK_IO, 0, "build_corpus: not replayable under EIGS_REPLAY " + "(filesystem boundary; see docs/TRACE.md)"); + return 1; +} + Value* builtin_build_corpus(Value *arg) { STRICT_LIST_MAX(arg, 7, "build_corpus"); - if (__builtin_expect(g_replay_enabled, 0)) { - rt_error(EK_IO, 0, "build_corpus: not replayable under EIGS_REPLAY " - "(filesystem boundary; see docs/TRACE.md)"); + if (__builtin_expect(g_replay_enabled, 0) && + (!arg || arg->type != VAL_LIST || arg->data.list.count < 4 || + list_elem_type(arg, 0) != VAL_LIST || + (list_elem_type(arg, 1) != VAL_INT && list_elem_type(arg, 1) != VAL_FLOAT) || + list_elem_type(arg, 2) != VAL_STR || list_elem_type(arg, 3) != VAL_STR || + (arg->data.list.count >= 6 && list_elem_type(arg, 5) != VAL_NULL && + list_elem_type(arg, 5) != VAL_INT && list_elem_type(arg, 5) != VAL_FLOAT) || + (arg->data.list.count >= 7 && list_elem_type(arg, 6) != VAL_NULL && + list_elem_type(arg, 6) != VAL_INT && list_elem_type(arg, 6) != VAL_FLOAT))) { + (void)build_corpus_replay_blocks(); return make_null(); } if (!arg || arg->type != VAL_LIST || arg->data.list.count < 4) @@ -438,11 +463,12 @@ Value* builtin_build_corpus(Value *arg) { if (!file_list || file_list->type != VAL_LIST) return make_null(); BOOL_REFUSE(topn_val, "build_corpus"); - if (!topn_val || topn_val->type != VAL_NUM) return make_null(); + if (!topn_val || !val_is_number(topn_val)) return make_null(); if (!stream_path_val || stream_path_val->type != VAL_STR) return make_null(); if (!vocab_path_val || vocab_path_val->type != VAL_STR) return make_null(); - int top_n = (int)eigs_num_arg(topn_val, __func__); + int top_n = builtin_host_c_int(topn_val, "build_corpus"); + if (g_has_error) return make_null(); int n_files = file_list->data.list.count; /* ---- SLOT MODE (optional 6th arg: slot_count > 0) -------------------- @@ -473,8 +499,8 @@ Value* builtin_build_corpus(Value *arg) { if (arg->data.list.count >= 6) { EIGS_VIEW(sv_view); Value *sv = list_get_view(arg, 5, &sv_view); - double d = 0; - if (eigs_opt_num(sv, &d, "build_corpus")) slot_count = (int)d; /* #1637: a bool raises */ + int64_t d = 0; + if (eigs_opt_int_range(sv, INT_MIN, INT_MAX, &d, "build_corpus")) slot_count = (int)d; if (g_has_error) return make_null(); } if (slot_count < 0) slot_count = 0; @@ -502,11 +528,12 @@ Value* builtin_build_corpus(Value *arg) { if (arg->data.list.count >= 7) { EIGS_VIEW(iv_view); Value *iv = list_get_view(arg, 6, &iv_view); - double d = 0; - if (eigs_opt_num(iv, &d, "build_corpus")) int_count = (int)d; /* #1637: a bool raises */ + int64_t d = 0; + if (eigs_opt_int_range(iv, INT_MIN, INT_MAX, &d, "build_corpus")) int_count = (int)d; if (g_has_error) return make_null(); } if (int_count < 0) int_count = 0; + if (build_corpus_replay_blocks()) return make_null(); /* Source of truth: the size of the TokType enum. Hardcoding 54 (which is * what this was before) silently aliases TOK_LBRACKET (=54) onto the most * common extended ident slot whenever the enum grows. */ @@ -728,13 +755,13 @@ Value* builtin_build_corpus(Value *arg) { tid = FIRST_IDENT + actual_top + hit; } } else if (int_count > 0 && tid == TOK_NUM) { - /* Give small non-negative integer literals exact tokens; the - * region sits directly after the slots. floor==value rejects - * 3.5 etc.; the lexer emits '-' as its own token so num_val is - * never negative for a literal, but guard anyway. */ - double nv = tl.tokens[i].num_val; - if (nv >= 0.0 && nv < (double)int_count && nv == (double)(long)nv) - tid = FIRST_IDENT + actual_top + slot_count + (int)nv; + /* Only int literals receive the compact integer token. + * Preserve literal kinds: `3.0` remains TOK_NUM. Hex literals + * can wrap negative, so check the range before the cast. */ + EigsNum number = tl.tokens[i].number; + if (number.is_int && number.value.i64 >= 0 && + number.value.i64 < int_count) + tid = FIRST_IDENT + actual_top + slot_count + (int)number.value.i64; } double d = (double)tid; fwrite(&d, sizeof(double), 1, stream_file); @@ -833,9 +860,9 @@ Value* builtin_build_corpus(Value *arg) { /* Return [stream_length, distinct_identifiers, files_found] */ Value *result = make_list(3); - list_append_owned(result, make_num(stream_pos)); - list_append_owned(result, make_num(n_idents)); - list_append_owned(result, make_num(files_found)); + list_append_owned(result, make_int(stream_pos)); + list_append_owned(result, make_int(n_idents)); + list_append_owned(result, make_int(files_found)); return result; } @@ -1196,18 +1223,16 @@ static const char *reader_cap_parse(const char *fn, Value *arg, Value **path, if (arg->data.list.count >= 2) { EIGS_VIEW(mv_view); Value *mv = list_get_view(arg, 1, &mv_view); - if (!mv || mv->type != VAL_NUM) { + if (!mv || !val_is_number(mv)) { return "max_bytes to be a number"; } - double n = eigs_num_arg(mv, __func__); - int64_t exact; - if (isfinite(n) && num_to_i64_exact(n, &exact) && exact < 1) - return "max_bytes to be a positive number"; - if (!isfinite(n) || !num_to_i64_exact(n, &exact) || - exact > READ_BYTES_BUF_HARD_CAP) { + int64_t exact = eigs_int_arg(mv, fn); + if (g_has_error) return NULL; + if (exact < 1) return "max_bytes to be a positive number"; + if (exact > READ_BYTES_BUF_HARD_CAP) { rt_error(EK_VALUE, 0, - "%s: max_bytes must be an exact integer in 1..%lld (got %g)", - fn, READ_BYTES_BUF_HARD_CAP, n); + "%s: max_bytes must be an exact integer in 1..%lld (got %lld)", + fn, READ_BYTES_BUF_HARD_CAP, (long long)exact); return NULL; } *cap = (long long)exact; @@ -1223,7 +1248,7 @@ static const char *reader_cap_parse(const char *fn, Value *arg, Value **path, static void reader_cap_raise(const char *fn, const char *path, long long len, long long cap) { if (__builtin_expect(g_trace_enabled, 0)) { - Value *sz = make_num((double)len); + Value *sz = make_int((int64_t)len); trace_nondet_value(fn, sz); val_decref(sz); } @@ -1273,15 +1298,15 @@ static int read_stream_bounded(FILE *f, size_t cap, unsigned char **out, } static int replay_reader_size(const char *fn, Value *v, long long *out) { - double n = eigs_num_arg(v, __func__); - int64_t exact; - if (!isfinite(n) || n < 0.0 || !num_to_i64_exact(n, &exact)) { + /* v8 records sizes as exact ints. A float-kind token, even an integral + * one, is corrupt metadata; never round it through the numeric bridge. */ + if (!v || v->type != VAL_INT || VAL_I64_RAW(v) < 0) { rt_error(EK_IO, 0, "%s: replay record size is not an exact integer in range; " "refusing to replay", fn); return 0; } - *out = (long long)exact; + *out = (long long)VAL_I64_RAW(v); return 1; } @@ -1296,7 +1321,7 @@ Value* builtin_read_bytes(Value *arg) { ARG_GUARD_PRETAKE(*guard, "read_bytes", guard, make_null()); Value *replayed = NULL; if (file_io_replay("read_bytes", &replayed)) { - if (replayed->type == VAL_NUM) { + if (replayed->type == VAL_INT || replayed->type == VAL_FLOAT) { long long len; if (!replay_reader_size("read_bytes", replayed, &len)) { val_decref(replayed); @@ -1334,7 +1359,7 @@ Value* builtin_read_bytes(Value *arg) { } Value *result = make_list((int)nread); for (size_t i = 0; i < nread; i++) - list_append_owned(result, make_num((double)buf[i])); + list_append_owned(result, make_int(buf[i])); free(buf); TRACE_NONDET_RECORD("read_bytes", result); } @@ -1349,7 +1374,7 @@ Value* builtin_read_bytes(Value *arg) { fclose(f); Value *result = make_list((int)nread); for (size_t i = 0; i < nread; i++) - list_append_owned(result, make_num((double)buf[i])); + list_append_owned(result, make_int(buf[i])); free(buf); TRACE_NONDET_RECORD("read_bytes", result); } @@ -1382,12 +1407,12 @@ Value* builtin_read_bytes_buf(Value *arg) { /* Tape boundary. Success records the VAL_BUFFER; unopenable file * records an impossible-success error list. A regular-file over-cap - * raise records its known size as a VAL_NUM; an unseekable stream records + * raise records its known size as a VAL_INT; an unseekable stream records * its non-exact error message. Both replay without live filesystem I/O. */ if (__builtin_expect(g_replay_enabled, 0)) { Value *tv = NULL; if (file_io_replay("read_bytes_buf", &tv)) { - if (tv && tv->type == VAL_NUM) { + if (tv && (tv->type == VAL_INT || tv->type == VAL_FLOAT)) { long long len; if (!replay_reader_size("read_bytes_buf", tv, &len)) { val_decref(tv); @@ -1462,7 +1487,7 @@ Value* builtin_read_text(Value *arg) { ARG_GUARD_PRETAKE(*guard, "read_text", guard, make_str("")); Value *replayed = NULL; if (file_io_replay("read_text", &replayed)) { - if (replayed->type == VAL_NUM) { + if (replayed->type == VAL_INT || replayed->type == VAL_FLOAT) { long long len; if (!replay_reader_size("read_text", replayed, &len)) { val_decref(replayed); @@ -1592,7 +1617,7 @@ Value* builtin_write_text(Value *arg) { static Value* exec_capture_result(int code, const char *text) { Value *result = make_list(2); - list_append_num(result, code); + list_append_owned(result, make_int(code)); list_append_owned(result, make_str(text)); return result; } @@ -1600,7 +1625,7 @@ static Value* exec_capture_result(int code, const char *text) { Value* builtin_exec_capture(Value *arg) { /* fs:CHANNEL replay refuses the exec; the refusal belongs to the replay * layer and [-1, ""] is exec_capture's documented "did not run". */ - /* #1008: these launder through a HELPER, so no `return make_num(-1)` + /* #1008: these launder through a HELPER, so no `return make_float(-1)` * appears and the classifier's matcher — which enumerates literal soft * returns — cannot see them at all. ARG_GUARD's soft argument is * evaluated only on the non-strict path, so the helper call is fine @@ -1612,19 +1637,28 @@ Value* builtin_exec_capture(Value *arg) { * layer and [-1, ""] is exec_capture's documented "did not run". #1072: * after the argument guard, so a wrong-typed call errs the same way in * both modes. */ - if (replay_blocks("exec_capture", "subprocess/concurrency")) return exec_capture_result(-1, ""); /* Detect timeout form: [["cmd", ...], timeout_num] */ - double timeout_sec = -1; + int64_t timeout_ms = -1; Value *cmd_list = arg; if (arg->data.list.count == 2 && list_elem_type(arg, 0) == VAL_LIST - && list_elem_type(arg, 1) == VAL_NUM) { + && (list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT)) { cmd_list = list_get_ref(arg, 0); - timeout_sec = eigs_list_num(arg, 1, __func__); - ARG_GUARD(cmd_list->data.list.count < 1, - "exec_capture", "a non-empty command list", exec_capture_result(-1, "")); + double seconds = eigs_list_float(arg, 1, "exec_capture"); + if (g_has_error) return make_null(); + /* The receiving API is poll(2), whose timeout is C int milliseconds. + * Own the scaled temporary until the borrowing magnitude reader has + * finished, including on its raise path. */ + Value *milliseconds = make_float(seconds * 1000.0); + int ok = eigs_magnitude_arg(milliseconds, INT_MIN, INT_MAX, &timeout_ms, + "exec_capture"); + val_decref(milliseconds); + if (!ok || g_has_error) return make_null(); } + if (replay_blocks("exec_capture", "subprocess/concurrency")) return exec_capture_result(-1, ""); + ARG_GUARD(cmd_list->data.list.count < 1, + "exec_capture", "a non-empty command list", exec_capture_result(-1, "")); int total = cmd_list->data.list.count; @@ -1686,11 +1720,11 @@ Value* builtin_exec_capture(Value *arg) { /* Compute deadline */ struct timespec deadline; - int has_timeout = (timeout_sec >= 0); + int has_timeout = (timeout_ms >= 0); if (has_timeout) { clock_gettime(CLOCK_MONOTONIC, &deadline); - deadline.tv_sec += (time_t)timeout_sec; - deadline.tv_nsec += (long)((timeout_sec - (time_t)timeout_sec) * 1e9); + deadline.tv_sec += (time_t)(timeout_ms / 1000); + deadline.tv_nsec += (long)(timeout_ms % 1000) * 1000000L; if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec++; deadline.tv_nsec -= 1000000000L; @@ -1765,8 +1799,9 @@ Value* builtin_exec_capture(Value *arg) { Value* builtin_random_hex(Value *arg) { /* #971 Phase D: a non-number length answered "" (as does a length of 0, * which stays the answer). Taped: the soft half records as before. */ - ARG_GUARD_TAPED(!arg || arg->type != VAL_NUM, "random_hex", "a number of hex digits", make_str("")); - int n = (arg && arg->type == VAL_NUM) ? (int)eigs_num_arg(arg, __func__) : 0; + ARG_GUARD_TAPED(!arg || !val_is_number(arg), "random_hex", "a number of hex digits", make_str("")); + int n = builtin_host_c_int(arg, "random_hex"); + if (g_has_error) return make_null(); if (n <= 0 || n > 256) TRACE_NONDET_RET("random_hex", make_str("")); int bytes_needed = (n + 1) / 2; unsigned char raw[128]; @@ -1783,7 +1818,7 @@ Value* builtin_random_hex(Value *arg) { } /* write_bytes of [path, data, append?] — write raw bytes to a file. - * `data` is a list of byte ints or a buffer (values taken mod 256). `append` + * `data` is a list or buffer of exact bytes in 0..255 (N5/Q1). `append` * (optional, default 0): 0 truncates the file, nonzero appends. Returns the * number of bytes written; I/O failure raises EK_IO. Unlike write_text this is * binary-clean — NUL bytes are written verbatim, not treated as terminators — @@ -1792,13 +1827,13 @@ Value* builtin_random_hex(Value *arg) { Value* builtin_write_bytes(Value *arg) { STRICT_LIST_MAX(arg, 3, "write_bytes"); ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, - "write_bytes", "[path, data] (optionally [path, data, append])", make_num(0)); + "write_bytes", "[path, data] (optionally [path, data, append])", make_int(0)); EIGS_VIEW(path_val_view); Value *path_val = list_get_view(arg, 0, &path_val_view); EIGS_VIEW(data_view); Value *data = list_get_view(arg, 1, &data_view); ARG_GUARD(!path_val || path_val->type != VAL_STR, - "write_bytes", "a string path as its first argument", make_num(0)); + "write_bytes", "a string path as its first argument", make_int(0)); /* #1637: append is a flag -- a bool, or the older nonzero number. Any * other value raises rather than silently meaning "truncate". */ int append = 0; @@ -1806,8 +1841,8 @@ Value* builtin_write_bytes(Value *arg) { EIGS_VIEW(fl_view); Value *fl = list_get_view(arg, 2, &fl_view); if (fl->type == VAL_BOOL) append = fl->data.boolean; - else if (fl->type != VAL_NULL) append = (eigs_num_arg(fl, "write_bytes") != 0.0); - if (g_has_error) return make_num(0); + else if (fl->type != VAL_NULL) append = (eigs_float_arg(fl, "write_bytes") != 0.0); + if (g_has_error) return make_int(0); } int n = 0; @@ -1820,7 +1855,7 @@ Value* builtin_write_bytes(Value *arg) { * byte ints nor a buffer, the only two forms the header documents. */ ARG_GUARD(1, "write_bytes", "a list of byte values or a buffer as its second argument", - make_num(0)); + make_int(0)); } unsigned char *out = xmalloc((size_t)(n > 0 ? n : 1)); @@ -1830,17 +1865,17 @@ Value* builtin_write_bytes(Value *arg) { * two zero bytes and reported success. Same class as join/str_replace * — no stand-in return, so invisible to the classifier. The free() * matters: STRICT_REQUIRE returns, and `out` is already allocated. */ - if (data->type == VAL_LIST && list_elem_type(data, i) != VAL_NUM + if (data->type == VAL_LIST && (list_elem_type(data, i) != VAL_FLOAT && list_elem_type(data, i) != VAL_INT) && (g_strict || list_elem_type(data, i) == VAL_BOOL)) { /* #1637: a bool in every mode */ free(out); rt_error(EK_TYPE, 0, "write_bytes: expected every element to be a number"); return make_null(); } - double dv = data->type == VAL_LIST - ? eigs_slot_elem_num(list_slot(data, i), __func__) - : buffer_read_num(data, i); + if (data->type == VAL_LIST) + out[i] = eigs_slot_elem_byte(list_slot(data, i), "write_bytes"); + else + buffer_byte_at(data, i, &out[i], "write_bytes"); if (g_has_error) { free(out); return make_null(); } - out[i] = finite_num_to_byte(dv); } Value *replayed = NULL; if (file_io_replay("write_bytes", &replayed)) { free(out); return replayed; } @@ -1861,7 +1896,7 @@ Value* builtin_write_bytes(Value *arg) { if (!close_ok || written != (size_t)n) return file_io_error("write_bytes", path_val->data.str, write_err ? write_err : (close_err ? close_err : EIO)); - TRACE_NONDET_RECORD("write_bytes", make_num((double)written)); + TRACE_NONDET_RECORD("write_bytes", make_int((int64_t)written)); } void register_host_builtins(Env *env) { diff --git a/src/builtins_int.c b/src/builtins_int.c new file mode 100644 index 000000000..6c301064f --- /dev/null +++ b/src/builtins_int.c @@ -0,0 +1,577 @@ +/* #1758 N9: all width operations share this descriptor table and engine. + * N5/range validation precedes arithmetic; unsigned intermediates avoid UB. */ +#include "eigenscript.h" +#include "container.h" +#include "builtins_internal.h" +#include +#include +#include + +typedef enum { N9_WRAP, N9_CHECK, N9_SAT } N9Mode; +typedef enum { N9_ADD, N9_SUB, N9_MUL } N9Op; +typedef struct { + const char *name; + unsigned width; + int is_signed; + N9Mode mode; + N9Op op; + int64_t lo, hi; +} N9Spec; +static const N9Spec n9_specs[] = { + {"i8_wrapping_add", 8, 1, N9_WRAP, N9_ADD, (-INT64_C(128)), INT64_C(127)}, + {"i8_wrapping_sub", 8, 1, N9_WRAP, N9_SUB, (-INT64_C(128)), INT64_C(127)}, + {"i8_wrapping_mul", 8, 1, N9_WRAP, N9_MUL, (-INT64_C(128)), INT64_C(127)}, + {"i8_checked_add", 8, 1, N9_CHECK, N9_ADD, (-INT64_C(128)), INT64_C(127)}, + {"i8_checked_sub", 8, 1, N9_CHECK, N9_SUB, (-INT64_C(128)), INT64_C(127)}, + {"i8_checked_mul", 8, 1, N9_CHECK, N9_MUL, (-INT64_C(128)), INT64_C(127)}, + {"i8_saturating_add", 8, 1, N9_SAT, N9_ADD, (-INT64_C(128)), INT64_C(127)}, + {"i8_saturating_sub", 8, 1, N9_SAT, N9_SUB, (-INT64_C(128)), INT64_C(127)}, + {"i8_saturating_mul", 8, 1, N9_SAT, N9_MUL, (-INT64_C(128)), INT64_C(127)}, + {"i16_wrapping_add", 16, 1, N9_WRAP, N9_ADD, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_wrapping_sub", 16, 1, N9_WRAP, N9_SUB, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_wrapping_mul", 16, 1, N9_WRAP, N9_MUL, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_checked_add", 16, 1, N9_CHECK, N9_ADD, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_checked_sub", 16, 1, N9_CHECK, N9_SUB, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_checked_mul", 16, 1, N9_CHECK, N9_MUL, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_saturating_add", 16, 1, N9_SAT, N9_ADD, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_saturating_sub", 16, 1, N9_SAT, N9_SUB, (-INT64_C(32768)), INT64_C(32767)}, + {"i16_saturating_mul", 16, 1, N9_SAT, N9_MUL, (-INT64_C(32768)), INT64_C(32767)}, + {"i32_wrapping_add", 32, 1, N9_WRAP, N9_ADD, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_wrapping_sub", 32, 1, N9_WRAP, N9_SUB, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_wrapping_mul", 32, 1, N9_WRAP, N9_MUL, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_checked_add", 32, 1, N9_CHECK, N9_ADD, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_checked_sub", 32, 1, N9_CHECK, N9_SUB, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_checked_mul", 32, 1, N9_CHECK, N9_MUL, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_saturating_add", 32, 1, N9_SAT, N9_ADD, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_saturating_sub", 32, 1, N9_SAT, N9_SUB, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i32_saturating_mul", 32, 1, N9_SAT, N9_MUL, (-INT64_C(2147483648)), INT64_C(2147483647)}, + {"i64_wrapping_add", 64, 1, N9_WRAP, N9_ADD, INT64_MIN, INT64_MAX}, + {"i64_wrapping_sub", 64, 1, N9_WRAP, N9_SUB, INT64_MIN, INT64_MAX}, + {"i64_wrapping_mul", 64, 1, N9_WRAP, N9_MUL, INT64_MIN, INT64_MAX}, + {"i64_checked_add", 64, 1, N9_CHECK, N9_ADD, INT64_MIN, INT64_MAX}, + {"i64_checked_sub", 64, 1, N9_CHECK, N9_SUB, INT64_MIN, INT64_MAX}, + {"i64_checked_mul", 64, 1, N9_CHECK, N9_MUL, INT64_MIN, INT64_MAX}, + {"i64_saturating_add", 64, 1, N9_SAT, N9_ADD, INT64_MIN, INT64_MAX}, + {"i64_saturating_sub", 64, 1, N9_SAT, N9_SUB, INT64_MIN, INT64_MAX}, + {"i64_saturating_mul", 64, 1, N9_SAT, N9_MUL, INT64_MIN, INT64_MAX}, + {"u8_wrapping_add", 8, 0, N9_WRAP, N9_ADD, INT64_C(0), INT64_C(255)}, + {"u8_wrapping_sub", 8, 0, N9_WRAP, N9_SUB, INT64_C(0), INT64_C(255)}, + {"u8_wrapping_mul", 8, 0, N9_WRAP, N9_MUL, INT64_C(0), INT64_C(255)}, + {"u8_checked_add", 8, 0, N9_CHECK, N9_ADD, INT64_C(0), INT64_C(255)}, + {"u8_checked_sub", 8, 0, N9_CHECK, N9_SUB, INT64_C(0), INT64_C(255)}, + {"u8_checked_mul", 8, 0, N9_CHECK, N9_MUL, INT64_C(0), INT64_C(255)}, + {"u8_saturating_add", 8, 0, N9_SAT, N9_ADD, INT64_C(0), INT64_C(255)}, + {"u8_saturating_sub", 8, 0, N9_SAT, N9_SUB, INT64_C(0), INT64_C(255)}, + {"u8_saturating_mul", 8, 0, N9_SAT, N9_MUL, INT64_C(0), INT64_C(255)}, + {"u16_wrapping_add", 16, 0, N9_WRAP, N9_ADD, INT64_C(0), INT64_C(65535)}, + {"u16_wrapping_sub", 16, 0, N9_WRAP, N9_SUB, INT64_C(0), INT64_C(65535)}, + {"u16_wrapping_mul", 16, 0, N9_WRAP, N9_MUL, INT64_C(0), INT64_C(65535)}, + {"u16_checked_add", 16, 0, N9_CHECK, N9_ADD, INT64_C(0), INT64_C(65535)}, + {"u16_checked_sub", 16, 0, N9_CHECK, N9_SUB, INT64_C(0), INT64_C(65535)}, + {"u16_checked_mul", 16, 0, N9_CHECK, N9_MUL, INT64_C(0), INT64_C(65535)}, + {"u16_saturating_add", 16, 0, N9_SAT, N9_ADD, INT64_C(0), INT64_C(65535)}, + {"u16_saturating_sub", 16, 0, N9_SAT, N9_SUB, INT64_C(0), INT64_C(65535)}, + {"u16_saturating_mul", 16, 0, N9_SAT, N9_MUL, INT64_C(0), INT64_C(65535)}, + {"u32_wrapping_add", 32, 0, N9_WRAP, N9_ADD, INT64_C(0), INT64_C(4294967295)}, + {"u32_wrapping_sub", 32, 0, N9_WRAP, N9_SUB, INT64_C(0), INT64_C(4294967295)}, + {"u32_wrapping_mul", 32, 0, N9_WRAP, N9_MUL, INT64_C(0), INT64_C(4294967295)}, + {"u32_checked_add", 32, 0, N9_CHECK, N9_ADD, INT64_C(0), INT64_C(4294967295)}, + {"u32_checked_sub", 32, 0, N9_CHECK, N9_SUB, INT64_C(0), INT64_C(4294967295)}, + {"u32_checked_mul", 32, 0, N9_CHECK, N9_MUL, INT64_C(0), INT64_C(4294967295)}, + {"u32_saturating_add", 32, 0, N9_SAT, N9_ADD, INT64_C(0), INT64_C(4294967295)}, + {"u32_saturating_sub", 32, 0, N9_SAT, N9_SUB, INT64_C(0), INT64_C(4294967295)}, + {"u32_saturating_mul", 32, 0, N9_SAT, N9_MUL, INT64_C(0), INT64_C(4294967295)}, + {"u64_wrapping_add", 64, 0, N9_WRAP, N9_ADD, INT64_MIN, INT64_MAX}, + {"u64_wrapping_sub", 64, 0, N9_WRAP, N9_SUB, INT64_MIN, INT64_MAX}, + {"u64_wrapping_mul", 64, 0, N9_WRAP, N9_MUL, INT64_MIN, INT64_MAX}, + {"u64_checked_add", 64, 0, N9_CHECK, N9_ADD, INT64_MIN, INT64_MAX}, + {"u64_checked_sub", 64, 0, N9_CHECK, N9_SUB, INT64_MIN, INT64_MAX}, + {"u64_checked_mul", 64, 0, N9_CHECK, N9_MUL, INT64_MIN, INT64_MAX}, + {"u64_saturating_add", 64, 0, N9_SAT, N9_ADD, INT64_MIN, INT64_MAX}, + {"u64_saturating_sub", 64, 0, N9_SAT, N9_SUB, INT64_MIN, INT64_MAX}, + {"u64_saturating_mul", 64, 0, N9_SAT, N9_MUL, INT64_MIN, INT64_MAX}, +}; + +static int n9_pair(Value *arg, int64_t lo, int64_t hi, + int64_t *a, int64_t *b, const char *who) { + if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) { + rt_error(EK_TYPE, 0, "%s: expected [a, b]", who); + return 0; + } + if (arg->data.list.count > 2) { + rt_error(EK_TYPE, 0, "%s: expected a fixed-shape list with at most 2 elements", who); + return 0; + } + EigsView av, bv; + return eigs_int_arg_range(list_get_view(arg, 0, &av), lo, hi, a, who) + && eigs_int_arg_range(list_get_view(arg, 1, &bv), lo, hi, b, who); +} + +/* Decode bits without an out-of-range unsigned-to-signed cast. */ +static int64_t n9_i64_bits(uint64_t bits) { + int64_t value; + memcpy(&value, &bits, sizeof(value)); + return value; +} + +static Value *n9_width(Value *arg, const N9Spec *s) { + int64_t a, b; + if (!n9_pair(arg, s->lo, s->hi, &a, &b, s->name)) return make_null(); + uint64_t ua = (uint64_t)a, ub = (uint64_t)b; + uint64_t mask = s->width == 64 ? UINT64_MAX : (UINT64_C(1) << s->width) - 1; + uint64_t bits = 0; + switch (s->op) { + case N9_ADD: bits = ua + ub; break; + case N9_SUB: bits = ua - ub; break; + case N9_MUL: bits = ua * ub; break; + } + if (s->mode != N9_WRAP) { + int overflow = 0, high = 0; + if (s->is_signed) { + int64_t result = 0; + switch (s->op) { + case N9_ADD: + overflow = __builtin_add_overflow(a, b, &result); + high = b > 0; + break; + case N9_SUB: + overflow = __builtin_sub_overflow(a, b, &result); + high = b < 0; + break; + case N9_MUL: + overflow = __builtin_mul_overflow(a, b, &result); + high = (a < 0) == (b < 0); + break; + } + if (!overflow) { + overflow = result < s->lo || result > s->hi; + high = result > s->hi; + } + } else { + switch (s->op) { + case N9_ADD: overflow = bits < ua || bits > mask; high = 1; break; + case N9_SUB: overflow = ua < ub; high = 0; break; + case N9_MUL: overflow = ub != 0 && ua > mask / ub; high = 1; break; + } + } + if (overflow) { + if (s->mode == N9_CHECK) { + rt_error(EK_VALUE, 0, "%s: integer overflow", s->name); + return make_null(); + } + if (s->is_signed) return make_int(high ? s->hi : s->lo); + return make_int(high ? n9_i64_bits(mask) : 0); + } + } + bits &= mask; + if (s->is_signed && s->width < 64 && (bits & (UINT64_C(1) << (s->width - 1)))) + return make_int(-1 - (int64_t)(mask - bits)); + return make_int(n9_i64_bits(bits)); +} + +/* i8_wrapping_add of [a, b] — i8 wrapping add; N5 operands, int result */ +Value* builtin_i8_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[0]); +} + +/* i8_wrapping_sub of [a, b] — i8 wrapping sub; N5 operands, int result */ +Value* builtin_i8_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[1]); +} + +/* i8_wrapping_mul of [a, b] — i8 wrapping mul; N5 operands, int result */ +Value* builtin_i8_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[2]); +} + +/* i8_checked_add of [a, b] — i8 checked add; N5 operands, int result */ +Value* builtin_i8_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[3]); +} + +/* i8_checked_sub of [a, b] — i8 checked sub; N5 operands, int result */ +Value* builtin_i8_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[4]); +} + +/* i8_checked_mul of [a, b] — i8 checked mul; N5 operands, int result */ +Value* builtin_i8_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[5]); +} + +/* i8_saturating_add of [a, b] — i8 saturating add; N5 operands, int result */ +Value* builtin_i8_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[6]); +} + +/* i8_saturating_sub of [a, b] — i8 saturating sub; N5 operands, int result */ +Value* builtin_i8_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[7]); +} + +/* i8_saturating_mul of [a, b] — i8 saturating mul; N5 operands, int result */ +Value* builtin_i8_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[8]); +} + +/* i16_wrapping_add of [a, b] — i16 wrapping add; N5 operands, int result */ +Value* builtin_i16_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[9]); +} + +/* i16_wrapping_sub of [a, b] — i16 wrapping sub; N5 operands, int result */ +Value* builtin_i16_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[10]); +} + +/* i16_wrapping_mul of [a, b] — i16 wrapping mul; N5 operands, int result */ +Value* builtin_i16_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[11]); +} + +/* i16_checked_add of [a, b] — i16 checked add; N5 operands, int result */ +Value* builtin_i16_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[12]); +} + +/* i16_checked_sub of [a, b] — i16 checked sub; N5 operands, int result */ +Value* builtin_i16_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[13]); +} + +/* i16_checked_mul of [a, b] — i16 checked mul; N5 operands, int result */ +Value* builtin_i16_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[14]); +} + +/* i16_saturating_add of [a, b] — i16 saturating add; N5 operands, int result */ +Value* builtin_i16_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[15]); +} + +/* i16_saturating_sub of [a, b] — i16 saturating sub; N5 operands, int result */ +Value* builtin_i16_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[16]); +} + +/* i16_saturating_mul of [a, b] — i16 saturating mul; N5 operands, int result */ +Value* builtin_i16_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[17]); +} + +/* i32_wrapping_add of [a, b] — i32 wrapping add; N5 operands, int result */ +Value* builtin_i32_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[18]); +} + +/* i32_wrapping_sub of [a, b] — i32 wrapping sub; N5 operands, int result */ +Value* builtin_i32_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[19]); +} + +/* i32_wrapping_mul of [a, b] — i32 wrapping mul; N5 operands, int result */ +Value* builtin_i32_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[20]); +} + +/* i32_checked_add of [a, b] — i32 checked add; N5 operands, int result */ +Value* builtin_i32_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[21]); +} + +/* i32_checked_sub of [a, b] — i32 checked sub; N5 operands, int result */ +Value* builtin_i32_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[22]); +} + +/* i32_checked_mul of [a, b] — i32 checked mul; N5 operands, int result */ +Value* builtin_i32_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[23]); +} + +/* i32_saturating_add of [a, b] — i32 saturating add; N5 operands, int result */ +Value* builtin_i32_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[24]); +} + +/* i32_saturating_sub of [a, b] — i32 saturating sub; N5 operands, int result */ +Value* builtin_i32_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[25]); +} + +/* i32_saturating_mul of [a, b] — i32 saturating mul; N5 operands, int result */ +Value* builtin_i32_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[26]); +} + +/* i64_wrapping_add of [a, b] — i64 wrapping add; N5 operands, int result */ +Value* builtin_i64_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[27]); +} + +/* i64_wrapping_sub of [a, b] — i64 wrapping sub; N5 operands, int result */ +Value* builtin_i64_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[28]); +} + +/* i64_wrapping_mul of [a, b] — i64 wrapping mul; N5 operands, int result */ +Value* builtin_i64_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[29]); +} + +/* i64_checked_add of [a, b] — i64 checked add; N5 operands, int result */ +Value* builtin_i64_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[30]); +} + +/* i64_checked_sub of [a, b] — i64 checked sub; N5 operands, int result */ +Value* builtin_i64_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[31]); +} + +/* i64_checked_mul of [a, b] — i64 checked mul; N5 operands, int result */ +Value* builtin_i64_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[32]); +} + +/* i64_saturating_add of [a, b] — i64 saturating add; N5 operands, int result */ +Value* builtin_i64_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[33]); +} + +/* i64_saturating_sub of [a, b] — i64 saturating sub; N5 operands, int result */ +Value* builtin_i64_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[34]); +} + +/* i64_saturating_mul of [a, b] — i64 saturating mul; N5 operands, int result */ +Value* builtin_i64_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[35]); +} + +/* u8_wrapping_add of [a, b] — u8 wrapping add; N5 operands, int result */ +Value* builtin_u8_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[36]); +} + +/* u8_wrapping_sub of [a, b] — u8 wrapping sub; N5 operands, int result */ +Value* builtin_u8_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[37]); +} + +/* u8_wrapping_mul of [a, b] — u8 wrapping mul; N5 operands, int result */ +Value* builtin_u8_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[38]); +} + +/* u8_checked_add of [a, b] — u8 checked add; N5 operands, int result */ +Value* builtin_u8_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[39]); +} + +/* u8_checked_sub of [a, b] — u8 checked sub; N5 operands, int result */ +Value* builtin_u8_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[40]); +} + +/* u8_checked_mul of [a, b] — u8 checked mul; N5 operands, int result */ +Value* builtin_u8_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[41]); +} + +/* u8_saturating_add of [a, b] — u8 saturating add; N5 operands, int result */ +Value* builtin_u8_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[42]); +} + +/* u8_saturating_sub of [a, b] — u8 saturating sub; N5 operands, int result */ +Value* builtin_u8_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[43]); +} + +/* u8_saturating_mul of [a, b] — u8 saturating mul; N5 operands, int result */ +Value* builtin_u8_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[44]); +} + +/* u16_wrapping_add of [a, b] — u16 wrapping add; N5 operands, int result */ +Value* builtin_u16_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[45]); +} + +/* u16_wrapping_sub of [a, b] — u16 wrapping sub; N5 operands, int result */ +Value* builtin_u16_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[46]); +} + +/* u16_wrapping_mul of [a, b] — u16 wrapping mul; N5 operands, int result */ +Value* builtin_u16_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[47]); +} + +/* u16_checked_add of [a, b] — u16 checked add; N5 operands, int result */ +Value* builtin_u16_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[48]); +} + +/* u16_checked_sub of [a, b] — u16 checked sub; N5 operands, int result */ +Value* builtin_u16_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[49]); +} + +/* u16_checked_mul of [a, b] — u16 checked mul; N5 operands, int result */ +Value* builtin_u16_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[50]); +} + +/* u16_saturating_add of [a, b] — u16 saturating add; N5 operands, int result */ +Value* builtin_u16_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[51]); +} + +/* u16_saturating_sub of [a, b] — u16 saturating sub; N5 operands, int result */ +Value* builtin_u16_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[52]); +} + +/* u16_saturating_mul of [a, b] — u16 saturating mul; N5 operands, int result */ +Value* builtin_u16_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[53]); +} + +/* u32_wrapping_add of [a, b] — u32 wrapping add; N5 operands, int result */ +Value* builtin_u32_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[54]); +} + +/* u32_wrapping_sub of [a, b] — u32 wrapping sub; N5 operands, int result */ +Value* builtin_u32_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[55]); +} + +/* u32_wrapping_mul of [a, b] — u32 wrapping mul; N5 operands, int result */ +Value* builtin_u32_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[56]); +} + +/* u32_checked_add of [a, b] — u32 checked add; N5 operands, int result */ +Value* builtin_u32_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[57]); +} + +/* u32_checked_sub of [a, b] — u32 checked sub; N5 operands, int result */ +Value* builtin_u32_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[58]); +} + +/* u32_checked_mul of [a, b] — u32 checked mul; N5 operands, int result */ +Value* builtin_u32_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[59]); +} + +/* u32_saturating_add of [a, b] — u32 saturating add; N5 operands, int result */ +Value* builtin_u32_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[60]); +} + +/* u32_saturating_sub of [a, b] — u32 saturating sub; N5 operands, int result */ +Value* builtin_u32_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[61]); +} + +/* u32_saturating_mul of [a, b] — u32 saturating mul; N5 operands, int result */ +Value* builtin_u32_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[62]); +} + +/* u64_wrapping_add of [a, b] — u64 wrapping add; N5 operands, int result */ +Value* builtin_u64_wrapping_add(Value *arg) { + return n9_width(arg, &n9_specs[63]); +} + +/* u64_wrapping_sub of [a, b] — u64 wrapping sub; N5 operands, int result */ +Value* builtin_u64_wrapping_sub(Value *arg) { + return n9_width(arg, &n9_specs[64]); +} + +/* u64_wrapping_mul of [a, b] — u64 wrapping mul; N5 operands, int result */ +Value* builtin_u64_wrapping_mul(Value *arg) { + return n9_width(arg, &n9_specs[65]); +} + +/* u64_checked_add of [a, b] — u64 checked add; N5 operands, int result */ +Value* builtin_u64_checked_add(Value *arg) { + return n9_width(arg, &n9_specs[66]); +} + +/* u64_checked_sub of [a, b] — u64 checked sub; N5 operands, int result */ +Value* builtin_u64_checked_sub(Value *arg) { + return n9_width(arg, &n9_specs[67]); +} + +/* u64_checked_mul of [a, b] — u64 checked mul; N5 operands, int result */ +Value* builtin_u64_checked_mul(Value *arg) { + return n9_width(arg, &n9_specs[68]); +} + +/* u64_saturating_add of [a, b] — u64 saturating add; N5 operands, int result */ +Value* builtin_u64_saturating_add(Value *arg) { + return n9_width(arg, &n9_specs[69]); +} + +/* u64_saturating_sub of [a, b] — u64 saturating sub; N5 operands, int result */ +Value* builtin_u64_saturating_sub(Value *arg) { + return n9_width(arg, &n9_specs[70]); +} + +/* u64_saturating_mul of [a, b] — u64 saturating mul; N5 operands, int result */ +Value* builtin_u64_saturating_mul(Value *arg) { + return n9_width(arg, &n9_specs[71]); +} + +/* u64_compare of [a, b] — unsigned ordering of int64 bit patterns */ +Value* builtin_u64_compare(Value *arg) { + int64_t a, b; + if (!n9_pair(arg, INT64_MIN, INT64_MAX, &a, &b, "u64_compare")) return make_null(); + return make_int(((uint64_t)a > (uint64_t)b) - ((uint64_t)a < (uint64_t)b)); +} +static Value *n9_unsigned_div(Value *arg, int remainder, const char *who) { + int64_t a, b; + if (!n9_pair(arg, INT64_MIN, INT64_MAX, &a, &b, who)) return make_null(); + if (b == 0) { + rt_error(EK_VALUE, 0, "division by zero"); + return make_null(); + } + uint64_t value = remainder ? (uint64_t)a % (uint64_t)b : (uint64_t)a / (uint64_t)b; + return make_int(n9_i64_bits(value)); +} +/* u64_div of [a, b] — unsigned division; zero raises value */ +Value* builtin_u64_div(Value *arg) { + return n9_unsigned_div(arg, 0, "u64_div"); +} +/* u64_rem of [a, b] — unsigned remainder; zero raises value */ +Value* builtin_u64_rem(Value *arg) { + return n9_unsigned_div(arg, 1, "u64_rem"); +} +/* u64_to_string of x — unsigned decimal text from an int64 bit pattern */ +Value* builtin_u64_to_string(Value *arg) { + EigsView view; + if (arg && arg->type == VAL_LIST && arg->data.list.count == 1) + arg = list_get_view(arg, 0, &view); + int64_t value = eigs_int_arg(arg, "u64_to_string"); + if (g_has_error) return make_null(); + char text[21]; + snprintf(text, sizeof(text), "%llu", (unsigned long long)(uint64_t)value); + return make_str(text); +} +/* fround of x — binary64 to binary32 to binary64, returning float kind */ +Value* builtin_fround(Value *arg) { + EigsView view; + if (arg && arg->type == VAL_LIST && arg->data.list.count == 1) + arg = list_get_view(arg, 0, &view); + double value = eigs_float_arg(arg, "fround"); + if (g_has_error) return make_null(); + /* Preserve int -> f64 -> f32 -> f64 double rounding. make_float keeps + * today's overflow guard and math flag until the IEEE round R2.2. */ + return make_float((double)(float)value); +} diff --git a/src/builtins_internal.h b/src/builtins_internal.h index 800481501..cf215b2ed 100644 --- a/src/builtins_internal.h +++ b/src/builtins_internal.h @@ -9,6 +9,85 @@ #include "eigenscript.h" +/* #1758 N9 — builtins_int.c, shared table-driven width engine. */ +Value* builtin_i8_wrapping_add(Value *arg); +Value* builtin_i8_wrapping_sub(Value *arg); +Value* builtin_i8_wrapping_mul(Value *arg); +Value* builtin_i8_checked_add(Value *arg); +Value* builtin_i8_checked_sub(Value *arg); +Value* builtin_i8_checked_mul(Value *arg); +Value* builtin_i8_saturating_add(Value *arg); +Value* builtin_i8_saturating_sub(Value *arg); +Value* builtin_i8_saturating_mul(Value *arg); +Value* builtin_i16_wrapping_add(Value *arg); +Value* builtin_i16_wrapping_sub(Value *arg); +Value* builtin_i16_wrapping_mul(Value *arg); +Value* builtin_i16_checked_add(Value *arg); +Value* builtin_i16_checked_sub(Value *arg); +Value* builtin_i16_checked_mul(Value *arg); +Value* builtin_i16_saturating_add(Value *arg); +Value* builtin_i16_saturating_sub(Value *arg); +Value* builtin_i16_saturating_mul(Value *arg); +Value* builtin_i32_wrapping_add(Value *arg); +Value* builtin_i32_wrapping_sub(Value *arg); +Value* builtin_i32_wrapping_mul(Value *arg); +Value* builtin_i32_checked_add(Value *arg); +Value* builtin_i32_checked_sub(Value *arg); +Value* builtin_i32_checked_mul(Value *arg); +Value* builtin_i32_saturating_add(Value *arg); +Value* builtin_i32_saturating_sub(Value *arg); +Value* builtin_i32_saturating_mul(Value *arg); +Value* builtin_i64_wrapping_add(Value *arg); +Value* builtin_i64_wrapping_sub(Value *arg); +Value* builtin_i64_wrapping_mul(Value *arg); +Value* builtin_i64_checked_add(Value *arg); +Value* builtin_i64_checked_sub(Value *arg); +Value* builtin_i64_checked_mul(Value *arg); +Value* builtin_i64_saturating_add(Value *arg); +Value* builtin_i64_saturating_sub(Value *arg); +Value* builtin_i64_saturating_mul(Value *arg); +Value* builtin_u8_wrapping_add(Value *arg); +Value* builtin_u8_wrapping_sub(Value *arg); +Value* builtin_u8_wrapping_mul(Value *arg); +Value* builtin_u8_checked_add(Value *arg); +Value* builtin_u8_checked_sub(Value *arg); +Value* builtin_u8_checked_mul(Value *arg); +Value* builtin_u8_saturating_add(Value *arg); +Value* builtin_u8_saturating_sub(Value *arg); +Value* builtin_u8_saturating_mul(Value *arg); +Value* builtin_u16_wrapping_add(Value *arg); +Value* builtin_u16_wrapping_sub(Value *arg); +Value* builtin_u16_wrapping_mul(Value *arg); +Value* builtin_u16_checked_add(Value *arg); +Value* builtin_u16_checked_sub(Value *arg); +Value* builtin_u16_checked_mul(Value *arg); +Value* builtin_u16_saturating_add(Value *arg); +Value* builtin_u16_saturating_sub(Value *arg); +Value* builtin_u16_saturating_mul(Value *arg); +Value* builtin_u32_wrapping_add(Value *arg); +Value* builtin_u32_wrapping_sub(Value *arg); +Value* builtin_u32_wrapping_mul(Value *arg); +Value* builtin_u32_checked_add(Value *arg); +Value* builtin_u32_checked_sub(Value *arg); +Value* builtin_u32_checked_mul(Value *arg); +Value* builtin_u32_saturating_add(Value *arg); +Value* builtin_u32_saturating_sub(Value *arg); +Value* builtin_u32_saturating_mul(Value *arg); +Value* builtin_u64_wrapping_add(Value *arg); +Value* builtin_u64_wrapping_sub(Value *arg); +Value* builtin_u64_wrapping_mul(Value *arg); +Value* builtin_u64_checked_add(Value *arg); +Value* builtin_u64_checked_sub(Value *arg); +Value* builtin_u64_checked_mul(Value *arg); +Value* builtin_u64_saturating_add(Value *arg); +Value* builtin_u64_saturating_sub(Value *arg); +Value* builtin_u64_saturating_mul(Value *arg); +Value* builtin_u64_compare(Value *arg); +Value* builtin_u64_div(Value *arg); +Value* builtin_u64_rem(Value *arg); +Value* builtin_u64_to_string(Value *arg); +Value* builtin_fround(Value *arg); + /* Tensor builtins — implemented in builtins_tensor.c */ Value* builtin_tensor_add(Value *arg); Value* builtin_tensor_subtract(Value *arg); diff --git a/src/builtins_tensor.c b/src/builtins_tensor.c index 10f7da2cc..4dd412f66 100644 --- a/src/builtins_tensor.c +++ b/src/builtins_tensor.c @@ -210,7 +210,7 @@ static Value* make_buffer_like(Value *a) { /* same count + shape as a */ /* ==================================================================== * TENSOR SUBSTRATE BUILTINS * Expose tensor operations so the .eigs standard library can run natively. - * Tensors are represented as nested VAL_LIST of VAL_NUM (1D or 2D). + * Tensors are nested numeric VAL_LIST values (1D or 2D), or float buffers. * ==================================================================== */ /* --- Tensor helper: detect dimensions --- */ @@ -226,7 +226,7 @@ static int tensor_dims(Value *v, int *rows, int *cols) { *rows = 1; *cols = v->data.buffer.count; return 1; } if (!v || v->type != VAL_LIST || v->data.list.count == 0) return 0; - if (list_elem_type(v, 0) == VAL_NUM) { + if ((list_elem_type(v, 0) == VAL_FLOAT || list_elem_type(v, 0) == VAL_INT)) { /* 1D tensor */ *rows = 1; *cols = v->data.list.count; @@ -254,7 +254,7 @@ static double* tensor_to_flat(Value *v, int *rows, int *cols, * explicit EIGS_STRICT=0 path deliberately retains the old stand-in. * Check even when the first element or row gives no usable shape. */ /* #1637: a bool element is refused in every strict mode. */ -#define FLAT_BAD(t) ((t) != VAL_NUM && (g_strict || (t) == VAL_BOOL)) +#define FLAT_BAD(t) (((t) != VAL_FLOAT && (t) != VAL_INT) && (g_strict || (t) == VAL_BOOL)) if (v && v->type == VAL_LIST) { int bad = 0; if (ndim == 1) { @@ -288,15 +288,19 @@ static double* tensor_to_flat(Value *v, int *rows, int *cols, return out; } if (ndim == 1) { - for (int i = 0; i < *cols; i++) - out[i] = (list_elem_type(v, i) == VAL_NUM) ? eigs_slot_num(list_slot(v, i), __func__) : 0.0; + for (int i = 0; i < *cols; i++) { + out[i] = ((list_elem_type(v, i) == VAL_FLOAT || list_elem_type(v, i) == VAL_INT)) ? eigs_slot_float(list_slot(v, i), who) : 0.0; + if (g_has_error) { free(out); return NULL; } + } } else { for (int r = 0; r < *rows; r++) { /* list_get_ref is NULL for a number: test the type first. */ Value *row = (list_elem_type(v, r) == VAL_LIST) ? list_get_ref(v, r) : NULL; int rc = row ? row->data.list.count : 0; - for (int c = 0; c < *cols && c < rc; c++) - out[r * (*cols) + c] = (list_elem_type(row, c) == VAL_NUM) ? eigs_slot_num(list_slot(row, c), __func__) : 0.0; + for (int c = 0; c < *cols && c < rc; c++) { + out[r * (*cols) + c] = ((list_elem_type(row, c) == VAL_FLOAT || list_elem_type(row, c) == VAL_INT)) ? eigs_slot_float(list_slot(row, c), who) : 0.0; + if (g_has_error) { free(out); return NULL; } + } } } return out; @@ -308,7 +312,7 @@ static Value* flat_to_tensor_2d(double *data, int rows, int cols) { for (int r = 0; r < rows; r++) { Value *row = make_list(cols); for (int c = 0; c < cols; c++) { - Value *item = make_num(data[r * cols + c]); + Value *item = make_float(data[r * cols + c]); if (g_has_error) { val_decref(item); val_decref(row); val_decref(outer); return make_null(); @@ -324,7 +328,7 @@ static Value* flat_to_tensor_2d(double *data, int rows, int cols) { static Value* flat_to_tensor_1d(double *data, int len) { Value *out = make_list(len); for (int i = 0; i < len; i++) { - Value *item = make_num(data[i]); + Value *item = make_float(data[i]); if (g_has_error) { val_decref(item); val_decref(out); return make_null(); @@ -356,7 +360,7 @@ static Value* flat_to_like(Value *src, double *data, int rows, int cols) { /* --- Tensor helper: count total elements recursively --- */ static int tensor_total(Value *v) { if (!v) return 0; - if (v->type == VAL_NUM) return 1; + if (val_is_number(v)) return 1; /* #1637: arithmetic on a bool raises. Other non-number elements keep * their old (counted-as-absent) reading; a bool is the one that used to * BE a number, so letting it vanish from `sum of [a < b, ...]` would turn @@ -377,10 +381,10 @@ static int tensor_total(Value *v) { * Return failure at the first raised read; callers own and free the workspace. */ static int tensor_flatten_recursive(Value *v, double *out, int *idx) { if (!v) return 1; - if (v->type == VAL_NUM) { out[(*idx)++] = eigs_num_arg(v, __func__); return 1; } + if (val_is_number(v)) { out[(*idx)++] = eigs_float_arg(v, __func__); return !g_has_error; } if (v->type == VAL_BUFFER) { /* #1093 */ for (int i = 0; i < v->data.buffer.count; i++) { - double x = buffer_read_num(v, i); + double x = buffer_read_f64(v, i); if (g_has_error) return 0; out[(*idx)++] = x; } @@ -436,9 +440,11 @@ static Value* buf_elementwise(Value *a, Value *b, BinOpFn fn) { Value *out = make_shaped_buffer(rows, cols); if (!out) return make_null(); for (int r = 0; r < rows; r++) - for (int c = 0; c < cols; c++) + for (int c = 0; c < cols; c++) { out->data.buffer.data[r * cols + c] = fn(ad[r * cols + c], (bn == cols) ? bd[c] : bd[r]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } } @@ -449,32 +455,42 @@ static Value* buf_elementwise(Value *a, Value *b, BinOpFn fn) { Value *out = make_shaped_buffer(rows, cols); if (!out) return make_null(); for (int r = 0; r < rows; r++) - for (int c = 0; c < cols; c++) + for (int c = 0; c < cols; c++) { out->data.buffer.data[r * cols + c] = fn((an == cols) ? ad[c] : ad[r], bd[r * cols + c]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } } if (an == bn) { /* same length: keep a's shape */ Value *out = make_buffer_like(a); if (!out) return make_null(); - for (int i = 0; i < an; i++) out->data.buffer.data[i] = fn(ad[i], bd[i]); + for (int i = 0; i < an; i++) { + out->data.buffer.data[i] = fn(ad[i], bd[i]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } /* Mismatched lengths truncate to the shorter operand, as the list path does. */ int n = an < bn ? an : bn; Value *out = make_shaped_buffer(0, n); if (!out) return make_null(); - for (int i = 0; i < n; i++) out->data.buffer.data[i] = fn(ad[i], bd[i]); + for (int i = 0; i < n; i++) { + out->data.buffer.data[i] = fn(ad[i], bd[i]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } static Value* buf_scalar_elementwise(Value *buf, double sc, BinOpFn fn, int buf_left) { Value *out = make_buffer_like(buf); if (!out) return make_null(); - for (int i = 0; i < buf->data.buffer.count; i++) + for (int i = 0; i < buf->data.buffer.count; i++) { out->data.buffer.data[i] = buf_left ? fn(buf->data.buffer.data[i], sc) : fn(sc, buf->data.buffer.data[i]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } @@ -485,11 +501,33 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { * `arg` is what the guard macros test for a bool (#1637): either operand. */ Value *arg = (a && a->type == VAL_BOOL) ? a : b; ARG_GUARD(!a || !b, "add/subtract/multiply/divide/pow", - "two tensor operands", make_num(0.0)); + "two tensor operands", make_float(0.0)); /* scalar op scalar */ - if (a->type == VAL_NUM && b->type == VAL_NUM) - return make_num(fn(eigs_num_arg(a, __func__), eigs_num_arg(b, __func__))); + if (val_is_number(a) && val_is_number(b)) { + const char *who = fn == op_add ? "add" : fn == op_sub ? "subtract" : + fn == op_mul ? "multiply" : "divide/pow"; + EigsNum x = eigs_number_arg(a, who); + if (g_has_error) return make_null(); + EigsNum y = eigs_number_arg(b, who); + if (g_has_error) return make_null(); + if (x.is_int && y.is_int && (fn == op_add || fn == op_sub || fn == op_mul)) { + int64_t result; + int overflow = fn == op_add ? __builtin_add_overflow(x.value.i64, y.value.i64, &result) : + fn == op_sub ? __builtin_sub_overflow(x.value.i64, y.value.i64, &result) : + __builtin_mul_overflow(x.value.i64, y.value.i64, &result); + if (overflow) { + rt_error(EK_VALUE, 0, "%s: int64 overflow in '%s'", who, + fn == op_add ? "+" : fn == op_sub ? "-" : "*"); + return make_null(); + } + return make_int(result); + } + double xd = x.is_int ? (double)x.value.i64 : x.value.f64; + double yd = y.is_int ? (double)y.value.i64 : y.value.f64; + double result = fn(xd, yd); + return g_has_error ? make_null() : make_float(result); + } /* #1093 buffers are flat numeric tensors. Buffer-only operands compute on * the flat doubles and return a buffer; a buffer MIXED with a list is @@ -497,10 +535,16 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { * (the rule: a buffer out iff every tensor operand was a buffer). */ if (a->type == VAL_BUFFER && b->type == VAL_BUFFER) return buf_elementwise(a, b, fn); - if (a->type == VAL_BUFFER && b->type == VAL_NUM) - return buf_scalar_elementwise(a, eigs_num_arg(b, __func__), fn, 1); - if (a->type == VAL_NUM && b->type == VAL_BUFFER) - return buf_scalar_elementwise(b, eigs_num_arg(a, __func__), fn, 0); + if (a->type == VAL_BUFFER && val_is_number(b)) { + double scalar = eigs_float_arg(b, __func__); + if (g_has_error) return make_null(); + return buf_scalar_elementwise(a, scalar, fn, 1); + } + if (val_is_number(a) && b->type == VAL_BUFFER) { + double scalar = eigs_float_arg(a, __func__); + if (g_has_error) return make_null(); + return buf_scalar_elementwise(b, scalar, fn, 0); + } if (a->type == VAL_BUFFER && b->type == VAL_LIST) { Value *al = buf_as_tensor_list(a); if (g_has_error) { val_decref(al); return make_null(); } @@ -517,7 +561,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { } /* scalar broadcast to list */ - if (a->type == VAL_NUM && b->type == VAL_LIST) { + if (val_is_number(a) && b->type == VAL_LIST) { Value *out = make_list(b->data.list.count); for (int i = 0; i < b->data.list.count; i++) { Value *item = tensor_elementwise_slots(slot_from_heap(a), list_slot(b, i), fn); @@ -528,7 +572,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { } return out; } - if (a->type == VAL_LIST && b->type == VAL_NUM) { + if (a->type == VAL_LIST && val_is_number(b)) { Value *out = make_list(a->data.list.count); for (int i = 0; i < a->data.list.count; i++) { Value *item = tensor_elementwise_slots(list_slot(a, i), slot_from_heap(b), fn); @@ -626,7 +670,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { * function. That is the wrong-type case, and 0.0 was indistinguishable * from a real elementwise result. */ ARG_GUARD(1, "add/subtract/multiply/divide/pow", - "numbers, lists or buffers as operands", make_num(0.0)); + "numbers, lists or buffers as operands", make_float(0.0)); } static Value* tensor_elementwise_slots(EigsSlot a, EigsSlot b, BinOpFn fn) { @@ -678,22 +722,41 @@ Value* builtin_tensor_pow(Value *arg) { /* ---- Element-wise unary op ---- */ typedef double (*UnaryOpFn)(double); +static double op_neg(double x); static Value* tensor_unary(Value *v, UnaryOpFn fn) { Value *arg = v; /* what the guard macros test for a bool (#1637) */ - if (v->type == VAL_NUM) return make_num(fn(eigs_num_arg(v, __func__))); + if (val_is_number(v)) { + EigsNum n = eigs_number_arg(v, fn == op_neg ? "negative" : "sqrt/exp/log"); + if (g_has_error) return make_null(); + if (fn == op_neg && n.is_int) { + if (n.value.i64 == INT64_MIN) { + rt_error(EK_VALUE, 0, "negative: int64 overflow in '-'"); + return make_null(); + } + return make_int(-n.value.i64); + } + double result = fn(n.is_int ? (double)n.value.i64 : n.value.f64); + return g_has_error ? make_null() : make_float(result); + } /* #1093: a buffer is a flat numeric tensor — same kernel, buffer out. */ if (v->type == VAL_BUFFER) { Value *out = make_buffer_like(v); if (!out) return make_null(); - for (int i = 0; i < v->data.buffer.count; i++) + for (int i = 0; i < v->data.buffer.count; i++) { out->data.buffer.data[i] = fn(v->data.buffer.data[i]); + if (g_has_error) { val_decref(out); return make_null(); } + } return out; } if (v->type == VAL_LIST) { Value *out = make_list(v->data.list.count); for (int i = 0; i < v->data.list.count; i++) { EIGS_VIEW(child_view); - list_append_owned(out, tensor_unary(list_get_view(v, i, &child_view), fn)); + Value *item = tensor_unary(list_get_view(v, i, &child_view), fn); + if (g_has_error) { + val_decref(item); val_decref(out); return make_null(); + } + list_append_owned(out, item); } return out; } @@ -702,7 +765,7 @@ static Value* tensor_unary(Value *v, UnaryOpFn fn) { * by sqrt/exp/log/negative and by its own recursion, so the guard names * that surface rather than one call. */ ARG_GUARD(1, "sqrt/exp/log/negative", "a number, a list or a buffer", - make_num(0.0)); + make_float(0.0)); } /* #865: `sqrt of -1` returns 0, which is indistinguishable from `sqrt of 0`. @@ -794,7 +857,7 @@ Value* builtin_tensor_matmul(Value *arg) { "result is not a number (NaN has no defined value)"); break; } - res->data.buffer.data[i] = SLOT_NUM_RAW(slot_null()); + res->data.buffer.data[i] = SLOT_F64_RAW(slot_null()); } } return res; @@ -823,7 +886,7 @@ Value* builtin_tensor_matmul(Value *arg) { double *out = xcalloc((size_t)ar * bc, sizeof(double)); ne_matmul_buf(af, ar, ac, bf, bc, out); /* #971: same NaN collapse as the buffer path, so the strict raise names - * matmul instead of the bare num_guard backstop inside make_num. */ + * matmul instead of the bare num_guard backstop inside make_float. */ for (int64_t i = 0; i < (int64_t)ar * bc; i++) { if (out[i] != out[i]) out[i] = num_guard_named(out[i], "matmul"); if (g_has_error) { free(af); free(bf); free(out); return make_null(); } @@ -992,7 +1055,7 @@ Value* builtin_tensor_scatter_add(Value *arg) { rt_error(EK_TYPE, 0, "scatter_add: indices must be a list or buffer, got %s", val_type_name(indices->type)); return make_null(); } - if (values->type != VAL_LIST && values->type != VAL_BUFFER && values->type != VAL_NUM) { + if (values->type != VAL_LIST && values->type != VAL_BUFFER && !val_is_number(values)) { rt_error(EK_TYPE, 0, "scatter_add: values must be a buffer, a list of numbers, or a number, got %s", val_type_name(values->type)); return make_null(); } @@ -1020,54 +1083,51 @@ Value* builtin_tensor_scatter_add(Value *arg) { * so a raise leaves dst untouched instead of half-updated. */ for (int pass = 0; pass < 2; pass++) { for (int i = 0; i < n; i++) { - double di, v; + int64_t di; + double v; if (indices->type == VAL_LIST) { ValType it = list_elem_type(indices, i); - if (it != VAL_NUM) { + if ((it != VAL_FLOAT && it != VAL_INT)) { rt_error(EK_TYPE, 0, "scatter_add: index %d is %s (expected a number)", i, val_type_name(it)); return make_null(); } - di = eigs_slot_num(list_slot(indices, i), __func__); + di = eigs_slot_int(list_slot(indices, i), "scatter_add"); + if (g_has_error) return make_null(); } else { - /* #1417: an index is a scalar read too. Guard before the - * double-to-int conversion: casting a stored NaN is undefined - * behavior, and must follow the same strict/lenient rule as - * [] and the buffer accessors. */ - di = buffer_read_num(indices, i); + /* N5: require an exact finite index before bounds checking. + * Fractional and non-finite cells raise value in both modes. */ + if (!buffer_index_at(indices, i, &di, "scatter_add")) return make_null(); if (g_has_error) return make_null(); } if (values->type == VAL_LIST) { ValType vt = list_elem_type(values, i); - if (vt != VAL_NUM) { + if ((vt != VAL_FLOAT && vt != VAL_INT)) { rt_error(EK_TYPE, 0, "scatter_add: value %d is %s (expected a number)", i, val_type_name(vt)); return make_null(); } - v = eigs_slot_num(list_slot(values, i), __func__); + v = eigs_slot_float(list_slot(values, i), "scatter_add"); } else if (values->type == VAL_BUFFER) { v = values->data.buffer.data[i]; } else { - v = eigs_num_arg(values, __func__); + v = eigs_float_arg(values, "scatter_add"); } - /* Check the double before converting it to int. In particular, - * an out-of-range floating-to-integer conversion is undefined C - * behavior, and indices can come from sandboxed bytecode. */ + /* N5 has established int64 exactness; check bounds before narrowing. */ int index_limit = per_row ? cols : dst->data.buffer.count; - int valid_index = isfinite(di) && di >= 0.0 && di < (double)index_limit; + if (g_has_error) return make_null(); + int valid_index = di >= 0 && di < index_limit; int idx = 0; if (valid_index) { - /* The range check makes this conversion representable. Check - * both directions instead of calling trunc(), which is not - * part of the freestanding runtime's mini-libm surface. */ + /* Bounds prove that the storage offset fits int. */ idx = (int)di; - valid_index = di >= (double)idx && di <= (double)idx; + valid_index = di == idx; } if (!valid_index) { if (per_row) { - rt_error(EK_INDEX, 0, "scatter_add: column index %.17g out of range for row %d (cols %d)", - di, i, cols); + rt_error(EK_INDEX, 0, "scatter_add: column index %lld out of range for row %d (cols %d)", + (long long)di, i, cols); } else { - rt_error(EK_INDEX, 0, "scatter_add: index %.17g out of range (length %d)", - di, index_limit); + rt_error(EK_INDEX, 0, "scatter_add: index %lld out of range (length %d)", + (long long)di, index_limit); } return make_null(); } @@ -1088,7 +1148,7 @@ Value* builtin_tensor_scatter_add(Value *arg) { Value* builtin_tensor_softmax(Value *arg) { BOOL_REFUSE(arg, "softmax"); /* #632: softmax of a single element normalizes to 1.0. */ - if (arg && arg->type == VAL_NUM) return make_num(1.0); + if (arg && val_is_number(arg)) return make_float(1.0); /* flat-buffer fast path (#973): row-wise on the shape, 1-D is one row; * same ne_softmax_buf kernel as the list path, so byte-identical. */ if (arg && arg->type == VAL_BUFFER) { @@ -1122,13 +1182,13 @@ Value* builtin_tensor_log_softmax(Value *arg) { Value *tensor = arg; if (arg && arg->type == VAL_LIST && arg->data.list.count == 2 && list_elem_type(arg, 0) == VAL_LIST && - list_elem_type(arg, 1) == VAL_NUM) + (list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT)) tensor = list_get_ref(arg, 0); /* [tensor, dim] form */ /* #632: log(softmax(scalar)) = log(1) = 0. */ /* fs:ANSWER softmax of a single element is 1 and log(1) is 0, so 0.0 is * the arithmetic result for a scalar argument — a NUMBER is a valid * argument here, which is what makes this not a type guard (#632). */ - if (tensor && tensor->type == VAL_NUM) return make_num(0.0); + if (tensor && val_is_number(tensor)) return make_float(0.0); /* flat-buffer fast path (#973): same kernel + the same #865 clamp. */ if (tensor && tensor->type == VAL_BUFFER) { Value *res = make_buffer_like(tensor); @@ -1162,9 +1222,9 @@ Value* builtin_tensor_log_softmax(Value *arg) { Value* builtin_tensor_relu(Value *arg) { BOOL_REFUSE(arg, "relu"); /* #632: a scalar is the degenerate element-wise case, like sqrt/exp/log. */ - if (arg && arg->type == VAL_NUM) { - double x = eigs_num_arg(arg, __func__); - return make_num(x < 0.0 ? 0.0 : x); + if (arg && val_is_number(arg)) { + double x = eigs_float_arg(arg, __func__); + return make_float(x < 0.0 ? 0.0 : x); } /* #1093: buffers go through the same flatten path and come back as * buffers via flat_to_like — one implementation, not two. */ @@ -1183,9 +1243,9 @@ Value* builtin_tensor_relu(Value *arg) { Value* builtin_tensor_leaky_relu(Value *arg) { BOOL_REFUSE(arg, "leaky_relu"); /* #632: scalar is the degenerate element-wise case. */ - if (arg && arg->type == VAL_NUM) { - double x = eigs_num_arg(arg, __func__); - return make_num(x < 0.0 ? 0.01 * x : x); + if (arg && val_is_number(arg)) { + double x = eigs_float_arg(arg, __func__); + return make_float(x < 0.0 ? 0.01 * x : x); } /* flat-buffer fast path (#973), the twin of relu's. */ if (arg && arg->type == VAL_BUFFER) { @@ -1216,14 +1276,14 @@ Value* builtin_tensor_mean(Value *arg) { /* fs:EMPTY the mean over zero elements, as for `mean of []` below; * the division would be 0/0. A buffer is a valid argument, so this * is not a type guard and strict must not raise. */ - if (n == 0) return make_num(0.0); + if (n == 0) return make_float(0.0); double s = 0.0; for (int i = 0; i < n; i++) { - double x = buffer_read_num(arg, i); + double x = buffer_read_f64(arg, i); if (g_has_error) return make_null(); s = num_guard(s + x); } - return make_num(s / n); + return make_float(s / n); } /* Split from the empty case below. `tensor_total` answers 0 for an empty * list AND for any non-tensor — a string, a dict, a function — so one @@ -1233,14 +1293,14 @@ Value* builtin_tensor_mean(Value *arg) { * tagged fs:EMPTY, which would have blessed the laundering permanently), * so the type half is hoisted out. Non-strict is byte-identical: both * halves still answer 0.0. Closes the main half of #1008. */ - ARG_GUARD(arg && arg->type != VAL_NUM && arg->type != VAL_LIST + ARG_GUARD(arg && !val_is_number(arg) && arg->type != VAL_LIST && arg->type != VAL_BUFFER, /* #1093 */ - "mean", "a number, a list of numbers or a buffer", make_num(0.0)); + "mean", "a number, a list of numbers or a buffer", make_float(0.0)); int total = tensor_total(arg); /* fs:EMPTY nothing to average, and the division below would be 0/0. The * non-tensor case is gone (guarded above), so this line now carries one * verdict only: `mean of []` is the empty mean and strict must not raise. */ - if (total == 0) return make_num(0.0); + if (total == 0) return make_float(0.0); double *flat = xcalloc(total, sizeof(double)); int idx = 0; if (!tensor_flatten_recursive(arg, flat, &idx)) { @@ -1250,50 +1310,74 @@ Value* builtin_tensor_mean(Value *arg) { double sum = 0.0; for (int i = 0; i < total; i++) sum += flat[i]; free(flat); - return make_num(sum / total); + return make_float(sum / total); } /* ==== BUILTIN: sum ==== */ /* sum / norm are association-unspecified reductions (see docs/SPEC.md * "Reductions"): the summation order is not guaranteed, which lets an * optimizing backend reassociate across SIMD lanes. no-NaN/Inf preserved. */ -Value* builtin_tensor_sum(Value *arg) { - if (arg && arg->type == VAL_BUFFER) { - int n = arg->data.buffer.count; - double s = 0.0; - for (int i = 0; i < n; i++) { - double x = buffer_read_num(arg, i); - if (g_has_error) return make_null(); - s = num_guard(s + x); - } - return make_num(s); +/* C2/C4 and (A): decide the reduction kind before adding. A later float + * makes the whole reduction floating, so an int prefix must not overflow. */ +static int tensor_sum_has_float(Value *v) { + if (!v) return 0; + /* tensor_total previously checked every bool before any accumulation. + * Preserve that validation order while deciding the numeric kind. */ + if (v->type == VAL_BOOL && !g_has_error) + rt_error(EK_TYPE, 0, "cannot use a bool as a number (branch on it: `if b:`)"); + if (v->type == VAL_FLOAT || v->type == VAL_BUFFER) return 1; + if (v->type != VAL_LIST) return 0; + int has_float = 0; + for (int i = 0; i < v->data.list.count; i++) { + EIGS_VIEW(child); + has_float |= tensor_sum_has_float(list_get_view(v, i, &child)); } - /* Split from the empty case below. `tensor_total` answers 0 for an empty - * list AND for any non-tensor — a string, a dict, a function — so one - * `total == 0` line was carrying two opposite verdicts: `sum of []` - * is the identity, `sum of "hello"` is a laundered type mistake. The - * mixed line could not be classified honestly (a blind review caught it - * tagged fs:EMPTY, which would have blessed the laundering permanently), - * so the type half is hoisted out. Non-strict is byte-identical: both - * halves still answer 0.0. Closes the main half of #1008. */ - ARG_GUARD(arg && arg->type != VAL_NUM && arg->type != VAL_LIST - && arg->type != VAL_BUFFER, /* #1093 */ - "sum", "a number, a list of numbers or a buffer", make_num(0.0)); - int total = tensor_total(arg); - /* fs:EMPTY 0.0 is the additive identity this loop would accumulate over - * zero elements. The non-tensor case is guarded above, so `sum of []` is - * the only reading left here. */ - if (total == 0) return make_num(0.0); - double *flat = xcalloc(total, sizeof(double)); - int idx = 0; - if (!tensor_flatten_recursive(arg, flat, &idx)) { - free(flat); - return make_null(); + return has_float; +} + +static int tensor_sum_into(Value *v, EigsNum *sum) { + if (!v) return 1; + if (val_is_number(v)) { + EigsNum n = eigs_number_arg(v, "sum"); + if (g_has_error) return 0; + if (sum->is_int) { + int64_t result; + if (__builtin_add_overflow(sum->value.i64, n.value.i64, &result)) { + rt_error(EK_VALUE, 0, "sum: int64 overflow in '+'"); + return 0; + } + sum->value.i64 = result; + } else { + double d = n.is_int ? (double)n.value.i64 : n.value.f64; + sum->value.f64 = num_guard(sum->value.f64 + d); + if (g_has_error) return 0; + } + } else if (v->type == VAL_BUFFER) { + for (int i = 0; i < v->data.buffer.count; i++) { + double d = buffer_read_f64(v, i); + if (g_has_error) return 0; + sum->value.f64 = num_guard(sum->value.f64 + d); + if (g_has_error) return 0; + } + } else if (v->type == VAL_LIST) { + for (int i = 0; i < v->data.list.count; i++) { + EIGS_VIEW(child); + if (!tensor_sum_into(list_get_view(v, i, &child), sum)) return 0; + } } - double sum = 0.0; - for (int i = 0; i < total; i++) sum = num_guard(sum + flat[i]); - free(flat); - return make_num(sum); + return 1; +} + +Value* builtin_tensor_sum(Value *arg) { + ARG_GUARD(arg && !val_is_number(arg) && arg->type != VAL_LIST && arg->type != VAL_BUFFER, + "sum", "a number, a list of numbers or a buffer", make_float(0.0)); + EigsNum sum = { .is_int = !tensor_sum_has_float(arg), .value = { .i64 = 0 } }; + if (g_has_error) return make_null(); + /* Explicitly initialize the active union arm rather than assuming all-bits + * zero is the floating identity on every target. */ + if (!sum.is_int) sum.value.f64 = 0.0; + if (!tensor_sum_into(arg, &sum)) return make_null(); + return sum.is_int ? make_int(sum.value.i64) : make_float(sum.value.f64); } /* norm of a → L2 (Euclidean) norm = sqrt(sum_i a[i]^2). Buffers and tensors. */ @@ -1302,11 +1386,11 @@ Value* builtin_tensor_norm(Value *arg) { int n = arg->data.buffer.count; double s = 0.0; for (int i = 0; i < n; i++) { - double x = buffer_read_num(arg, i); + double x = buffer_read_f64(arg, i); if (g_has_error) return make_null(); s = num_guard(s + num_guard(x * x)); } - return make_num(num_guard(sqrt(s))); + return make_float(num_guard(sqrt(s))); } /* Split from the empty case below. `tensor_total` answers 0 for an empty * list AND for any non-tensor — a string, a dict, a function — so one @@ -1316,13 +1400,13 @@ Value* builtin_tensor_norm(Value *arg) { * tagged fs:EMPTY, which would have blessed the laundering permanently), * so the type half is hoisted out. Non-strict is byte-identical: both * halves still answer 0.0. Closes the main half of #1008. */ - ARG_GUARD(arg && arg->type != VAL_NUM && arg->type != VAL_LIST + ARG_GUARD(arg && !val_is_number(arg) && arg->type != VAL_LIST && arg->type != VAL_BUFFER, /* #1093 */ - "norm", "a number, a list of numbers or a buffer", make_num(0.0)); + "norm", "a number, a list of numbers or a buffer", make_float(0.0)); int total = tensor_total(arg); /* fs:EMPTY the L2 norm over zero elements is sqrt(0) = 0, exactly what the * loop below would produce. The non-tensor case is guarded above. */ - if (total == 0) return make_num(0.0); + if (total == 0) return make_float(0.0); double *flat = xcalloc(total, sizeof(double)); int idx = 0; if (!tensor_flatten_recursive(arg, flat, &idx)) { @@ -1332,7 +1416,7 @@ Value* builtin_tensor_norm(Value *arg) { double s = 0.0; for (int i = 0; i < total; i++) s = num_guard(s + num_guard(flat[i] * flat[i])); free(flat); - return make_num(num_guard(sqrt(s))); + return make_float(num_guard(sqrt(s))); } /* #292: bytes a list-of-fresh-numbers tensor of `n` elements costs — one Value @@ -1353,8 +1437,9 @@ Value* builtin_tensor_zeros(Value *arg) { * the list cost a Value per element on the VM and 12.4x on the AOT * (ouroboros#170). make_shaped_buffer carries the sandbox charge, which * is now 8 bytes/element instead of TENSOR_LIST_ELEM_BYTES. */ - if (arg->type == VAL_NUM) { - int64_t n64 = (int64_t)eigs_num_arg(arg, __func__); + if (val_is_number(arg)) { + int64_t n64 = eigs_int_arg(arg, "zeros"); + if (g_has_error) return make_null(); if (n64 < 0) n64 = 0; if (n64 > EIGS_TENSOR_MAX_ELEMENTS) n64 = EIGS_TENSOR_MAX_ELEMENTS; /* #292: cap like fill/buffer (was uncapped → x_oom/abort) */ Value *out = make_shaped_buffer(0, (int)n64); @@ -1362,10 +1447,11 @@ Value* builtin_tensor_zeros(Value *arg) { } /* zeros of [rows, cols] → 2D */ if (arg->type == VAL_LIST && arg->data.list.count >= 2 - && list_elem_type(arg, 0) == VAL_NUM - && list_elem_type(arg, 1) == VAL_NUM) { - int64_t rows64 = (int64_t)eigs_list_num(arg, 0, __func__); - int64_t cols64 = (int64_t)eigs_list_num(arg, 1, __func__); + && (list_elem_type(arg, 0) == VAL_FLOAT || list_elem_type(arg, 0) == VAL_INT) + && (list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT)) { + int64_t rows64 = eigs_list_int(arg, 0, "zeros"); + int64_t cols64 = eigs_list_int(arg, 1, "zeros"); + if (g_has_error) return make_null(); if (rows64 < 0) rows64 = 0; if (cols64 < 0) cols64 = 0; /* #292: bound each dim before multiplying (no int64 overflow), then the @@ -1378,7 +1464,7 @@ Value* builtin_tensor_zeros(Value *arg) { Value *outer = make_list(rows); for (int r = 0; r < rows; r++) { Value *row = make_list(cols); - for (int c = 0; c < cols; c++) list_append_owned(row, make_num(0.0)); + for (int c = 0; c < cols; c++) list_append_owned(row, make_float(0.0)); list_append_owned(outer, row); } return outer; @@ -1393,7 +1479,7 @@ Value* builtin_tensor_zeros_like(Value *arg) { /* fs:LITERAL a number is a valid argument and this IS the value being * constructed — the zero of the same shape, which for a scalar is 0.0. * It is the scalar leaf of the recursion below, not a guard. */ - if (arg->type == VAL_NUM) return make_num(0.0); + if (val_is_number(arg)) return make_float(0.0); if (arg->type == VAL_LIST) { Value *out = make_list(arg->data.list.count); for (int i = 0; i < arg->data.list.count; i++) { @@ -1410,12 +1496,11 @@ Value* builtin_tensor_zeros_like(Value *arg) { } /* Every shape zeros_like can mirror exits above, so `arg` is none of them * (a string, a dict, a function). */ - ARG_GUARD(1, "zeros_like", "a number, a list or a buffer", make_num(0.0)); + ARG_GUARD(1, "zeros_like", "a number, a list or a buffer", make_float(0.0)); } -/* #1093: an index vector is a flat numeric tensor, so it may be a list or a - * buffer. A non-numeric list element reads as -1, the out-of-range sentinel - * the index loops already skip on. */ +/* Index vectors accept lists and buffers. N5 checks exactness before the + * caller applies its bounds policy; retain int64 until that check. */ static int flat_count(Value *v) { if (!v) return 0; if (v->type == VAL_LIST) return v->data.list.count; @@ -1425,24 +1510,18 @@ static int flat_count(Value *v) { static int flat_is_vector(Value *v) { return v && (v->type == VAL_LIST || v->type == VAL_BUFFER); } -static int flat_index_at(Value *v, int i) { +static int64_t flat_index_at(Value *v, int i, const char *who) { if (v->type == VAL_LIST) - return (list_elem_type(v, i) == VAL_NUM) - ? (int)eigs_slot_num(list_slot(v, i), __func__) : -1; - /* #1417: normalize before conversion, and let every caller propagate a - * raised read before using its index or touching an output. Saturated - * infinity is still outside the int domain and uses the existing invalid - * index sentinel. Ordinary representable finite indices keep truncating. */ - double index = buffer_read_num(v, i); - if (g_has_error) return -1; - if (index < (double)INT_MIN || index > (double)INT_MAX) return -1; - return (int)index; + return eigs_slot_int(list_slot(v, i), who); + int64_t index = 0; + if (!buffer_index_at(v, i, &index, who)) return 0; + return index; } /* #973: a non-numeric element of an index LIST has no index, and reporting it * as "index -1 out of range" would name the wrong fault. Buffers hold doubles, * so every element is a number by construction. */ static int flat_index_is_num(Value *v, int i) { - return v->type != VAL_LIST || list_elem_type(v, i) == VAL_NUM; + return v->type != VAL_LIST || (list_elem_type(v, i) == VAL_FLOAT || list_elem_type(v, i) == VAL_INT); } /* Validate and copy selectors in one pass. Sparse writers and numerical-loss @@ -1460,18 +1539,18 @@ static int snapshot_flat_indices(Value *indices, int limit, const char *name, free(snapshot); return 0; } - int index = flat_index_at(indices, i); + int64_t index = flat_index_at(indices, i, name); if (g_has_error) { free(snapshot); return 0; } if (index < 0 || index >= limit) { - rt_error(EK_INDEX, 0, "%s: %s index %d out of range (length %d)", - name, axis, index, limit); + rt_error(EK_INDEX, 0, "%s: %s index %lld out of range (length %d)", + name, axis, (long long)index, limit); free(snapshot); return 0; } - snapshot[i] = index; + snapshot[i] = (int)index; } *snapshot_out = snapshot; *count_out = count; @@ -1537,14 +1616,15 @@ Value* builtin_tensor_gather(Value *arg) { * row selects one element per row and yields a buffer; an unshaped (1-D) * buffer with a scalar index yields that element. */ if (tensor->type == VAL_BUFFER) { - if (indices->type == VAL_NUM && tensor->data.buffer.rows == 0) { - int idx = (int)eigs_num_arg(indices, __func__); + if (val_is_number(indices) && tensor->data.buffer.rows == 0) { + int64_t idx = eigs_int_arg(indices, "gather"); + if (g_has_error) return make_null(); if (idx < 0 || idx >= tensor->data.buffer.count) { - rt_error(EK_INDEX, 0, "gather: index %d out of range (length %d)", - idx, tensor->data.buffer.count); + rt_error(EK_INDEX, 0, "gather: index %lld out of range (length %d)", + (long long)idx, tensor->data.buffer.count); return make_null(); } - return make_num(buffer_read_num(tensor, idx)); + return make_float(buffer_read_f64(tensor, idx)); } else if (flat_is_vector(indices)) { int shaped = tensor->data.buffer.rows > 0; int rows = shaped ? tensor->data.buffer.rows : tensor->data.buffer.count; @@ -1561,13 +1641,13 @@ Value* builtin_tensor_gather(Value *arg) { i, val_type_name(list_elem_type(indices, i))); return make_null(); } - int idx = flat_index_at(indices, i); + int64_t idx = flat_index_at(indices, i, "gather"); if (g_has_error) { val_decref(out); return make_null(); } if (idx < 0 || idx >= cols) { val_decref(out); rt_error(EK_INDEX, 0, - "gather: column index %d out of range for row %d (cols %d)", - idx, i, cols); + "gather: column index %lld out of range for row %d (cols %d)", + (long long)idx, i, cols); return make_null(); } out->data.buffer.data[i] = tensor->data.buffer.data[(int64_t)i * cols + idx]; @@ -1587,46 +1667,48 @@ Value* builtin_tensor_gather(Value *arg) { /* #1637: a bool where a row belongs raises, in every mode. */ if (row_t == VAL_BOOL) { val_decref(out); - eigs_num_arg_slow(list_get_ref(tensor, i), "gather"); + eigs_float_arg(list_get_ref(tensor, i), "gather"); return make_null(); } - list_append_owned(out, make_num(0.0)); + list_append_owned(out, make_float(0.0)); continue; } Value *row = list_get_ref(tensor, i); - if (list_elem_type(indices, i) != VAL_NUM) { + if ((list_elem_type(indices, i) != VAL_FLOAT && list_elem_type(indices, i) != VAL_INT)) { val_decref(out); rt_error(EK_TYPE, 0, "gather: index %d is %s (expected a number)", i, val_type_name(list_elem_type(indices, i))); return make_null(); } - int idx = (int)eigs_slot_num(list_slot(indices, i), __func__); + int64_t idx = eigs_slot_int(list_slot(indices, i), "gather"); + if (g_has_error) { val_decref(out); return make_null(); } if (idx < 0 || idx >= row->data.list.count) { val_decref(out); rt_error(EK_INDEX, 0, - "gather: column index %d out of range for row %d (cols %d)", - idx, i, row->data.list.count); + "gather: column index %lld out of range for row %d (cols %d)", + (long long)idx, i, row->data.list.count); return make_null(); } /* #1637 round 4: the selected cell is read as a number -- a * bool there raised nothing and read as 0 (critic r3). */ - double cell = eigs_slot_elem_num(list_slot(row, idx), "gather"); + EigsNum cell = eigs_slot_elem_number(list_slot(row, (int)idx), "gather"); if (g_has_error) { val_decref(out); return make_null(); } - list_append_owned(out, make_num(cell)); + list_append_owned(out, cell.is_int ? make_int(cell.value.i64) : make_float(cell.value.f64)); } return out; } /* 1D tensor, scalar index */ - if (tensor->type == VAL_LIST && indices->type == VAL_NUM) { - int idx = (int)eigs_num_arg(indices, __func__); + if (tensor->type == VAL_LIST && val_is_number(indices)) { + int64_t idx = eigs_int_arg(indices, "gather"); + if (g_has_error) return make_null(); if (idx < 0 || idx >= tensor->data.list.count) { - rt_error(EK_INDEX, 0, "gather: index %d out of range (length %d)", - idx, tensor->data.list.count); + rt_error(EK_INDEX, 0, "gather: index %lld out of range (length %d)", + (long long)idx, tensor->data.list.count); return make_null(); } - double cell = eigs_slot_elem_num(list_slot(tensor, idx), "gather"); /* #1637 round 4 */ + EigsNum cell = eigs_slot_elem_number(list_slot(tensor, (int)idx), "gather"); /* #1637 round 4 */ if (g_has_error) return make_null(); - return make_num(cell); + return cell.is_int ? make_int(cell.value.i64) : make_float(cell.value.f64); } /* The fs:TODO #971 left here is resolved by the raise above: the two * readings that shared this line are separated. Out-of-range no longer @@ -1635,8 +1717,9 @@ Value* builtin_tensor_gather(Value *arg) { * neither a vector nor a number — and it converts to ARG_GUARD like every * other wrong-type case: a raise in strict mode (the default, #1361), * 0.0 under EIGS_STRICT=0. */ - ARG_GUARD(1, "gather", "a tensor and an index or index vector", make_num(0.0)); - return make_null(); + ARG_GUARD(1, "gather", "a tensor and an index or index vector", make_float(0.0)); + return make_null(); /* unreachable: the guard above always returns, but + * -O0 does not fold its constant test (#1775) */ } /* ==== Helper: call a user-defined EigenScript function from C ==== @@ -1726,59 +1809,44 @@ Value* call_eigs_fn(Value *fn, Value *arg) { /* random_normal of [rows, cols, scale] → 2D, or random_normal of [len, scale] → 1D */ Value* builtin_random_normal(Value *arg) { STRICT_LIST_MAX(arg, 3, "random_normal"); - BOOL_REFUSE(arg, "random_normal"); /* before the tape: a type error is not recorded */ - /* #1750: argument validation is the deterministic, fail-fast half of the - * call and therefore belongs before the nondeterministic tape boundary. - * eigs_list_num used to discover a bad dimension/scale after TAKE; the - * live run then recorded the partially constructed stand-in and strict - * replay returned that value instead of raising. Validate every element - * the selected overload will read before a replay can consume a record. */ - if (arg && arg->type == VAL_LIST && - (arg->data.list.count == 2 || arg->data.list.count == 3)) { - for (int i = 0; i < arg->data.list.count; i++) { - if (list_elem_type(arg, i) != VAL_NUM) { - (void)eigs_list_num(arg, i, __func__); - return make_null(); - } - } + /* #1750: every dimension and scale reader precedes the tape seam. */ + BOOL_REFUSE(arg, "random_normal"); + int argc = arg && arg->type == VAL_LIST ? arg->data.list.count : 0; + int rows = 0, cols = 0; + double scale = 1.0; + if (argc == 2 || argc == 3) { + int64_t r, c = 1; + if (!eigs_list_int_range(arg, 0, INT_MIN, INT_MAX, &r, "random_normal")) + return make_null(); + if (argc == 3 && !eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &c, "random_normal")) + return make_null(); + scale = eigs_list_float(arg, argc - 1, "random_normal"); + if (g_has_error) return make_null(); + rows = (int)r; + cols = (int)c; } TRACE_NONDET_TAKE("random_normal"); - if (!arg || arg->type != VAL_LIST) TRACE_NONDET_RECORD("random_normal", make_null()); - /* #960: draw from the shared drand48 stream that `seed_random` pins, not - * libc generator (formerly seeded only by main from the clock, so a - * randn-initialised tensor was unreproducible from script). After the TAKE - * above: a replayed call serves its record without touching the stream. */ - int argc = arg->data.list.count; if (argc == 3) { - /* 2D: [rows, cols, scale] */ - int rows = (int)eigs_list_num(arg, 0, __func__); - int cols = (int)eigs_list_num(arg, 1, __func__); - double scale = eigs_list_num(arg, 2, __func__); Value *outer = make_list(rows); for (int r = 0; r < rows; r++) { Value *row = make_list(cols); for (int c = 0; c < cols; c++) { - /* Box-Muller. 1 - drand48() lands in (0, 1]: drand48 can - * return exactly 0, and log(0) is an infinity. */ double u1 = 1.0 - eigs_random_double(); double u2 = eigs_random_double(); double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); - list_append_owned(row, make_num(z * scale)); + list_append_owned(row, make_float(z * scale)); } list_append_owned(outer, row); } TRACE_NONDET_RECORD("random_normal", outer); } if (argc == 2) { - /* 1D: [len, scale] */ - int len = (int)eigs_list_num(arg, 0, __func__); - double scale = eigs_list_num(arg, 1, __func__); - Value *out = make_list(len); - for (int i = 0; i < len; i++) { - double u1 = 1.0 - eigs_random_double(); /* (0, 1] — see the 2D branch */ + Value *out = make_list(rows); + for (int i = 0; i < rows; i++) { + double u1 = 1.0 - eigs_random_double(); double u2 = eigs_random_double(); double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); - list_append_owned(out, make_num(z * scale)); + list_append_owned(out, make_float(z * scale)); } TRACE_NONDET_RECORD("random_normal", out); } @@ -1789,7 +1857,7 @@ Value* builtin_random_normal(Value *arg) { /* shape of tensor → [rows, cols] for 2D, [len] for 1D */ Value* builtin_tensor_shape(Value *arg) { if (!arg) return make_null(); - if (arg->type == VAL_NUM) { + if (val_is_number(arg)) { Value *out = make_list(0); return out; /* scalar: empty shape */ } @@ -1797,18 +1865,18 @@ Value* builtin_tensor_shape(Value *arg) { /* shaped buffer -> [rows, cols]; unshaped -> [count] */ if (arg->data.buffer.rows > 0) { Value *out = make_list(2); - list_append_owned(out, make_num(arg->data.buffer.rows)); - list_append_owned(out, make_num(arg->data.buffer.cols)); + list_append_owned(out, make_int(arg->data.buffer.rows)); + list_append_owned(out, make_int(arg->data.buffer.cols)); return out; } Value *out = make_list(1); - list_append_owned(out, make_num(arg->data.buffer.count)); + list_append_owned(out, make_int(arg->data.buffer.count)); return out; } if (arg->type != VAL_LIST) return make_null(); if (arg->data.list.count == 0) { Value *out = make_list(1); - list_append_owned(out, make_num(0)); + list_append_owned(out, make_int(0)); return out; } if (list_elem_type(arg, 0) == VAL_LIST) { @@ -1816,13 +1884,13 @@ Value* builtin_tensor_shape(Value *arg) { Value *first = list_get_view(arg, 0, &first_view); /* 2D */ Value *out = make_list(2); - list_append_owned(out, make_num(arg->data.list.count)); - list_append_owned(out, make_num(first->data.list.count)); + list_append_owned(out, make_int(arg->data.list.count)); + list_append_owned(out, make_int(first->data.list.count)); return out; } /* 1D */ Value *out = make_list(1); - list_append_owned(out, make_num(arg->data.list.count)); + list_append_owned(out, make_int(arg->data.list.count)); return out; } @@ -1831,8 +1899,8 @@ static double numerical_loss(Value *loss_fn, Value *arg, const char *who, int *loss_valid) { if (!*loss_valid) return 0.0; Value *loss = call_eigs_fn(loss_fn, arg); - if (loss && loss->type == VAL_NUM) { - double result = eigs_num_arg(loss, __func__); + if (loss && val_is_number(loss)) { + double result = eigs_float_arg(loss, who); val_decref(loss); return result; } @@ -1850,13 +1918,13 @@ static double numerical_loss(Value *loss_fn, Value *arg, const char *who, * was just perturbed. For matrix rows, also validate the parent shape and * identity. The caller retains container so restoration never targets a * callback's replacement row. */ -/* Perturb one cell and return the value actually STORED there. list_set_num +/* Perturb one cell and return the value actually STORED there. list_set_f64 * normalizes through num_guard (it clamps beyond +-EIGS_NUM_MAX), so the failure * path must compare the cell with what was stored, not with old_val +- eps. */ static double gradient_perturb(Value *container, int index, double value) { - list_set_num(container, index, value); + list_set_f64(container, index, value); double stored = value; - list_get_num(container, index, &stored); + list_get_f64(container, index, &stored); return stored; } @@ -1880,8 +1948,8 @@ static int gradient_container_unchanged(Value *container, int expected_len, double current_val; if (container && container->type == VAL_LIST && index >= 0 && index < container->data.list.count && - list_get_num(container, index, ¤t_val) && current_val == written_val) - list_set_num(container, index, old_val); + list_get_f64(container, index, ¤t_val) && current_val == written_val) + list_set_f64(container, index, old_val); rt_error(EK_VALUE, 0, "param mutated during gradient"); return 0; } @@ -1908,12 +1976,12 @@ static int tensor_cells_numeric(const Value *v, const char *who) { const Value *e = et == VAL_LIST ? list_get_ref(v, i) : NULL; if (et == VAL_LIST) { for (int c = 0; c < e->data.list.count; c++) - if (list_elem_type(e, c) != VAL_NUM) { - eigs_slot_num(list_slot(e, c), who); + if ((list_elem_type(e, c) != VAL_FLOAT && list_elem_type(e, c) != VAL_INT)) { + eigs_slot_float(list_slot(e, c), who); return 0; } - } else if (et != VAL_NUM) { - eigs_slot_num(list_slot(v, i), who); + } else if ((et != VAL_FLOAT && et != VAL_INT)) { + eigs_slot_float(list_slot(v, i), who); return 0; } } @@ -1930,7 +1998,7 @@ Value* builtin_numerical_grad(Value *arg) { /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_slot_opt_num(list_slot(arg, 2), &eps, __func__); + eigs_slot_opt_float(list_slot(arg, 2), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(param, "numerical_grad")) return make_null(); if (eps <= 0) eps = 0.001; @@ -1966,7 +2034,7 @@ Value* builtin_numerical_grad(Value *arg) { int len = param->data.list.count; Value *grad = make_list(len); for (int i = 0; i < len; i++) { - double old_val = eigs_slot_num(list_slot(param, i), __func__); + double old_val = eigs_slot_float(list_slot(param, i), __func__); double written = gradient_perturb(param, i, old_val + eps); double lp = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); if (!gradient_container_unchanged(param, len, NULL, 0, -1, i, old_val, written)) @@ -1975,9 +2043,9 @@ Value* builtin_numerical_grad(Value *arg) { double lm = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); if (!gradient_container_unchanged(param, len, NULL, 0, -1, i, old_val, written)) goto list_grad_failed; - list_set_num(param, i, old_val); + list_set_f64(param, i, old_val); /* Central difference */ - list_append_owned(grad, make_num((lp - lm) / (2.0 * eps))); + list_append_owned(grad, make_float((lp - lm) / (2.0 * eps))); } val_decref(nul); if (!loss_valid) { val_decref(grad); return make_null(); } @@ -1999,7 +2067,7 @@ Value* builtin_numerical_grad(Value *arg) { Value *grad_row = make_list(cols); unfinished_grad_row = grad_row; for (int c = 0; c < cols; c++) { - double old_val = eigs_slot_num(list_slot(row, c), __func__); + double old_val = eigs_slot_float(list_slot(row, c), __func__); val_incref(row); double written = gradient_perturb(row, c, old_val + eps); double lp = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); @@ -2011,9 +2079,9 @@ Value* builtin_numerical_grad(Value *arg) { if (!gradient_container_unchanged(row, cols, param, rows, r, c, old_val, written)) { val_decref(row); goto matrix_grad_failed; } - list_set_num(row, c, old_val); + list_set_f64(row, c, old_val); val_decref(row); - list_append_owned(grad_row, make_num((lp - lm) / (2.0 * eps))); + list_append_owned(grad_row, make_float((lp - lm) / (2.0 * eps))); } list_append_owned(grad, grad_row); unfinished_grad_row = NULL; @@ -2040,7 +2108,7 @@ Value* builtin_sgd_update(Value *arg) { /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_slot_opt_num(list_slot(arg, 2), &lr, __func__); + eigs_slot_opt_float(list_slot(arg, 2), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(param, "sgd_update") || !tensor_cells_numeric(grad, "sgd_update")) return make_null(); @@ -2061,9 +2129,9 @@ Value* builtin_sgd_update(Value *arg) { int len = param->data.list.count < grad->data.list.count ? param->data.list.count : grad->data.list.count; for (int i = 0; i < len; i++) { - double pv = eigs_slot_num(list_slot(param, i), __func__); - double gv = eigs_slot_num(list_slot(grad, i), __func__); - list_set_num(param, i, pv - lr * gv); + double pv = eigs_slot_float(list_slot(param, i), __func__); + double gv = eigs_slot_float(list_slot(grad, i), __func__); + list_set_f64(param, i, pv - lr * gv); } } else { /* 2D */ @@ -2076,9 +2144,9 @@ Value* builtin_sgd_update(Value *arg) { int cols = pr->data.list.count < gr->data.list.count ? pr->data.list.count : gr->data.list.count; for (int c = 0; c < cols; c++) { - double pv = eigs_slot_num(list_slot(pr, c), __func__); - double gv = eigs_slot_num(list_slot(gr, c), __func__); - list_set_num(pr, c, pv - lr * gv); + double pv = eigs_slot_float(list_slot(pr, c), __func__); + double gv = eigs_slot_float(list_slot(gr, c), __func__); + list_set_f64(pr, c, pv - lr * gv); } } } @@ -2102,7 +2170,7 @@ Value* builtin_numerical_grad_rows(Value *arg) { /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_slot_opt_num(list_slot(arg, 3), &eps, __func__); + eigs_slot_opt_float(list_slot(arg, 3), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "numerical_grad_rows") || !tensor_cells_numeric(row_indices, "numerical_grad_rows")) return make_null(); if (eps <= 0) eps = 0.001; @@ -2155,7 +2223,7 @@ Value* builtin_numerical_grad_rows(Value *arg) { for (int r = 0; r < rows; r++) { Value *grow = make_list(cols); for (int c = 0; c < cols; c++) - list_append_owned(grow, make_num(0.0)); + list_append_owned(grow, make_float(0.0)); list_append_owned(grad, grow); } /* #1722: make_list caps capacity at its 8-slot fallback when the sandbox @@ -2176,7 +2244,7 @@ Value* builtin_numerical_grad_rows(Value *arg) { Value *grad_row = list_get_ref(grad, r); for (int c = 0; c < cols && c < row->data.list.count; c++) { - double old_val = eigs_slot_num(list_slot(row, c), __func__); + double old_val = eigs_slot_float(list_slot(row, c), __func__); val_incref(row); double written = gradient_perturb(row, c, old_val + eps); double loss_plus = numerical_loss(loss_fn, nul, "numerical_grad_rows", &loss_valid); @@ -2188,10 +2256,10 @@ Value* builtin_numerical_grad_rows(Value *arg) { if (!gradient_container_unchanged(row, row_len, matrix, rows, r, c, old_val, written)) { val_decref(row); goto rows_grad_failed; } - list_set_num(row, c, old_val); + list_set_f64(row, c, old_val); val_decref(row); /* gradient — release the zero placeholder this slot held */ - list_set_num(grad_row, c, (loss_plus - loss_minus) / (2.0 * eps)); + list_set_f64(grad_row, c, (loss_plus - loss_minus) / (2.0 * eps)); } } free(row_snapshot); @@ -2220,7 +2288,7 @@ Value* builtin_sgd_update_rows(Value *arg) { /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_slot_opt_num(list_slot(arg, 3), &lr, __func__); + eigs_slot_opt_float(list_slot(arg, 3), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "sgd_update_rows") || !tensor_cells_numeric(grad, "sgd_update_rows") || !tensor_cells_numeric(row_indices, "sgd_update_rows")) return make_null(); @@ -2264,9 +2332,9 @@ Value* builtin_sgd_update_rows(Value *arg) { int cols = mrow->data.list.count < grow->data.list.count ? mrow->data.list.count : grow->data.list.count; for (int c = 0; c < cols; c++) { - double pv = eigs_slot_num(list_slot(mrow, c), __func__); - double gv = eigs_slot_num(list_slot(grow, c), __func__); - list_set_num(mrow, c, pv - lr * gv); + double pv = eigs_slot_float(list_slot(mrow, c), __func__); + double gv = eigs_slot_float(list_slot(grow, c), __func__); + list_set_f64(mrow, c, pv - lr * gv); } } free(row_snapshot); @@ -2290,7 +2358,7 @@ Value* builtin_numerical_grad_cols(Value *arg) { /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_slot_opt_num(list_slot(arg, 3), &eps, __func__); + eigs_slot_opt_float(list_slot(arg, 3), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "numerical_grad_cols") || !tensor_cells_numeric(col_indices, "numerical_grad_cols")) return make_null(); if (eps <= 0) eps = 0.001; @@ -2346,7 +2414,7 @@ Value* builtin_numerical_grad_cols(Value *arg) { for (int r = 0; r < rows; r++) { Value *grow = make_list(cols); for (int c = 0; c < cols; c++) - list_append_owned(grow, make_num(0.0)); + list_append_owned(grow, make_float(0.0)); list_append_owned(grad, grow); } /* #1722: see numerical_grad_rows — a refused make_list caps grad/grad_row @@ -2363,7 +2431,7 @@ Value* builtin_numerical_grad_cols(Value *arg) { if (!row || row->type != VAL_LIST || col >= row->data.list.count) continue; int row_len = row->data.list.count; - double old_val = eigs_slot_num(list_slot(row, col), __func__); + double old_val = eigs_slot_float(list_slot(row, col), __func__); val_incref(row); /* +eps */ double written = gradient_perturb(row, col, old_val + eps); @@ -2378,11 +2446,11 @@ Value* builtin_numerical_grad_cols(Value *arg) { val_decref(row); goto cols_grad_failed; } /* restore */ - list_set_num(row, col, old_val); + list_set_f64(row, col, old_val); val_decref(row); /* gradient — release the zero placeholder this slot held */ Value *grad_row = list_get_ref(grad, r); - list_set_num(grad_row, col, (loss_plus - loss_minus) / (2.0 * eps)); + list_set_f64(grad_row, col, (loss_plus - loss_minus) / (2.0 * eps)); } } free(col_snapshot); @@ -2411,7 +2479,7 @@ Value* builtin_sgd_update_cols(Value *arg) { /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_slot_opt_num(list_slot(arg, 3), &lr, __func__); + eigs_slot_opt_float(list_slot(arg, 3), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "sgd_update_cols") || !tensor_cells_numeric(grad, "sgd_update_cols") || !tensor_cells_numeric(col_indices, "sgd_update_cols")) return make_null(); @@ -2467,9 +2535,9 @@ Value* builtin_sgd_update_cols(Value *arg) { if (!mrow || mrow->type != VAL_LIST || col >= mrow->data.list.count) continue; if (!grow || grow->type != VAL_LIST || col >= grow->data.list.count) continue; - double pv = eigs_slot_num(list_slot(mrow, col), __func__); - double gv = eigs_slot_num(list_slot(grow, col), __func__); - list_set_num(mrow, col, pv - lr * gv); + double pv = eigs_slot_float(list_slot(mrow, col), __func__); + double gv = eigs_slot_float(list_slot(grow, col), __func__); + list_set_f64(mrow, col, pv - lr * gv); } } free(col_snapshot); @@ -2590,8 +2658,8 @@ static void tensor_load_record_verdict(uint32_t rows, uint32_t cols) { Value *verdict; if (rows || cols) { verdict = make_list(2); - list_append_owned(verdict, make_num((double)rows)); - list_append_owned(verdict, make_num((double)cols)); + list_append_owned(verdict, make_int((int64_t)rows)); + list_append_owned(verdict, make_int((int64_t)cols)); } else verdict = make_null(); trace_nondet_value("tensor_load", verdict); val_decref(verdict); @@ -2601,7 +2669,7 @@ Value* builtin_tensor_load(Value *arg) { if (!arg || arg->type != VAL_STR) return make_null(); if (__builtin_expect(g_replay_enabled, 0)) { - /* Consume and structurally validate the v7 verdict so stream order and + /* Consume and structurally validate the v8 verdict so stream order and * corrupt-tape diagnostics stay intact, but never use it as permission * to open the payload: tensor bytes themselves were never taped. */ Value *verdict = NULL; @@ -2610,8 +2678,8 @@ Value* builtin_tensor_load(Value *arg) { int valid = taken && verdict && (verdict->type == VAL_NULL || (verdict->type == VAL_LIST && verdict->data.list.count == 2 && - list_elem_type(verdict, 0) == VAL_NUM && - list_elem_type(verdict, 1) == VAL_NUM)); + list_elem_type(verdict, 0) == VAL_INT && + list_elem_type(verdict, 1) == VAL_INT)); if (!valid && !g_has_error) rt_error(EK_IO, 0, "tensor_load: malformed replay verdict"); val_decref(verdict); @@ -2681,7 +2749,7 @@ Value* builtin_tensor_load(Value *arg) { free(data); fclose(f); tensor_load_record_verdict(0, 0); return make_null(); } /* #971: the file is untrusted bytes, so a NaN pattern is reachable - * here. flat_to_tensor_* would collapse it through make_num anyway + * here. flat_to_tensor_* would collapse it through make_float anyway * (same 0 + EIGS_MATH_INVALID); guarding first lets the strict raise * name tensor_load. */ for (int i = 0; i < total; i++) { diff --git a/src/chunk.c b/src/chunk.c index 049d71e9c..c1b32c539 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -189,14 +189,12 @@ void chunk_patch_jump(EigsChunk *chunk, int offset) { /* ---- Constant pool ---- */ /* ---- Constant-pool dedup index (#341) ---- - * Equality is EXACTLY the old linear scan's tests, so pools stay - * byte-identical (the AOT oracle depends on that): NaN never equals - * itself (each NaN appends), and -0.0 merges with +0.0 (they hash the - * same and compare ==). Non-NUM/STR constants are never deduped. */ + * N6 compares float identities by binary64 bits, retaining signed zero + * and NaN payloads. Integers and strings keep their existing value keys; + * numeric kinds stay distinct. */ static uint32_t const_hash_value(const Value *v) { - if (v->type == VAL_NUM) { - double d = VAL_NUM_RAW(v); - if (d == 0.0) d = 0.0; /* fold -0.0 into +0.0, matching == */ + if (v->type == VAL_FLOAT) { + double d = VAL_F64_RAW(v); uint64_t bits; memcpy(&bits, &d, sizeof bits); bits ^= bits >> 33; @@ -204,6 +202,13 @@ static uint32_t const_hash_value(const Value *v) { bits ^= bits >> 33; return (uint32_t)bits; } + if (v->type == VAL_INT) { + uint64_t bits = (uint64_t)VAL_I64_RAW(v); + bits ^= bits >> 33; + bits *= 0xff51afd7ed558ccdULL; + bits ^= bits >> 33; + return (uint32_t)bits ^ 0x9e3779b9u; /* kind is part of a constant's key */ + } uint32_t h = 2166136261u; /* FNV-1a */ for (const char *p = v->data.str; *p; p++) { h ^= (unsigned char)*p; @@ -213,8 +218,13 @@ static uint32_t const_hash_value(const Value *v) { } static int const_equal(const Value *a, const Value *b) { - if (a->type == VAL_NUM && b->type == VAL_NUM) - return VAL_NUM_RAW(a) == VAL_NUM_RAW(b); + if (a->type == VAL_FLOAT && b->type == VAL_FLOAT) { + /* N6: constant identity preserves signed zero and every double bit. */ + double av = VAL_F64_RAW(a), bv = VAL_F64_RAW(b); + return memcmp(&av, &bv, sizeof av) == 0; + } + if (a->type == VAL_INT && b->type == VAL_INT) + return VAL_I64_RAW(a) == VAL_I64_RAW(b); if (a->type == VAL_STR && b->type == VAL_STR) return strcmp(a->data.str, b->data.str) == 0; return 0; @@ -234,7 +244,7 @@ static void const_dedup_grow(EigsChunk *c) { c->const_dedup_cap = new_cap; for (int i = 0; i < c->const_count; i++) { Value *v = c->constants[i]; - if (v->type == VAL_NUM || v->type == VAL_STR) + if (val_is_number(v) || v->type == VAL_STR) const_dedup_insert(c, const_hash_value(v), i); } } @@ -243,7 +253,7 @@ static int chunk_add_constant_ex(EigsChunk *chunk, Value *val, int dedup) { /* Deduplicate numbers and strings via the hash index — the linear * scan here was O(pool) per add: 92% of compile time on a 12k-name * generated program (#341). */ - int hashable = (val->type == VAL_NUM || val->type == VAL_STR); + int hashable = (val_is_number(val) || val->type == VAL_STR); uint32_t h = 0; if (hashable) { if ((chunk->const_count + 1) * 2 > chunk->const_dedup_cap) @@ -340,7 +350,8 @@ const char *op_name(uint8_t op) { switch ((OpCode)op) { #define N(o) case o: return &(#o)[3]; N(OP_CONST) N(OP_NULL) N(OP_NUM_ZERO) N(OP_NUM_ONE) N(OP_TRUE) N(OP_FALSE) - N(OP_ADD) N(OP_SUB) N(OP_MUL) N(OP_DIV) N(OP_MOD) + N(OP_INT_ZERO) N(OP_INT_ONE) N(OP_INT_CONST) + N(OP_ADD) N(OP_SUB) N(OP_MUL) N(OP_DIV) N(OP_MOD) N(OP_IDIV) N(OP_BAND) N(OP_BOR) N(OP_BXOR) N(OP_SHL) N(OP_SHR) N(OP_NEG) N(OP_NOT) N(OP_BNOT) N(OP_EQ) N(OP_NE) N(OP_LT) N(OP_GT) N(OP_LE) N(OP_GE) @@ -406,10 +417,15 @@ void chunk_disassemble(EigsChunk *chunk, const char *label) { for (int k = 0; k < nops && i + 1 < chunk->code_len; k++) { uint16_t arg = chunk->code[i] | (chunk->code[i + 1] << 8); fprintf(stderr, " %d", arg); - if (op == OP_CONST && arg < (uint16_t)chunk->const_count) { + if ((op == OP_CONST || op == OP_INT_CONST) && arg < (uint16_t)chunk->const_count) { Value *v = chunk->constants[arg]; - if (v->type == VAL_NUM) - fprintf(stderr, " (%.6g)", VAL_NUM_RAW(v)); + if (v->type == VAL_FLOAT) { + char number[32]; + eigs_num_text(number, sizeof number, VAL_F64_RAW(v)); + fprintf(stderr, " (%s)", number); + } + else if (v->type == VAL_INT) + fprintf(stderr, " (%lld)", (long long)VAL_I64_RAW(v)); else if (v->type == VAL_STR) fprintf(stderr, " (\"%s\")", v->data.str); } @@ -518,6 +534,7 @@ static StackEffect op_verify_stack_effect(uint8_t op8, int operand0) { switch ((OpCode)op8) { /* Pushes, consuming nothing. */ case OP_CONST: case OP_NULL: case OP_NUM_ZERO: case OP_NUM_ONE: + case OP_INT_ZERO: case OP_INT_ONE: case OP_INT_CONST: case OP_TRUE: case OP_FALSE: case OP_GET_LOCAL: case OP_GET_NAME: case OP_CLOSURE: case OP_LOCAL_DOT_GET: case OP_LOCAL_IDX_GET: case OP_LOCAL_IDX_DOT_GET: @@ -554,7 +571,7 @@ static StackEffect op_verify_stack_effect(uint8_t op8, int operand0) { * DOT_GET, ITER_SETUP and the interrogatives. They still require their * operands: see the frame-base note above — vm_pop()'s guard is the bottom * of the whole VM stack, not the bottom of this frame's window. */ - case OP_DIV: case OP_MOD: + case OP_DIV: case OP_MOD: case OP_IDIV: return EFF(2, 2, 1); case OP_DOT_GET: case OP_ITER_SETUP: case OP_INTERROGATE: case OP_INTERROGATE_NAMED: @@ -671,7 +688,7 @@ static int chunk_verify_impl(EigsChunk *chunk, char *why, size_t whyn) { if (i + 7 > n || code[i + 6] != OP_BINARY_LINE_END) { ok = 0; break; } uint8_t bin = code[i + 5]; if (!((bin >= OP_ADD && bin <= OP_SHR) || - (bin >= OP_EQ && bin <= OP_GE))) { ok = 0; break; } + (bin >= OP_EQ && bin <= OP_GE) || bin == OP_IDIV)) { ok = 0; break; } is_start[i + 5] = is_start[i + 6] = 2; } else if (op == OP_BINARY_LINE_END && is_start[i] != 2) { ok = 0; break; @@ -712,6 +729,9 @@ static int chunk_verify_impl(EigsChunk *chunk, char *why, size_t whyn) { if (operand >= chunk->const_count) { WHY("constant index %d out of range at offset %d", operand, i); ok = 0; + } else if (op == OP_INT_CONST && chunk->constants[operand]->type != VAL_INT) { + WHY("integer constant index %d is not an int at offset %d", operand, i); + ok = 0; } break; case VR_NAME: if (operand >= chunk->const_count) { @@ -1015,7 +1035,7 @@ void chunk_scan_leaf_accessor(EigsChunk *c) { case OP_CONST: { uint16_t idx = (uint16_t)(ip[0] | (ip[1] << 8)); if (idx >= (uint16_t)c->const_count) return; - if (c->constants[idx]->type != VAL_NUM) return; + if (c->constants[idx]->type != VAL_FLOAT) return; depth++; break; } diff --git a/src/compiler.c b/src/compiler.c index 0c92c1983..ef0a63abd 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -337,6 +337,7 @@ static int op_stack_effect(uint8_t op8) { switch ((OpCode)op8) { /* Push 1 */ case OP_CONST: case OP_NULL: case OP_NUM_ZERO: case OP_NUM_ONE: + case OP_INT_ZERO: case OP_INT_ONE: case OP_INT_CONST: case OP_TRUE: case OP_FALSE: case OP_GET_LOCAL: case OP_GET_NAME: case OP_DUP: case OP_PREDICATE: case OP_LISTCOMP_BEGIN: @@ -353,7 +354,7 @@ static int op_stack_effect(uint8_t op8) { case OP_POP: return -1; /* Pop 2, push 1 = net -1 */ - case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: case OP_IDIV: case OP_BAND: case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: case OP_EQ: case OP_NE: case OP_LT: case OP_GT: case OP_LE: case OP_GE: case OP_INDEX_GET: @@ -639,12 +640,34 @@ static int add_string_constant(Compiler *c, const char *str) { } static int add_num_constant(Compiler *c, double num) { - Value *v = make_num(num); + Value *v = make_float(num); int idx = chunk_add_constant(c->chunk, v); val_decref(v); return check_const_index(idx); } +static int add_int_constant(Compiler *c, int64_t integer) { + Value *v = make_int(integer); + int idx = chunk_add_constant(c->chunk, v); + val_decref(v); + return check_const_index(idx); +} + +/* A fused literal index is an exact u16 for either numeric kind. Check + * before casting: large int/float literals must take the generic path. */ +static int literal_index_u16(const ASTNode *node, int *out) { + EigsNum n = node->data.number; + if (n.is_int) { + if (n.value.i64 < 0 || n.value.i64 > 0xFFFF) return 0; + *out = (int)n.value.i64; + return 1; + } + double d = n.value.f64; + if (!(d >= 0.0 && d <= 65535.0) || floor(d) != d) return 0; + *out = (int)d; + return 1; +} + /* ---- Local variable tracking ---- */ static int resolve_local(Compiler *c, const char *name, uint32_t hash) { @@ -701,6 +724,7 @@ static uint8_t binop_to_opcode(const char *op) { if (op[0] == '-' && op[1] == 0) return OP_SUB; if (op[0] == '*' && op[1] == 0) return OP_MUL; if (op[0] == '/' && op[1] == 0) return OP_DIV; + if (op[0] == '/' && op[1] == '/') return OP_IDIV; if (op[0] == '%' && op[1] == 0) return OP_MOD; if (op[0] == '&' && op[1] == 0) return OP_BAND; if (op[0] == '|' && op[1] == 0) return OP_BOR; @@ -2175,10 +2199,20 @@ static void compile_node_inner(Compiler *c, ASTNode *node) { switch (node->type) { case AST_NUM: { - double v = node->data.num; - if (v == 0.0) { emit(c, OP_NUM_ZERO, node->line); } - else if (v == 1.0) { emit(c, OP_NUM_ONE, node->line); } - else { emit_op_u16(c, OP_CONST, (uint16_t)add_num_constant(c, v), node->line); } + EigsNum number = node->data.number; + if (number.is_int) { + int64_t i = number.value.i64; + if (i == 0) emit(c, OP_INT_ZERO, node->line); + else if (i == 1) emit(c, OP_INT_ONE, node->line); + else emit_op_u16(c, OP_INT_CONST, + (uint16_t)add_int_constant(c, i), node->line); + } else { + double f = number.value.f64; + if (f == 0.0) emit(c, OP_NUM_ZERO, node->line); + else if (f == 1.0) emit(c, OP_NUM_ONE, node->line); + else emit_op_u16(c, OP_CONST, + (uint16_t)add_num_constant(c, f), node->line); + } break; } @@ -2193,7 +2227,7 @@ static void compile_node_inner(Compiler *c, ASTNode *node) { break; case AST_BOOL: /* #1637 */ - emit(c, node->data.num != 0.0 ? OP_TRUE : OP_FALSE, node->line); + emit(c, node->data.boolean ? OP_TRUE : OP_FALSE, node->line); break; case AST_IDENT: { @@ -3087,9 +3121,8 @@ static void compile_node_inner(Compiler *c, ASTNode *node) { if (c->enclosing && node->data.index.target->type == AST_IDENT && node->data.index.index->type == AST_NUM) { - double dv = node->data.index.index->data.num; - int iv = (int)dv; - if (iv == dv && iv >= 0 && iv <= 0xFFFF) { + int iv; + if (literal_index_u16(node->data.index.index, &iv)) { const char *tname = node->data.index.target->data.ident.name; uint32_t th = node->data.index.target->name_hash; if (th == 0) th = env_hash_name(tname); @@ -3143,9 +3176,8 @@ static void compile_node_inner(Compiler *c, ASTNode *node) { ASTNode *idx_node = node->data.dot.target; if (idx_node->data.index.target->type == AST_IDENT && idx_node->data.index.index->type == AST_NUM) { - double dv = idx_node->data.index.index->data.num; - int iv = (int)dv; - if (iv == dv && iv >= 0 && iv <= 0xFFFF) { + int iv; + if (literal_index_u16(idx_node->data.index.index, &iv)) { const char *tname = idx_node->data.index.target->data.ident.name; uint32_t th = idx_node->data.index.target->name_hash; if (th == 0) th = env_hash_name(tname); @@ -3188,9 +3220,8 @@ static void compile_node_inner(Compiler *c, ASTNode *node) { ASTNode *idx_node = node->data.dot_assign.target; if (idx_node->data.index.target->type == AST_IDENT && idx_node->data.index.index->type == AST_NUM) { - double dv = idx_node->data.index.index->data.num; - int iv = (int)dv; - if (iv == dv && iv >= 0 && iv <= 0xFFFF) { + int iv; + if (literal_index_u16(idx_node->data.index.index, &iv)) { const char *tname = idx_node->data.index.target->data.ident.name; uint32_t th = idx_node->data.index.target->name_hash; if (th == 0) th = env_hash_name(tname); diff --git a/src/container.h b/src/container.h index e89a47121..c4291669c 100644 --- a/src/container.h +++ b/src/container.h @@ -3,13 +3,14 @@ /* Container slot boundary (#1665 arm D). List items and dict values are * EigsSlot storage (B10, the flip). Every stored slot is one of: - * - an immediate number, always num_guard'ed (the default x86 NaN - * 0xFFF8... is SLOT_NULL_BITS, so an unguarded double could read back - * as null); + * - an immediate number: a double, always num_guard'ed (the default x86 + * NaN 0xFFF8... is SLOT_NULL_BITS, so an unguarded double could read + * back as null), or an SMI, an int in [-2^47, 2^47) (#1758 design 2.1); * - SLOT_NULL_BITS or a bool encoding (the immortal singletons are never * stored as heap pointers); * - a TAG_HEAP pointer owning one counted reference to a Value whose type - * is none of NUM / NULL / BOOL. + * is none of FLOAT / NULL / BOOL. A VAL_INT there is an int outside the + * SMI range, a box (eigs_container_slot below keeps it, since R1.3). * All-zero bits are the number 0.0, not a vacancy: calloc'd storage reads as * zeros, so only slots below `count` are meaningful. Nothing outside this * header touches the storage arrays except the bulk alloc/grow/free code that @@ -23,7 +24,13 @@ typedef struct { Value v; } EigsView; * encodings, everything else is a heap pointer. C NULL encodes as null. */ static inline EigsSlot eigs_container_slot(Value *v) { if (!v || v->type == VAL_NULL) return slot_null(); - if (v->type == VAL_NUM) return slot_from_num(num_guard(VAL_NUM_RAW(v))); + if (v->type == VAL_FLOAT) return slot_from_f64(num_guard(VAL_F64_RAW(v))); + /* #1758 (design 2.1): container storage is canonical -- a small int is + * an SMI, and only an int outside [-2^47, 2^47) stays its box. */ + if (__builtin_expect(v->type == VAL_INT, 0)) { + int64_t i = VAL_I64_RAW(v); + return (i >= SLOT_SMI_MIN && i <= SLOT_SMI_MAX) ? slot_from_int(i) : slot_from_heap(v); + } if (v->type == VAL_BOOL) return slot_from_bool(v->data.boolean); return slot_from_heap(v); } @@ -37,18 +44,19 @@ static inline EigsSlot eigs_slot_adopt(Value *v) { /* Normalise a slot for storage. Immediates are guarded; a heap slot that * points at a NUM / NULL / BOOL Value (a VM-internal box or a singleton) is * re-encoded as an immediate. _owned consumes the slot's reference, _borrow - * takes a new one for the container. */ + * takes a new one for the container. #1758: an SMI is already canonical + * storage and is returned as it is (it owns no reference). */ static inline EigsSlot eigs_slot_store_owned(EigsSlot s) { - if (slot_is_num(s)) return slot_from_num(num_guard(SLOT_NUM_RAW(s))); + if (slot_is_f64(s)) return slot_from_f64(num_guard(SLOT_F64_RAW(s))); if (slot_is_heap(s)) { Value *v = slot_as_ptr(s); - if (v->type == VAL_NUM || v->type == VAL_NULL || v->type == VAL_BOOL) - return eigs_slot_adopt(v); + if (v->type == VAL_FLOAT || v->type == VAL_INT || v->type == VAL_NULL || v->type == VAL_BOOL) + return eigs_slot_adopt(v); /* #1758: a small-int box becomes its SMI */ } return s; } static inline EigsSlot eigs_slot_store_borrow(EigsSlot s) { - if (slot_is_num(s)) return slot_from_num(num_guard(SLOT_NUM_RAW(s))); + if (slot_is_f64(s)) return slot_from_f64(num_guard(SLOT_F64_RAW(s))); if (slot_is_heap(s)) { EigsSlot e = eigs_container_slot(slot_as_ptr(s)); slot_incref(e); @@ -71,34 +79,26 @@ static inline EigsSlot dict_value_slot(const Value *dict, int i) { return dict->data.dict.vals[i]; } -/* Store an immediate number the caller has already normalised (a VM stack - * slot is guarded at its producer), skipping the redundant num_guard on the - * assignment hot path. A plain slot store: the previous occupant is - * released after the store, so a destructor it runs sees the new value. */ -static inline int dict_value_rewrite_num(const Value *dict, int i, double d) { - EigsSlot old = dict->data.dict.vals[i]; - dict->data.dict.vals[i] = slot_from_num(d); - slot_decref(old); - return 1; -} -static inline int list_rewrite_num(const Value *list, int i, double d) { - EigsSlot old = list->data.list.items[i]; - list->data.list.items[i] = slot_from_num(d); - slot_decref(old); - return 1; -} - -/* Return the logical type of a borrowed slot without allocating. */ +/* Return the logical type of a borrowed slot without allocating. #1758: an + * SMI's type is VAL_INT; any reserved tag refuses instead of being read + * as a pointer. */ static inline ValType slot_type(EigsSlot s) { - if (slot_is_num(s)) return VAL_NUM; + if (slot_is_f64(s)) return VAL_FLOAT; if (slot_is_null(s)) return VAL_NULL; if (slot_is_bool(s)) return VAL_BOOL; - return slot_as_ptr(s)->type; -} + if (slot_is_heap(s)) return slot_as_ptr(s)->type; + if (slot_is_smi(s)) return VAL_INT; + eigs_slot_refuse("slot_type", s); +} +/* A slot's Value for a reader that wants a pointer: null and bool become + * their singletons, a heap slot its Value. #1758 R1.1: anything else (a + * double, an SMI, a reserved tag) is a broken caller contract -- every caller + * tests for a number first -- and aborts instead of being dereferenced. */ static inline Value *eigs_slot_ref(EigsSlot s) { if (slot_is_null(s)) return make_null(); if (slot_is_bool(s)) return make_bool(slot_as_bool(s)); - return slot_as_ptr(s); + if (slot_is_heap(s)) return slot_as_ptr(s); + eigs_slot_refuse("eigs_slot_ref", s); } static inline ValType list_elem_type(const Value *list, int i) { return slot_type(list_slot(list, i)); @@ -108,51 +108,38 @@ static inline ValType dict_value_type(const Value *dict, int i) { } /* Return the real heap pointer (including the immortal null/bool singleton), - * or NULL when the element is numeric. */ + * or NULL when the element is an immediate number: a double or an SMI. A + * boxed int (|i| >= 2^47, #1758 R1.3) is a heap slot, so it comes back as its + * non-NULL VAL_INT Value; type the element (list_elem_type) before reading. */ static inline Value *list_get_ref(const Value *list, int i) { EigsSlot s = list_slot(list, i); - return slot_is_num(s) ? NULL : eigs_slot_ref(s); + return (slot_is_f64(s) || slot_is_smi(s)) ? NULL : eigs_slot_ref(s); } static inline Value *dict_value_get_ref(const Value *dict, int i) { EigsSlot s = dict_value_slot(dict, i); - return slot_is_num(s) ? NULL : eigs_slot_ref(s); -} - -/* Numeric readers have the same strict/bool behavior as the Value readers: - * required raises for every non-number, element raises for bool or in strict - * mode, and optional treats null as absent. The heap-VAL_NUM arms serve a - * slot that did not come from container storage (a VM-internal box). */ -static inline double eigs_slot_num(EigsSlot s, const char *who) { - if (slot_is_num(s)) return SLOT_NUM_RAW(s); - if (slot_is_heap(s) && slot_as_ptr(s)->type == VAL_NUM) - return eigs_num_arg(slot_as_ptr(s), who); - return eigs_num_arg_slow(eigs_slot_ref(s), who); -} -static inline double eigs_slot_elem_num(EigsSlot s, const char *who) { - if (slot_is_num(s)) return SLOT_NUM_RAW(s); - Value *v = eigs_slot_ref(s); - if (v && v->type == VAL_NUM) return eigs_num_arg(v, who); - if (g_strict || (v && v->type == VAL_BOOL)) return eigs_num_arg_slow(v, who); - return 0.0; -} -static inline int eigs_slot_opt_num(EigsSlot s, double *out, const char *who) { - if (slot_is_null(s) || (slot_is_heap(s) && slot_as_ptr(s)->type == VAL_NULL)) return 0; - if (slot_is_num(s)) { *out = SLOT_NUM_RAW(s); return 1; } - if (slot_is_heap(s) && slot_as_ptr(s)->type == VAL_NUM) { - *out = eigs_num_arg(slot_as_ptr(s), who); return 1; - } - (void)eigs_num_arg_slow(eigs_slot_ref(s), who); - return 0; + return (slot_is_f64(s) || slot_is_smi(s)) ? NULL : eigs_slot_ref(s); } /* A numeric view is a caller-owned, allocation-free Value facade. Reference * slots return their real pointer. The cleanup guard catches retaining or - * decrefing a numeric facade: its sentinel refcount must be unchanged. */ + * decrefing a numeric facade: its sentinel refcount must be unchanged. + * #1758: an SMI's facade preserves its exact int Value kind. */ static inline Value *slot_view(EigsSlot s, EigsView *view) { - if (!slot_is_num(s) && slot_type(s) != VAL_NUM) return eigs_slot_ref(s); - view->v.type = VAL_NUM; - VAL_NUM_RAW(&view->v) = slot_is_num(s) ? SLOT_NUM_RAW(s) - : eigs_num_arg(slot_as_ptr(s), "slot_view"); + /* Float storage exits before any int classification (#1758 cost gate). */ + if (!slot_is_f64(s)) { + if (slot_is_smi(s)) { + view->v.type = VAL_INT; + VAL_I64_RAW(&view->v) = slot_smi_val(s); + view->v.refcount = EIGS_VIEW_RC; + return &view->v; + } + /* A heap VAL_INT is a real Value, returned as itself. */ + if (slot_type(s) != VAL_FLOAT) return eigs_slot_ref(s); + } + view->v.type = VAL_FLOAT; + /* The branch above proves float storage; retain raw facade semantics + * even for a direct-C nonfinite box, without a flag write or raise. */ + (void)slot_f64_value(s, &VAL_F64_RAW(&view->v)); view->v.refcount = EIGS_VIEW_RC; return &view->v; } @@ -163,8 +150,9 @@ static inline Value *dict_value_get_view(const Value *dict, int i, EigsView *vie return slot_view(dict_value_slot(dict, i), view); } static inline void eigs_view_check(EigsView *view) { - if (view->v.type == VAL_NUM && view->v.refcount != 0 && - view->v.refcount != EIGS_VIEW_RC) { + /* A valid facade (or an unused zero view) exits before kind checks. */ + if (view->v.refcount != 0 && view->v.refcount != EIGS_VIEW_RC && + (view->v.type == VAL_FLOAT || view->v.type == VAL_INT)) { fprintf(stderr, "%s:%d: EigenScript FATAL: numeric view refcount moved (%d)\n", __FILE__, __LINE__, view->v.refcount); abort(); @@ -180,15 +168,105 @@ static inline void eigs_view_check(EigsView *view) { * eigs_view_check explicitly before every exit from the view's scope. */ #define EIGS_VIEW_NOCHECK(name) EigsView name = {{0}} +/* Final slot readers borrow a facade for the duration of the call. The + * facade is never retained, so SMI, boxed-int and raise paths own no refs. */ +__attribute__((unused)) +static inline EigsNum eigs_slot_number(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_number_arg(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline EigsNum eigs_slot_elem_number(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_elem_number(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_number(EigsSlot s, EigsNum *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_number(slot_view(s, &view), out, who); +} +__attribute__((unused)) +static inline double eigs_slot_float(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_float_arg(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline double eigs_slot_elem_float(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_elem_float(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_float(EigsSlot s, double *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_float(slot_view(s, &view), out, who); +} +__attribute__((unused)) +static inline int64_t eigs_slot_int(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_int_arg(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline int64_t eigs_slot_elem_int(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_elem_int(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_int(EigsSlot s, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_int(slot_view(s, &view), out, who); +} +__attribute__((unused)) +static inline uint8_t eigs_slot_byte(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_byte_arg(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline uint8_t eigs_slot_elem_byte(EigsSlot s, const char *who) { + EIGS_VIEW(view); return eigs_elem_byte(slot_view(s, &view), who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_byte(EigsSlot s, uint8_t *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_byte(slot_view(s, &view), out, who); +} +__attribute__((unused)) +static inline int eigs_slot_int_range(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_int_arg_range(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_elem_int_range(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_elem_int_range(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_int_range(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_int_range(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_bounded(EigsSlot s, double lo, double hi, int *out, const char *who) { + EIGS_VIEW(view); return eigs_bounded_arg(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_elem_bounded(EigsSlot s, double lo, double hi, int *out, const char *who) { + EIGS_VIEW(view); return eigs_elem_bounded(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_bounded(EigsSlot s, double lo, double hi, int *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_bounded(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_magnitude(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_magnitude_arg(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_elem_magnitude(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_elem_magnitude(slot_view(s, &view), lo, hi, out, who); +} +__attribute__((unused)) +static inline int eigs_slot_opt_magnitude(EigsSlot s, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); return eigs_opt_magnitude(slot_view(s, &view), lo, hi, out, who); +} + /* Owned/return reads make numeric storage independent. Reference values are * increfed by the owned form; return preserves the builtin direct-child - * borrow protocol for references while numbers are always fresh. */ + * borrow protocol for references while numbers are always fresh (#1758: an + * int's fresh Value comes from slot_to_value, the one place that boxes an + * SMI). */ static inline Value *list_get_owned(const Value *list, int i) { return slot_to_value(list_slot(list, i)); } static inline Value *list_get_return(const Value *list, int i) { EigsSlot s = list_slot(list, i); - return slot_is_num(s) ? make_num(SLOT_NUM_RAW(s)) : eigs_slot_ref(s); + if (slot_is_smi(s)) return slot_to_value(s); + return slot_is_f64(s) ? make_float(SLOT_F64_RAW(s)) : eigs_slot_ref(s); } static inline Value *dict_value_get_owned(const Value *dict, int i) { return slot_to_value(dict_value_slot(dict, i)); @@ -196,7 +274,8 @@ static inline Value *dict_value_get_owned(const Value *dict, int i) { __attribute__((unused)) static inline Value *dict_value_get_return(const Value *dict, int i) { EigsSlot s = dict_value_slot(dict, i); - return slot_is_num(s) ? make_num(SLOT_NUM_RAW(s)) : eigs_slot_ref(s); + if (slot_is_smi(s)) return slot_to_value(s); + return slot_is_f64(s) ? make_float(SLOT_F64_RAW(s)) : eigs_slot_ref(s); } /* Keyed reads (eigenscript.c). The slot is borrowed from the dict (for a @@ -226,7 +305,8 @@ static inline void dict_value_set_owned(Value *dict, int i, Value *v) { * append borrows it and therefore increfs. Every double path applies * num_guard. */ static inline Value *eigs_slot_box(EigsSlot s) { - if (slot_is_num(s)) return make_num(num_guard(SLOT_NUM_RAW(s))); + if (slot_is_f64(s)) return make_float(num_guard(SLOT_F64_RAW(s))); + if (slot_is_smi(s)) return slot_to_value(s); /* #1758: kind-preserving */ Value *v = eigs_slot_ref(s); val_incref(v); return v; } static inline __attribute__((always_inline)) void @@ -247,19 +327,85 @@ static inline void list_append_slot(Value *list, EigsSlot s) { if (!list_reserve_one(list)) return; list->data.list.items[list->data.list.count++] = eigs_slot_store_borrow(s); } -static inline __attribute__((always_inline)) void -list_set_num(Value *list, int i, double d) { - list_rewrite_num(list, i, num_guard(d)); +/* Typed container writers own exactly one stored reference. _rewrite_f64 + * receives an already-normalised double, matching the existing fast store. + * An int outside the SMI range owns the box slot_from_int creates. */ +__attribute__((unused)) +static inline int list_rewrite_f64(const Value *v, int i, double n) { + EigsSlot old = list_slot(v, i); + v->data.list.items[i] = slot_from_f64(n); + slot_decref(old); return 1; } -static inline void list_append_num(Value *list, double d) { - d = num_guard(d); - if (!list_reserve_one(list)) return; - list->data.list.items[list->data.list.count++] = slot_from_num(d); +__attribute__((unused)) +static inline void list_set_f64(Value *v, int i, double n) { + list_rewrite_f64(v, i, num_guard(n)); +} +__attribute__((unused)) +static inline int list_get_f64(const Value *v, int i, double *out) { + return slot_f64_value(list_slot(v, i), out); +} +__attribute__((unused)) +static inline int dict_value_rewrite_f64(const Value *v, int i, double n) { + EigsSlot old = dict_value_slot(v, i); + v->data.dict.vals[i] = slot_from_f64(n); + slot_decref(old); return 1; +} +__attribute__((unused)) +static inline void dict_value_set_f64(Value *v, int i, double n) { + dict_value_rewrite_f64(v, i, num_guard(n)); +} +__attribute__((unused)) +static inline int dict_value_get_f64(const Value *v, int i, double *out) { + return slot_f64_value(dict_value_slot(v, i), out); +} +__attribute__((unused)) +static inline void list_append_f64(Value *v, double n) { + n = num_guard(n); + if (!list_reserve_one(v)) return; + v->data.list.items[v->data.list.count++] = slot_from_f64(n); +} +__attribute__((unused)) +static inline int list_rewrite_int(const Value *v, int i, int64_t n) { + EigsSlot old = list_slot(v, i); + v->data.list.items[i] = slot_from_int(n); + slot_decref(old); return 1; +} +__attribute__((unused)) +static inline void list_set_int(Value *v, int i, int64_t n) { + list_rewrite_int(v, i, n); +} +/* The caller has proved this slot is an SMI with a nonnegative value + * below the SMI maximum. Incrementing the payload cannot carry into the tag and + * changes no reference ownership. ITER_NEXT additionally bounds it by + * idx < len <= INT_MAX. Keep element storage behind this header. */ +static inline void list_bump_smi(Value *v, int i) { + v->data.list.items[i].u++; +} +__attribute__((unused)) +static inline int list_get_int(const Value *v, int i, int64_t *out) { + return slot_int_value(list_slot(v, i), out); +} +__attribute__((unused)) +static inline int dict_value_rewrite_int(const Value *v, int i, int64_t n) { + EigsSlot old = dict_value_slot(v, i); + v->data.dict.vals[i] = slot_from_int(n); + slot_decref(old); return 1; +} +__attribute__((unused)) +static inline void dict_value_set_int(Value *v, int i, int64_t n) { + dict_value_rewrite_int(v, i, n); } __attribute__((unused)) -static inline void dict_value_set_num(Value *dict, int i, double d) { - dict_value_rewrite_num(dict, i, num_guard(d)); +static inline int dict_value_get_int(const Value *v, int i, int64_t *out) { + return slot_int_value(dict_value_slot(v, i), out); } +__attribute__((unused)) +static inline void list_append_int(Value *v, int64_t n) { + + if (!list_reserve_one(v)) return; + v->data.list.items[v->data.list.count++] = slot_from_int(n); +} + /* Remove element i, shifting the tail down; the caller owns the result. */ static inline EigsSlot list_take_slot(Value *list, int i) { EigsSlot s = list->data.list.items[i]; @@ -277,12 +423,13 @@ static inline EigsSlot dict_value_take_slot(Value *dict, int i) { static inline void list_move(Value *list, int dst, int src, int n) { memmove(&list->data.list.items[dst], &list->data.list.items[src], (size_t)n * sizeof(EigsSlot)); } +__attribute__((unused)) static inline void dict_value_move(Value *dict, int dst, int src, int n) { memmove(&dict->data.dict.vals[dst], &dict->data.dict.vals[src], (size_t)n * sizeof(EigsSlot)); } /* Stable bottom-up mergesort: libc qsort has no tie-order guarantee. * The comparator receives `const EigsSlot *` element pointers. Scratch - * slots borrow the list's references; only their order changes. #1982. */ + * slots borrow the list's references; only their order changes. #1758/#1982. */ static inline void list_sort(Value *list, int (*cmp)(const void *, const void *)) { size_t n = (size_t)list->data.list.count; if (n < 2) return; @@ -308,20 +455,6 @@ static inline void list_sort(Value *list, int (*cmp)(const void *, const void *) free(scratch); } -__attribute__((unused)) -static inline int list_get_num(const Value *list, int i, double *out) { - EigsSlot s = list_slot(list, i); - if (!slot_is_num(s)) return 0; - *out = SLOT_NUM_RAW(s); - return 1; -} -__attribute__((unused)) -static inline int dict_value_get_num(const Value *dict, int i, double *out) { - EigsSlot s = dict_value_slot(dict, i); - if (!slot_is_num(s)) return 0; - *out = SLOT_NUM_RAW(s); - return 1; -} __attribute__((unused)) static inline int list_iter_count(const Value *list) { return list->data.list.count; } __attribute__((unused)) diff --git a/src/eigenlsp.c b/src/eigenlsp.c index 56f185828..dc44565bb 100644 --- a/src/eigenlsp.c +++ b/src/eigenlsp.c @@ -1147,11 +1147,20 @@ static void handle_hover(int id, const char *params) { if (!doc) { lsp_response_null(id); return; } Token *tok = find_token_at(doc, line, col); - if (!tok || !tok->str_val) { lsp_response_null(id); return; } + if (!tok || (!tok->str_val && tok->type != TOK_NUM)) { + lsp_response_null(id); + return; + } const char *hover_text = NULL; char hover_buf[1024]; + /* #1758 R1.8: report the kind retained by the lexer/parser, rather + * than testing whether a double happens to be integral. In particular + * 3.0 is a float, while exact/hex-wrapped int64 literals stay ints. */ + if (tok->type == TOK_NUM) + hover_text = tok->number.is_int ? "int" : "float"; + /* A dotted token (`x.name`) is a member access — never a bare builtin, * keyword, or document symbol, so those lookups are skipped for it and * only the module-qualified stdlib branch below may claim it. Became @@ -1163,7 +1172,7 @@ static void handle_hover(int id, const char *params) { int dotted = (ti >= 1 && doc->tokens.tokens[ti - 1].type == TOK_DOT); /* Check builtins */ - if (!dotted) + if (!hover_text && !dotted) for (int i = 0; builtin_docs[i][0]; i++) { if (strcmp(tok->str_val, builtin_docs[i][0]) == 0) { hover_text = builtin_docs[i][1]; diff --git a/src/eigenscript.c b/src/eigenscript.c index 64de814ee..c1973dadd 100644 --- a/src/eigenscript.c +++ b/src/eigenscript.c @@ -265,6 +265,8 @@ const char* tok_type_name(TokType t) { case TOK_MINUS: return "'-'"; case TOK_STAR: return "'*'"; case TOK_SLASH: return "'/'"; + case TOK_SLASH_SLASH: return "'//'"; + case TOK_SLASH_SLASH_EQ: return "'//='"; case TOK_PERCENT: return "'%'"; case TOK_LT: return "'<'"; case TOK_GT: return "'>'"; @@ -325,7 +327,8 @@ const char* eigs_interrogative_word(int kind) { const char* val_type_name(ValType t) { switch (t) { - case VAL_NUM: return "num"; + case VAL_FLOAT: return "num"; + case VAL_INT: return "num"; /* #1758 (B): one number type at the surface */ case VAL_STR: return "str"; case VAL_LIST: return "list"; case VAL_FN: return "fn"; @@ -372,7 +375,7 @@ static void env_intern_scope_remove(char *name); /* #262 Phase-3 D: scalar entropy of a number, factored out so the slot * observer can update from a raw immediate double without materializing a * Value (the default path no longer promotes observed nums to tracked - * pointers). Single source of truth for the VAL_NUM entropy formula. */ + * pointers). Single source of truth for the VAL_FLOAT entropy formula. */ static double entropy_of_num(double num) { /* #412: unity is the HORIZON, not a home point. The binary-entropy * formula is smooth and maximal at |x| = 1 (p = 0.5, H = 1.0); the old @@ -444,7 +447,9 @@ static double ent_child(Value *c) { } static double ent_child_slot(EigsSlot s) { - if (slot_is_num(s)) return entropy_of_num(SLOT_NUM_RAW(s)); + if (slot_is_f64(s)) return entropy_of_num(SLOT_F64_RAW(s)); + /* #1758: an int is observed as its double until round 3 (design 7). */ + if (slot_is_smi(s)) return entropy_of_num((double)slot_smi_val(s)); return ent_child(eigs_slot_ref(s)); } @@ -452,7 +457,10 @@ static double compute_entropy_impl(Value *v) { if (!v) return 0.0; switch (v->type) { case VAL_NULL: return 0.0; - case VAL_NUM: return entropy_of_num(VAL_NUM_RAW(v)); + case VAL_FLOAT: return entropy_of_num(VAL_F64_RAW(v)); + /* #1758 R1.2a (design 7): an int's entropy is its double's; it joins + * the numeric channel when ints exist (R1.3). */ + case VAL_INT: return entropy_of_num((double)VAL_I64_RAW(v)); case VAL_STR: return entropy_of_cstr(v->data.str); case VAL_LIST: { if (v->data.list.count == 0) return 0.0; @@ -595,15 +603,33 @@ static int observer_eq_within_budget(Value *v, size_t *units, int depth) { return 1; } +/* Shared structural reflexivity test (R1.6z-D2), defined with equality below. */ +static int equal_structure_reflexive(Value *root); + /* Equality used only by observer bookkeeping. Unlike language equality it * cannot raise (notably num-vs-bool), set g_has_error, normalize buffers, or * synchronize module namespaces. Observer snapshots are ordinary detached * values, and unsupported/deep values are refused before reaching here. */ static int values_equal_for_observation(Value *a, Value *b, int depth) { - if (a == b) return 1; - if (!a || !b || a->type != b->type || depth > 64) return 0; + if (a == b) { + if (a && (a->type == VAL_LIST || a->type == VAL_DICT || a->type == VAL_BUFFER)) + return equal_structure_reflexive(a); + return 1; + } + if (!a || !b || depth > 64) return 0; + if (a->type != b->type) { + /* N2 applies to numeric elements inside the equality snapshots too. + * Keep this observer-only comparison non-raising for every other + * kind pair, especially number/bool. */ + if (a->type == VAL_INT && b->type == VAL_FLOAT) + return eigs_cmp_int_float(VAL_I64_RAW(a), VAL_F64_RAW(b)) == 0; + if (a->type == VAL_FLOAT && b->type == VAL_INT) + return eigs_cmp_int_float(VAL_I64_RAW(b), VAL_F64_RAW(a)) == 0; + return 0; + } switch (a->type) { - case VAL_NUM: return VAL_NUM_RAW(a) == VAL_NUM_RAW(b); + case VAL_FLOAT: return VAL_F64_RAW(a) == VAL_F64_RAW(b); + case VAL_INT: return VAL_I64_RAW(a) == VAL_I64_RAW(b); case VAL_STR: return strcmp(a->data.str, b->data.str) == 0; case VAL_NULL: return 1; case VAL_BOOL: return a->data.boolean == b->data.boolean; @@ -636,9 +662,9 @@ static int values_equal_for_observation(Value *a, Value *b, int depth) { case VAL_BUFFER: if (a->data.buffer.count != b->data.buffer.count) return 0; for (int i = 0; i < a->data.buffer.count; i++) { - double av = num_guard_value(a->data.buffer.data[i]); - double bv = num_guard_value(b->data.buffer.data[i]); - if (av != bv) return 0; + /* S10-D1 / R1.6z-D1: observe the stored IEEE values, + * never normalize the work area or alter math flags. */ + if (a->data.buffer.data[i] != b->data.buffer.data[i]) return 0; } return 1; case VAL_TEXT_BUILDER: @@ -998,9 +1024,10 @@ static void observer_slot_update_e_locked(Env *e, int idx, double new_entropy) { static inline void observer_slot_update_locked(Env *e, int idx, Value *newval, double entropy) { observer_slot_update_e_locked(e, idx, entropy); - if (newval && newval->type == VAL_NUM) { + if (newval && val_is_number(newval)) { ObserverSlot *s = env_obs_slot(e, idx); - if (s) observer_slot_record_value(s, VAL_NUM_RAW(newval)); + if (s) observer_slot_record_value(s, newval->type == VAL_INT ? (double)VAL_I64_RAW(newval) + : VAL_F64_RAW(newval)); } else { ObserverSlot *s = env_obs_slot(e, idx); if (s) { @@ -1060,7 +1087,8 @@ static void observer_slot_update_value_num_shared( env_shared_fold_lock(); observer_slot_update_e_locked(e, idx, entropy); ObserverSlot *s = env_obs_slot(e, idx); - if (s) observer_slot_record_value(s, VAL_NUM_RAW(newval)); + if (s) observer_slot_record_value(s, newval->type == VAL_INT ? (double)VAL_I64_RAW(newval) + : VAL_F64_RAW(newval)); env_shared_fold_unlock(); } @@ -1133,7 +1161,7 @@ void observer_slot_update(Env *e, int idx, Value *newval) { if (!e || idx < 0) return; if (idx >= e->obs_cap && !observer_obs_grow(e, idx)) return; if (observer_fold_is_shared(e)) { - if (newval && newval->type == VAL_NUM) { + if (newval && val_is_number(newval)) { observer_slot_update_value_num_shared(e, idx, newval, entropy); } else { ObserverEqualitySnapshot eq = observer_equality_snapshot(newval); @@ -1202,8 +1230,9 @@ void observer_slot_sample_num_gated(Env *e, int idx, double num) { } void observer_slot_sample_gated(Env *e, int idx, Value *newval) { - if (newval && newval->type == VAL_NUM) { - observer_slot_sample_num_gated(e, idx, VAL_NUM_RAW(newval)); + if (newval && val_is_number(newval)) { /* #1758: an int box as its double */ + observer_slot_sample_num_gated(e, idx, newval->type == VAL_INT ? (double)VAL_I64_RAW(newval) + : VAL_F64_RAW(newval)); return; } if (!e || idx < 0) return; @@ -1672,9 +1701,21 @@ static int obs_entropy_stable(const ObserverSlot *s) { return 1; } +/* The snapshot is detached and bounded by observer_eq_within_budget. + * Its caller owns the slot or holds the shared-env fold lock while querying. */ int observer_slot_stable(const ObserverSlot *s) { if (obs_route_num(s)) return obs_num_stable(s); - if (s && s->eq_seen) return observer_slot_eq_settled(s); + if (s && s->eq_seen) { + /* S10-D1 retains bank stability for a snapshot reaching a NaN + * buffer: its entropy can be stable while IEEE equality can never + * certify convergence. Finite snapshots keep #2048's equality + * route. Refusal clears bounded-window eligibility even when the + * older entropy window was already stable. */ + if (obs_eq_count(s) < (size_t)observer_slot_window(s)) return 0; + if (s->eq_last && !equal_structure_reflexive(s->eq_last)) + return obs_entropy_stable(s); + return observer_slot_eq_settled(s); + } return obs_entropy_stable(s); } @@ -1774,9 +1815,12 @@ int observer_entropy_now(Env *e, int idx, double *out) { env_dump_lock(e); /* exported #607 lock wrapper (defined below) */ if (idx < e->count) { EigsSlot s = e->values[idx]; - if (slot_is_num(s)) { + if (slot_is_f64(s)) { kind = 1; - num = SLOT_NUM_RAW(s); + num = SLOT_F64_RAW(s); + } else if (slot_is_smi(s)) { + kind = 1; /* #1758: the numeric channel, as a double */ + num = (double)slot_smi_val(s); } else if (slot_is_bool(s)) { held = make_bool(slot_as_bool(s)); /* #1637: immortal; incref no-op */ kind = 2; @@ -1821,24 +1865,24 @@ Value *observer_slot_trajectory(const ObserverSlot *s) { /* windows read newest-first (offset 0 = most recent); emit oldest-first * so the lists read chronologically, like a history. */ for (int i = vcnt - 1; i >= 0; i--) { - list_append_owned(rel, make_num(observer_slot_v_get(s, (size_t)i))); - list_append_owned(raw, make_num(observer_slot_vr_get(s, (size_t)i))); + list_append_owned(rel, make_float(observer_slot_v_get(s, (size_t)i))); + list_append_owned(raw, make_float(observer_slot_vr_get(s, (size_t)i))); } for (int i = dcnt - 1; i >= 0; i--) - list_append_owned(dh, make_num(observer_slot_window_get(s, (size_t)i))); + list_append_owned(dh, make_float(observer_slot_window_get(s, (size_t)i))); dict_set_owned(out, "rel", rel); dict_set_owned(out, "raw", raw); dict_set_owned(out, "dh", dh); - dict_set_owned(out, "entropy", make_num(s ? s->entropy : 0.0)); - dict_set_owned(out, "dH", make_num(s ? s->dH : 0.0)); - dict_set_owned(out, "last_entropy", make_num(s ? s->last_entropy : 0.0)); - dict_set_owned(out, "last_value", make_num((s && s->v_used) ? s->last_value : 0.0)); + dict_set_owned(out, "entropy", make_float(s ? s->entropy : 0.0)); + dict_set_owned(out, "dH", make_float(s ? s->dH : 0.0)); + dict_set_owned(out, "last_entropy", make_float(s ? s->last_entropy : 0.0)); + dict_set_owned(out, "last_value", make_float((s && s->v_used) ? s->last_value : 0.0)); dict_set_owned(out, "observed", make_bool(s && s->used != 0)); /* #1637 */ dict_set_owned(out, "numeric", make_bool(s && s->v_used != 0)); /* #1044: the depth this slot classifies over travels with the snapshot, * so `classify of (trajectory of x)` agrees with `report of x` for a * binding carrying a per-binding override. */ - dict_set_owned(out, "window", make_num((double)observer_slot_window(s))); + dict_set_owned(out, "window", make_int(observer_slot_window(s))); return out; } @@ -1870,10 +1914,12 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { EIGS_VIEW(w_view); Value *w = dict_get_view_local(dict, "window", &w_view); if (w) { - if (w->type != VAL_NUM || VAL_NUM_RAW(w) < OBSERVER_WINDOW_MIN || - VAL_NUM_RAW(w) > OBSERVER_WINDOW_MAX || VAL_NUM_RAW(w) != (int)VAL_NUM_RAW(w)) + /* #1758: a number of either kind, read through the bridge */ + double wd = val_is_number(w) ? eigs_float_arg(w, __func__) : 0.0; + if (!val_is_number(w) || wd < OBSERVER_WINDOW_MIN || + wd > OBSERVER_WINDOW_MAX || wd != (int)wd) return 0; - out->win_override = (uint8_t)(int)VAL_NUM_RAW(w); + out->win_override = (uint8_t)(int)wd; } } const int N = observer_slot_window(out); @@ -1887,13 +1933,14 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { out->v_cap = out->dh_cap = (uint8_t)N; for (int i = vstart; i < vcnt; i++) { EigsSlot a = list_slot(rel, i), b = list_slot(raw, i); - if (slot_type(a) != VAL_NUM || slot_type(b) != VAL_NUM) { + ValType ta = slot_type(a), tb = slot_type(b); /* #1758: either number kind */ + if ((ta != VAL_FLOAT && ta != VAL_INT) || (tb != VAL_FLOAT && tb != VAL_INT)) { free(out->v_window); free(out->vr_window); memset(out, 0, sizeof *out); return 0; } - out->v_window[out->v_window_count] = eigs_slot_num(a, __func__); - out->vr_window[out->v_window_count] = eigs_slot_num(b, __func__); + out->v_window[out->v_window_count] = eigs_slot_float(a, __func__); + out->vr_window[out->v_window_count] = eigs_slot_float(b, __func__); out->v_window_count++; } out->v_window_head = (uint8_t)(out->v_window_count % N); @@ -1902,12 +1949,13 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { out->dh_window = xcalloc((size_t)N, sizeof(double)); for (int i = dstart; i < dcnt; i++) { EigsSlot a = list_slot(dh, i); - if (slot_type(a) != VAL_NUM) { + ValType ta = slot_type(a); + if (ta != VAL_FLOAT && ta != VAL_INT) { free(out->v_window); free(out->vr_window); free(out->dh_window); memset(out, 0, sizeof *out); return 0; } - out->dh_window[out->dh_window_count++] = eigs_slot_num(a, __func__); + out->dh_window[out->dh_window_count++] = eigs_slot_float(a, __func__); } out->dh_window_head = (uint8_t)(out->dh_window_count % N); /* #1637: the scalar fields are numbers and the two flags are bools, as @@ -1920,12 +1968,12 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { EIGS_VIEW(v_view); Value *v = dict_get_view_local(dict, nums[k], &v_view); if (!v) continue; - if (v->type != VAL_NUM) { + if (!val_is_number(v)) { free(out->v_window); free(out->vr_window); free(out->dh_window); memset(out, 0, sizeof *out); return 0; } - *dsts[k] = VAL_NUM_RAW(v); + *dsts[k] = eigs_float_arg(v, __func__); /* #1758: an int converts (the bridge) */ } EIGS_VIEW(fo_view); EIGS_VIEW(fn_view); Value *fo = dict_get_view_local(dict, "observed", &fo_view); @@ -2028,9 +2076,9 @@ void env_retain_intern_table(Env *env, EnvInternTable *table) { void free_value(Value *v) { if (!v) return; env_intern_release_value(v); - if (v->type == VAL_NUM) { + if (v->type == VAL_FLOAT) { /* the freelist recycles float boxes; an int box is freed */ #ifndef EIGS_POOL_OFF_NUM - /* Route freed NUMs to freelist for reuse by make_num */ + /* Route freed NUMs to freelist for reuse by make_float */ if (g_num_freelist_count < NUM_FREELIST_CAP) { memcpy(EIGS_NUM_LINK(v), &g_num_freelist, sizeof(Value *)); g_num_freelist = v; @@ -2109,7 +2157,8 @@ void free_value(Value *v) { /* No owned memory. Enumerated rather than covered by a `default:` so * that -Werror=switch (Makefile CFLAGS) makes a new ValType a build * error here instead of a silent leak. */ - case VAL_NUM: + case VAL_FLOAT: + case VAL_INT: /* #1758 R1.2a: an int box owns nothing */ case VAL_BUILTIN: case VAL_NULL: case VAL_BOOL: @@ -2118,7 +2167,8 @@ void free_value(Value *v) { free(v); } -Value* make_num(double n) { +static inline Value *make_float_pooled(double n) __attribute__((always_inline)); +static inline Value *make_float_pooled(double n) { n = num_guard(n); Value *v; #ifndef EIGS_POOL_OFF_NUM @@ -2130,19 +2180,32 @@ Value* make_num(double n) { eigs_num_park_unpoison(v); memset(v, 0, sizeof(Value)); } else -#endif /* EIGS_POOL_OFF_NUM (#1665): never satisfy a make_num from the freelist — +#endif /* EIGS_POOL_OFF_NUM (#1665): never satisfy a make_float from the freelist — * every heap NUM is a fresh xcalloc. */ { v = xcalloc(1, sizeof(Value)); } - v->type = VAL_NUM; - VAL_NUM_RAW(v) = n; + v->type = VAL_FLOAT; + VAL_F64_RAW(v) = n; + v->refcount = 1; + return v; +} + +/* Float construction retains the existing pooling and normalization path. */ +Value *make_float(double n) { return make_float_pooled(n); } + +/* #1758 R1.3: large int slots and Value API boundaries own ordinary heap + * Values. Int boxes never enter the float freelist and own no GC edges. */ +Value* make_int(int64_t n) { + Value *v = xcalloc(1, sizeof(Value)); + v->type = VAL_INT; + VAL_I64_RAW(v) = n; v->refcount = 1; return v; } void recycle_intermediate(Value *v) { - if (!v || v->type != VAL_NUM || v->refcount > 1) return; + if (!v || v->type != VAL_FLOAT || v->refcount > 1) return; #ifdef EIGS_POOL_OFF_NUM /* #1665: no freelist — a discarded intermediate NUM is freed outright * (the pooled path already free()s it when the cap is hit, so callers @@ -2162,17 +2225,18 @@ void recycle_intermediate(Value *v) { #endif } -/* Unpooled make_num: never touches the per-thread NUM freelist, so it is +/* Unpooled make_float: never touches the per-thread NUM freelist, so it is * safe without a current VM thread (C-level fixtures, tests/test_intern_owners.c). */ -Value* make_num_permanent(double n) { +Value* make_float_permanent(double n) { n = num_guard(n); Value *v = xcalloc(1, sizeof(Value)); - v->type = VAL_NUM; - VAL_NUM_RAW(v) = n; + v->type = VAL_FLOAT; + VAL_F64_RAW(v) = n; v->refcount = 1; return v; } + /* Forward decl of the VAL_NULL singleton — defined below near make_null. */ static Value g_null_singleton; /* #1637: the two VAL_BOOL singletons are immortal like null. @@ -2197,30 +2261,465 @@ static const char *num_who(const char *who) { return strncmp(who, "builtin_", 8) == 0 ? who + 8 : who; } -double eigs_num_arg_slow(const Value *v, const char *who) { - if (v && v->type == VAL_NUM) return VAL_NUM_RAW(v); +/* The final readers never own their argument. Keep the payload/type + * predicate here so all forms share it, including every refusal path. */ +static int eigs_number_read(const Value *v, EigsNum *out, const char *who) { + if (v && v->type == VAL_INT) { + out->is_int = 1; out->value.i64 = VAL_I64_RAW(v); return 1; + } + if (v && v->type == VAL_FLOAT) { + out->is_int = 0; out->value.f64 = VAL_F64_RAW(v); return 1; + } if (!g_has_error) rt_error(EK_TYPE, 0, "%s: expected a number, got %s", num_who(who), v ? val_type_name(v->type) : "nothing"); - return 0.0; + return 0; } -double eigs_list_num(const Value *list, int i, const char *who) { - if (!list || list->type != VAL_LIST || i < 0 || i >= list->data.list.count) { +EigsNum eigs_number_arg(const Value *v, const char *who) { + EigsNum n = { .is_int = 0, .value = { .f64 = 0.0 } }; + if (!eigs_number_read(v, &n, who)) return n; + if (!n.is_int && !isfinite(n.value.f64)) { if (!g_has_error) - rt_error(EK_TYPE, 0, "%s: expected a number as argument %d, got nothing", - num_who(who), i); - return 0.0; + rt_error(EK_VALUE, 0, "%s: expected a finite number", num_who(who)); + n.value.f64 = 0.0; + } + return n; +} + +double eigs_float_arg(const Value *v, const char *who) { + EigsNum n = eigs_number_arg(v, who); + return n.is_int ? (double)n.value.i64 : n.value.f64; +} + +int64_t eigs_int_arg(const Value *v, const char *who) { + EigsNum n = { .is_int = 0, .value = { .f64 = 0.0 } }; + if (!eigs_number_read(v, &n, who)) return 0; + if (n.is_int) return n.value.i64; + int64_t i; + if (num_to_i64_exact(n.value.f64, &i)) return i; + if (!g_has_error) + rt_error(EK_VALUE, 0, EIGS_NO_INT_REP_FMT, num_who(who), n.value.f64); + return 0; +} + +int eigs_int_arg_range(const Value *v, int64_t lo, int64_t hi, + int64_t *out, const char *who) { + int64_t i = eigs_int_arg(v, who); + if (g_has_error) return 0; + if (i < lo || i > hi || lo > hi) { + rt_error(EK_VALUE, 0, "%s: integer %lld is outside [%lld, %lld]", num_who(who), + (long long)i, (long long)lo, (long long)hi); + return 0; } - return eigs_slot_num(list_slot(list, i), who); + *out = i; + return 1; +} + +uint8_t eigs_byte_arg(const Value *v, const char *who) { + int64_t i; + return eigs_int_arg_range(v, 0, 255, &i, who) ? (uint8_t)i : 0; +} + +/* Q7 clips complete continuous shapes before Q4 conversion. */ +/* Exact sums of up to six binary64 products. A product occupies bits + * 0..4195 in units of 2^-2148; six terms need at most bit4198. Fixed + * 66-word positive/negative accumulators retain cancellation and Q4 ties + * without assuming long double has a wider exponent range than double. */ +#define EIGS_GEOM_WORDS 66 +static double eigs_geom_products(const double terms[][2], int count, int *sign, int *exponent) { + uint64_t positive[EIGS_GEOM_WORDS]={0},negative[EIGS_GEOM_WORDS]={0}; + for (int k=0;k>52)&0x7ff); + mant[j]=(bits[j]&UINT64_C(0xfffffffffffff)) | (e ? UINT64_C(0x10000000000000) : 0); + power[j]=e ? (int)e-1075 : -1074; + } + if (!mant[0] || !mant[1]) continue; + /* S10-D4: the freestanding rv32 target has no __int128. Keep the + * native path unchanged; the test-only macro exercises the same + * portable path on hosted compilers that do provide __int128. */ +#if defined(__SIZEOF_INT128__) && !defined(EIGS_TEST_FORCE_PORTABLE_GEOM) + unsigned __int128 product=(unsigned __int128)mant[0]*mant[1]; + int shift=power[0]+power[1]+2148,word=shift/64,b=shift%64; + uint64_t lo=(uint64_t)product,hi=(uint64_t)(product>>64),part[3]; +#else + uint64_t a0=(uint32_t)mant[0],a1=mant[0]>>32; + uint64_t b0=(uint32_t)mant[1],b1=mant[1]>>32; + uint64_t p00=a0*b0,p01=a0*b1,p10=a1*b0,p11=a1*b1; + uint64_t middle=(p00>>32)+(uint32_t)p01+(uint32_t)p10; + uint64_t lo=(middle<<32)|(uint32_t)p00; + uint64_t hi=p11+(p01>>32)+(p10>>32)+(middle>>32),part[3]; + int shift=power[0]+power[1]+2148,word=shift/64,b=shift%64; +#endif + part[0]=lo<>(64-b)) : hi; + part[2]=b ? hi>>(64-b) : 0; + uint64_t *dest=((bits[0]^bits[1])>>63) ? negative : positive; +#if defined(__SIZEOF_INT128__) && !defined(EIGS_TEST_FORCE_PORTABLE_GEOM) + unsigned __int128 carry=0; +#else + uint64_t carry=0; +#endif + for (int j=0;j<3 || carry;++j) { + if (word+j>=EIGS_GEOM_WORDS) abort(); /* internal six-product bound */ +#if defined(__SIZEOF_INT128__) && !defined(EIGS_TEST_FORCE_PORTABLE_GEOM) + unsigned __int128 sum=(unsigned __int128)dest[word+j]+(j<3 ? part[j] : 0)+carry; + dest[word+j]=(uint64_t)sum;carry=sum>>64; +#else + uint64_t addend=j<3 ? part[j] : 0; + uint64_t sum=dest[word+j]+addend,overflow=sum=0;--i) + if (positive[i]!=negative[i]) { cmp=positive[i]>negative[i] ? 1 : -1;break; } + *sign=cmp;*exponent=0;if (!cmp) return 0; + uint64_t *large=cmp>0 ? positive : negative,*small=cmp>0 ? negative : positive; + unsigned borrow=0; + for (int i=0;i1023 ? 1023 : power < -1022 ? -1022 : power; + uint64_t bits=(uint64_t)(chunk+1023)<<52;double factor; + memcpy(&factor,&bits,sizeof factor);value*=factor;power-=chunk; + } + return value; +} +static double eigs_geom_intersection(const double *v,int edge_axis,double edge,int rounded) { + double x0=v[0],y0=v[1],x1=v[2],y1=v[3],a0,a1,terms[6][2]; + if (!edge_axis) { + terms[0][0]=y1;terms[0][1]=edge;terms[1][0]=-y0;terms[1][1]=edge; + terms[2][0]=x1;terms[2][1]=y0;terms[3][0]=-x0;terms[3][1]=y1; + a0=x0;a1=x1; + } else { + terms[0][0]=x1;terms[0][1]=edge;terms[1][0]=-x0;terms[1][1]=edge; + terms[2][0]=-x1;terms[2][1]=y0;terms[3][0]=x0;terms[3][1]=y1; + a0=y0;a1=y1; + } + int sign,ne,de,ds;double nm=eigs_geom_products(terms,4,&sign,&ne); + double denominator[2][2]={{1,a1},{-1,a0}}; + double dm=eigs_geom_products(denominator,2,&ds,&de); + double q=eigs_geom_scale((sign*ds)*nm/dm,ne-de); + if (!rounded || !isfinite(q)) return q; + double lower=floor(q),tie=lower+0.5; + terms[4][0]=-tie;terms[4][1]=a1;terms[5][0]=tie;terms[5][1]=a0; + (void)eigs_geom_products(terms,6,&sign,&ne);sign*=ds; + return sign>0 || (sign==0 && tie>=0) ? lower+1 : lower; +} +static double eigs_geom_min(double a,double b) { return ab ? a : b; } +static int eigs_geom_line(const EigsRect *d, const double *v, EigsShape *out) { + double x0=v[0], y0=v[1], x1=v[2], y1=v[3]; + double left=d->x, top=d->y, right=left+d->w, bottom=top+d->h; + if (eigs_geom_max(x0,x1)=right || + eigs_geom_max(y0,y1)=bottom) return 0; + int dx=(x1>x0)-(x1y0)-(y1x=out->x2=(int)round(x0); out->y=out->y2=(int)round(y0); + return out->xyye || (xe==ye && xm>=ym) ? 0 : 1; + double direction=(axis ? dy : dx)>0 ? 1 : -1; + double enter=direction*(axis ? y0 : x0), leave=direction*(axis ? y1 : x1); + double ex=x0, ey=y0, lx=x1, ly=y1; + int enter_edge=-1,leave_edge=-1; + double bound[4]={left,right,top,bottom}; + double p[4]={-dx,dx,-dy,dy}; + double q[4]={x0-left,right-x0,y0-top,bottom-y0}; + for (int k=0;k<4;++k) { + if (p[k]==0) { if (q[k]<0) return 0; continue; } + double cx,cy; + if (k<2) { cx=bound[k]; cy=eigs_geom_intersection(v,0,cx,0); } + else { cy=bound[k]; cx=eigs_geom_intersection(v,1,cy,0); } + double u=direction*(axis ? cy : cx); + if (p[k]<0) { + if (u>leave) return 0; + if (u>enter) { enter=u;ex=cx;ey=cy;enter_edge=k; } + } else { + if (u=0) { + if (enter_edge<2) ey=eigs_geom_intersection(v,0,bound[enter_edge],1); + else ex=eigs_geom_intersection(v,1,bound[enter_edge],1); + } + if (leave_edge>=0) { + if (leave_edge<2) ly=eigs_geom_intersection(v,0,bound[leave_edge],1); + else lx=eigs_geom_intersection(v,1,bound[leave_edge],1); + } + out->x=(int)round(eigs_geom_max(left,eigs_geom_min(right,ex))); + out->y=(int)round(eigs_geom_max(top,eigs_geom_min(bottom,ey))); + out->x2=(int)round(eigs_geom_max(left,eigs_geom_min(right,lx))); + out->y2=(int)round(eigs_geom_max(top,eigs_geom_min(bottom,ly))); + return 1; +} + +int eigs_geom_shape(const Value *args, int first, EigsShapeKind kind, + const EigsRect *drawable, EigsShape *out, const char *who) { + memset(out, 0, sizeof *out); + out->empty = 1; + out->kind = kind; + int count = kind == EIGS_POINT ? 2 : kind == EIGS_CIRCLE ? 3 : kind == EIGS_RRECT ? 5 : 4; + for (int i = 0; i < count; ++i) { + out->raw[i] = eigs_list_float(args, first + i, who); + if (g_has_error) return 0; + } + if (!drawable || drawable->w <= 0 || drawable->h <= 0) return 1; + double left = drawable->x, top = drawable->y; + double right = left + drawable->w, bottom = top + drawable->h; + /* Drawable bounds themselves are C ints supplied by the renderer. */ + if (left < INT_MIN || top < INT_MIN || right > INT_MAX || bottom > INT_MAX) { + rt_error(EK_VALUE, 0, "%s: drawable bounds do not fit a C int", num_who(who)); + return 0; + } + double x = out->raw[0], y = out->raw[1], x2, y2; + if (kind == EIGS_POINT) { + if (x < left || x >= right || y < top || y >= bottom) return 1; + out->x = (int)round(x); out->y = (int)round(y); + if (out->x >= right || out->y >= bottom) return 1; + out->w = out->h = 1; + } else if (kind == EIGS_LINE) { + if (!eigs_geom_line(drawable,out->raw,out)) return 1; + } else { + if (kind == EIGS_CIRCLE) { + double r = out->raw[2]; + if (r < 0) return 1; + x2 = x+r+1; y2 = y+r+1; x -= r; y -= r; + } else { + if (out->raw[2] <= 0 || out->raw[3] <= 0) return 1; + x2 = x+out->raw[2]; y2 = y+out->raw[3]; + } + x = eigs_geom_max(left, x); y = eigs_geom_max(top, y); + x2 = eigs_geom_min(right, x2); y2 = eigs_geom_min(bottom, y2); + if (x >= x2 || y >= y2) return 1; + out->x = (int)round(x); out->y = (int)round(y); + out->w = (int)round(x2) - out->x; + out->h = (int)round(y2) - out->y; + if (out->w <= 0 || out->h <= 0) return 1; + } + out->empty = 0; + return 1; +} + +int eigs_bounded_arg(const Value *v, double lo, double hi, int *out, const char *who) { + double x = eigs_float_arg(v, who); + if (g_has_error) return 0; + if (!isfinite(lo) || !isfinite(hi) || lo > hi) { + rt_error(EK_VALUE, 0, "%s: invalid bounded range", num_who(who)); return 0; + } + if (x < lo) x = lo; + if (x > hi) x = hi; + x = round(x); /* Q4: half away from zero, after clamping */ + if (!(x >= (double)INT_MIN && x <= (double)INT_MAX)) { + rt_error(EK_VALUE, 0, "%s: bounded value does not fit a C int", num_who(who)); + return 0; + } + *out = (int)x; + return 1; +} + +int eigs_magnitude_arg(const Value *v, int64_t lo, int64_t hi, + int64_t *out, const char *who) { + EigsNum n = eigs_number_arg(v, who); + if (g_has_error) return 0; + int64_t i; + if (n.is_int) i = n.value.i64; + else if (!num_to_i64_exact(round(n.value.f64), &i)) { + rt_error(EK_VALUE, 0, "%s: magnitude has no integer representation", num_who(who)); + return 0; + } + if (i < lo || i > hi || lo > hi) { + rt_error(EK_VALUE, 0, "%s: magnitude is outside [%lld, %lld]", num_who(who), + (long long)lo, (long long)hi); + return 0; + } + *out = i; + return 1; +} + +/* Validate list shape once; readers below use borrowed views of its slot. */ +static const Value *eigs_reader_list_arg(const Value *list, int i, + EigsView *view, const char *who) { + if (list && list->type == VAL_LIST && i >= 0 && i < list->data.list.count) + return list_get_view(list, i, view); + if (!g_has_error) + rt_error(EK_TYPE, 0, "%s: expected a number as argument %d, got nothing", + num_who(who), i); + return NULL; +} +static int eigs_reader_elem_default(const Value *v) { + return !g_strict && (!v || (!val_is_number(v) && v->type != VAL_BOOL)); } -double eigs_elem_num(const Value *v, const char *who) { - return eigs_slot_elem_num(eigs_container_slot((Value *)v), who); +EigsNum eigs_list_number(const Value *list, int i, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? (EigsNum){ .is_int = 0, .value = { .f64 = 0.0 } } : eigs_number_arg(v, who); +} +EigsNum eigs_elem_number(const Value *v, const char *who) { + return eigs_reader_elem_default(v) ? (EigsNum){ .is_int = 0, .value = { .f64 = 0.0 } } : eigs_number_arg(v, who); +} +int eigs_opt_number(const Value *v, EigsNum *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + EigsNum n = eigs_number_arg(v, who); + if (g_has_error) return 0; + *out = n; return 1; +} + +double eigs_list_float(const Value *list, int i, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_float_arg(v, who); +} +double eigs_elem_float(const Value *v, const char *who) { + return eigs_reader_elem_default(v) ? 0 : eigs_float_arg(v, who); +} +int eigs_opt_float(const Value *v, double *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + double n = eigs_float_arg(v, who); + if (g_has_error) return 0; + *out = n; return 1; +} + +int64_t eigs_list_int(const Value *list, int i, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_int_arg(v, who); +} +int64_t eigs_elem_int(const Value *v, const char *who) { + return eigs_reader_elem_default(v) ? 0 : eigs_int_arg(v, who); +} +int eigs_opt_int(const Value *v, int64_t *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + int64_t n = eigs_int_arg(v, who); + if (g_has_error) return 0; + *out = n; return 1; +} + +uint8_t eigs_list_byte(const Value *list, int i, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_byte_arg(v, who); +} +uint8_t eigs_elem_byte(const Value *v, const char *who) { + return eigs_reader_elem_default(v) ? 0 : eigs_byte_arg(v, who); +} +int eigs_opt_byte(const Value *v, uint8_t *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + uint8_t n = eigs_byte_arg(v, who); + if (g_has_error) return 0; + *out = n; return 1; +} + +int eigs_list_int_range(const Value *list, int i, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_int_arg_range(v, lo, hi, out, who); +} +int eigs_elem_int_range(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who) { + const Value zero = { .type = VAL_INT }; + return eigs_int_arg_range(eigs_reader_elem_default(v) ? &zero : v, lo, hi, out, who); +} +int eigs_opt_int_range(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + return eigs_int_arg_range(v, lo, hi, out, who); +} + +int eigs_list_bounded(const Value *list, int i, double lo, double hi, int *out, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_bounded_arg(v, lo, hi, out, who); +} +int eigs_elem_bounded(const Value *v, double lo, double hi, int *out, const char *who) { + const Value zero = { .type = VAL_INT }; + return eigs_bounded_arg(eigs_reader_elem_default(v) ? &zero : v, lo, hi, out, who); +} +int eigs_opt_bounded(const Value *v, double lo, double hi, int *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + return eigs_bounded_arg(v, lo, hi, out, who); +} + +int eigs_list_magnitude(const Value *list, int i, int64_t lo, int64_t hi, int64_t *out, const char *who) { + EIGS_VIEW(view); + const Value *v = eigs_reader_list_arg(list, i, &view, who); + return g_has_error ? 0 : eigs_magnitude_arg(v, lo, hi, out, who); +} +int eigs_elem_magnitude(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who) { + const Value zero = { .type = VAL_INT }; + return eigs_magnitude_arg(eigs_reader_elem_default(v) ? &zero : v, lo, hi, out, who); +} +int eigs_opt_magnitude(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who) { + if (!v || v->type == VAL_NULL) return 0; + return eigs_magnitude_arg(v, lo, hi, out, who); } -int eigs_opt_num(const Value *v, double *out, const char *who) { - return eigs_slot_opt_num(eigs_container_slot((Value *)v), out, who); +/* buffer_read_f64 is the renamed data boundary; callers that need exact + * bytes or indices instead call the corresponding checked conversion. */ +double buffer_read_f64(const Value *buffer, int64_t index) { + double x = buffer->data.buffer.data[index]; + /* #1417: a strict NaN scalar read retains the first NaN diagnostic and + * INVALID flag. Do this before the generic nonfinite refusal; callers + * must stop before reading a later infinity. The finite-only refusal in + * non-strict mode and the infinity refusal remain unchanged. */ + if (g_strict && x != x && !g_has_error) return num_guard(x); + if (!isfinite(x)) { + if (!g_has_error) + rt_error(EK_VALUE, 0, "buffer_read_f64: expected a finite number"); + return 0.0; + } + return num_guard(x); +} +int buffer_index_at(const Value *buffer, int64_t index, int64_t *out, const char *who) { + if (!buffer || buffer->type != VAL_BUFFER || index < 0 || index >= buffer->data.buffer.count) { + if (!g_has_error) rt_error(EK_INDEX, 0, "%s: buffer index is out of range", num_who(who)); + return 0; + } + /* #1417: exact byte/index reads share the data boundary's first-NaN + * diagnostic and INVALID flag. Stop before converting its stand-in or + * reading a later cell; non-strict nonfinite inputs still refuse (N13). */ + double cell = buffer_read_f64(buffer, index); + if (g_has_error) return 0; + Value v = { .type = VAL_FLOAT }; + VAL_F64_RAW(&v) = cell; + int64_t i = eigs_int_arg(&v, who); + if (g_has_error) return 0; + *out = i; return 1; +} +int buffer_byte_at(const Value *buffer, int64_t index, uint8_t *out, const char *who) { + int64_t i; + if (!buffer_index_at(buffer, index, &i, who)) return 0; + if (i < 0 || i > 255) { + rt_error(EK_VALUE, 0, "%s: byte is outside [0, 255]", num_who(who)); return 0; + } + *out = (uint8_t)i; return 1; } /* Two levels: the argument, an element of the argument list, or an element @@ -2246,7 +2745,7 @@ int eigs_arg_has_bool(const Value *arg) { * * slot_from_value: takes ownership of the input ref. * - NULL or VAL_NULL singleton -> immediate null (input is borrowed) - * - VAL_NUM -> immediate double; input released (#262 Step E: nums never + * - VAL_FLOAT -> immediate double; input released (#262 Step E: nums never * carry observer state, so there is no tracked-pointer case) * - any other type -> TAG_HEAP; ref transferred * @@ -2256,26 +2755,41 @@ EigsSlot slot_from_value(Value *v) { if (v && v != &g_null_singleton) val_decref(v); return slot_null(); } - if (v->type == VAL_NUM) { + if (v->type == VAL_FLOAT) { /* #262 Step E: a number never carries observer state (it lives on the - * Env slot), so every VAL_NUM ships as an immediate double. TAG_TRACKED + * Env slot), so every VAL_FLOAT ships as an immediate double. TAG_TRACKED * is dead. */ - double n = VAL_NUM_RAW(v); + double n = VAL_F64_RAW(v); val_decref(v); - return slot_from_num(n); + return slot_from_f64(n); + } + /* #1758 (design 2.1): a small int is stored as its SMI and its box is + * released; an int outside [-2^47, 2^47) keeps its box (a heap slot). */ + if (v->type == VAL_INT) { + int64_t i = VAL_I64_RAW(v); + if (i >= SLOT_SMI_MIN && i <= SLOT_SMI_MAX) { + val_decref(v); + return slot_from_int(i); + } + return slot_from_heap(v); } if (v->type == VAL_BOOL) return slot_from_bool(v->data.boolean); /* singleton: borrowed */ return slot_from_heap(v); } /* slot_to_value: produces a Value* the caller owns a ref to. - * - immediate number -> make_num + * - immediate double -> make_float * - immediate null -> g_null_singleton * - immediate bool -> the true/false singleton (#1637) * - heap/tracked pointer -> incref and return + * - SMI (#1758) -> a fresh int Value. This is the ONE place that boxes an + * SMI (the lifts, slot_bridge_unwrap, eigs_slot_box and the *_get_return + * readers all route here). + * - any other tag is a broken contract and aborts (it used to read back + * as null, silently). */ Value* slot_to_value(EigsSlot s) { - if (slot_is_num(s)) return make_num(SLOT_NUM_RAW(s)); + if (slot_is_f64(s)) return make_float(SLOT_F64_RAW(s)); if (slot_is_null(s)) return &g_null_singleton; if (slot_is_bool(s)) return make_bool(slot_as_bool(s)); if (slot_is_heap(s)) { @@ -2283,8 +2797,15 @@ Value* slot_to_value(EigsSlot s) { val_incref(v); return v; } - /* unknown tag: fall back to null */ - return &g_null_singleton; + if (slot_is_smi(s)) return make_int(slot_smi_val(s)); + eigs_slot_refuse("slot_to_value", s); +} + +void eigs_slot_refuse(const char *who, EigsSlot s) { + fprintf(stderr, "EigenScript FATAL: %s: slot 0x%016llx (tag 0x%04llx) is not " + "a value this reader handles (#1758)\n", who, + (unsigned long long)s.u, (unsigned long long)(s.u >> 48)); + abort(); } Value* make_str(const char *s) { @@ -2770,10 +3291,12 @@ static EigsSlot module_ns_project(Value *d, Env *e, const char *key, uint32_t h, *found = 1; return cur; } - if (slot_is_num(s) && slot_is_num(cur)) { + if (slot_is_f64(s) && slot_is_f64(cur)) { /* Number over number: a plain slot store with nothing to - * release, so no destructor runs under the hold. */ - dict_value_rewrite_num(d, di, SLOT_NUM_RAW(s)); + * release, so no destructor runs under the hold. (#1758: an + * int takes the general path below; slot_to_value boxes it and + * the dict store re-canonicalizes it.) */ + dict_value_rewrite_f64(d, di, SLOT_F64_RAW(s)); env_shared_unlock(e); *found = 1; return dict_value_slot(d, di); @@ -3033,10 +3556,11 @@ static Value *chan_clone_rec(Value *v, int depth) { * behavior) rather than overflow the stack. */ /* #1665: scalars are copied at any depth: a number may be a stack view, * which must never be shared out of its frame. */ - if (depth > CHAN_CLONE_MAX_DEPTH && v->type != VAL_NUM && v->type != VAL_NULL && + if (depth > CHAN_CLONE_MAX_DEPTH && v->type != VAL_FLOAT && v->type != VAL_INT && v->type != VAL_NULL && v->type != VAL_BOOL && v->type != VAL_STR) { val_incref(v); return v; } switch (v->type) { - case VAL_NUM: return make_num(VAL_NUM_RAW(v)); + case VAL_FLOAT: return make_float(VAL_F64_RAW(v)); + case VAL_INT: return make_int(VAL_I64_RAW(v)); case VAL_NULL: return make_null(); case VAL_BOOL: return make_bool(v->data.boolean); case VAL_STR: return make_str(v->data.str); @@ -3239,7 +3763,8 @@ int is_truthy(Value *v) { if (!v) return 0; switch (v->type) { case VAL_NULL: return 0; - case VAL_NUM: return VAL_NUM_RAW(v) != 0.0; + case VAL_FLOAT: return VAL_F64_RAW(v) != 0.0; + case VAL_INT: return VAL_I64_RAW(v) != 0; /* #1758 R1.2a */ case VAL_STR: return v->data.str && v->data.str[0] != '\0'; case VAL_LIST: return v->data.list.count > 0; case VAL_FN: return 1; @@ -3252,12 +3777,91 @@ int is_truthy(Value *v) { return 0; } +/* R1.6z-D2: identity is sound only if every reachable element is reflexive. + * Before R2.2 only a NaN-holding buffer is non-reflexive; R2.2 must extend + * this predicate to NaN floats (including immediate container slots). + * The work stack avoids C recursion and the pointer set visits shared + * containers/buffers once, so DAGs stay linear and cycles terminate. + * All pointers are borrowed for this call. No scalar boundary, raise or + * g_math_flags write belongs in this scan. */ +typedef struct { + Value **todo, **seen; + size_t count, capacity, seen_count, seen_capacity; +} EqualScan; + +static size_t equal_scan_hash(Value *v) { + uintptr_t x = (uintptr_t)v; + x ^= x >> 16; x *= 0x45d9f3bU; x ^= x >> 13; + return (size_t)x; +} + +/* Return true if already visited; otherwise insert before following edges. */ +static int equal_scan_seen(EqualScan *scan, Value *v) { + if (!scan->seen_capacity || scan->seen_count * 2 >= scan->seen_capacity) { + size_t old_capacity = scan->seen_capacity; + Value **old = scan->seen; + scan->seen_capacity = old_capacity ? old_capacity * 2 : 64; + scan->seen = xcalloc_array(scan->seen_capacity, sizeof(*scan->seen)); + for (size_t i = 0; i < old_capacity; i++) { + if (!old[i]) continue; + size_t slot = equal_scan_hash(old[i]) & (scan->seen_capacity - 1); + while (scan->seen[slot]) slot = (slot + 1) & (scan->seen_capacity - 1); + scan->seen[slot] = old[i]; + } + free(old); + } + size_t slot = equal_scan_hash(v) & (scan->seen_capacity - 1); + while (scan->seen[slot]) { + if (scan->seen[slot] == v) return 1; + slot = (slot + 1) & (scan->seen_capacity - 1); + } + scan->seen[slot] = v; + scan->seen_count++; + return 0; +} + +static void equal_scan_push(EqualScan *scan, Value *v) { + if (!v || (v->type != VAL_LIST && v->type != VAL_DICT && v->type != VAL_BUFFER)) + return; + if (scan->count == scan->capacity) { + scan->capacity = scan->capacity ? scan->capacity * 2 : 32; + scan->todo = xrealloc_array(scan->todo, scan->capacity, sizeof(*scan->todo)); + } + scan->todo[scan->count++] = v; +} + +static int equal_structure_reflexive(Value *root) { + EqualScan scan = {0}; + int reflexive = 1; + equal_scan_push(&scan, root); + while (scan.count && reflexive) { + Value *v = scan.todo[--scan.count]; + if (equal_scan_seen(&scan, v)) continue; + if (v->type == VAL_BUFFER) { + for (int i = 0; i < v->data.buffer.count; i++) { + if (isnan(v->data.buffer.data[i])) { reflexive = 0; break; } + } + } else if (v->type == VAL_LIST) { + for (int i = 0; i < v->data.list.count; i++) + equal_scan_push(&scan, list_get_ref(v, i)); + } else { + eigs_module_ns_sync(v); /* #1057 whole-dict reader */ + DICT_FOR_EACH(v, i) + equal_scan_push(&scan, dict_value_get_ref(v, i)); + } + } + free(scan.todo); + free(scan.seen); + return reflexive; +} + /* Structural equality for `==` / `!=`. * Scalars compare by value (numbers, strings, null); collections compare * recursively by structure (lists element-wise, dicts by key/value * order-independently, buffers/text-builders by contents). Functions, * and builtins compare by identity. Mixed types are never equal - * (no coercion — consistent with the comparison operators). The depth + * (no coercion — consistent with the comparison operators), except an int + * and a float, which are one type, `num`, and compare by value (#1758 N2). The depth * guard prevents runaway recursion on self-referential containers; beyond * it we fall back to identity. */ /* #1637 item 6: a bool met by a number — at any depth of the two values — @@ -3269,13 +3873,23 @@ int is_truthy(Value *v) { * the operator (`==`, `!=`) or the builtin, for the message. Every other * mixed-type pair stays unequal. */ static int values_equal_impl(Value *a, Value *b, int depth, const char *op) { - /* #1417: a buffer compared with itself still exposes its elements. Do - * not bypass normalization (or a strict NaN raise) through identity. */ - if (a == b && (!a || a->type != VAL_BUFFER)) return 1; + /* #1417 / R1.6z-D2: identity cannot hide a reachable NaN buffer. */ + if (a == b && (!a || a->type != VAL_BUFFER)) { + if (a && (a->type == VAL_LIST || a->type == VAL_DICT)) + return equal_structure_reflexive(a); + return 1; + } if (!a || !b) return 0; if (a->type != b->type) { - if ((a->type == VAL_BOOL && b->type == VAL_NUM) || - (a->type == VAL_NUM && b->type == VAL_BOOL)) { + /* #1758 N2 (design 7): an int and a float are both numbers and are + * equal exactly when their values are; no conversion rounds either + * side. */ + if (a->type == VAL_INT && b->type == VAL_FLOAT) + return eigs_cmp_int_float(VAL_I64_RAW(a), VAL_F64_RAW(b)) == 0; + if (a->type == VAL_FLOAT && b->type == VAL_INT) + return eigs_cmp_int_float(VAL_I64_RAW(b), VAL_F64_RAW(a)) == 0; + if ((a->type == VAL_BOOL && val_is_number(b)) || + (val_is_number(a) && b->type == VAL_BOOL)) { if (op[0] == '=' || op[0] == '!') rt_error(EK_TYPE, 0, "cannot compare %s and %s with '%s'", val_type_name(a->type), val_type_name(b->type), op); @@ -3289,7 +3903,8 @@ static int values_equal_impl(Value *a, Value *b, int depth, const char *op) { * (a number may be a view, so pointer identity is meaningless). */ if (depth > 64 && (a->type == VAL_LIST || a->type == VAL_DICT)) return a == b; switch (a->type) { - case VAL_NUM: return VAL_NUM_RAW(a) == VAL_NUM_RAW(b); + case VAL_FLOAT: return VAL_F64_RAW(a) == VAL_F64_RAW(b); + case VAL_INT: return VAL_I64_RAW(a) == VAL_I64_RAW(b); /* #1758 R1.2a: both ints */ case VAL_STR: return strcmp(a->data.str, b->data.str) == 0; case VAL_NULL: return 1; case VAL_BOOL: return a->data.boolean == b->data.boolean; @@ -3319,11 +3934,9 @@ static int values_equal_impl(Value *a, Value *b, int depth, const char *op) { case VAL_BUFFER: { if (a->data.buffer.count != b->data.buffer.count) return 0; for (int i = 0; i < a->data.buffer.count; i++) { - double av = buffer_read_num(a, i); - if (g_has_error) return 0; - double bv = buffer_read_num(b, i); - if (g_has_error) return 0; - if (av != bv) return 0; + /* R1.6z-D1: total IEEE equality on stored doubles. No + * scalar boundary, normalization, error or math-flag write. */ + if (!(a->data.buffer.data[i] == b->data.buffer.data[i])) return 0; } return 1; } @@ -3356,23 +3969,120 @@ int values_equal_op(Value *a, Value *b, const char *op) { return values_equal_im * * Every producer of number text now calls this, so a fourth copy cannot * appear with a fourth rule. Writes at most 32 bytes. */ +/* #1758 R1.2a: THE int->text rule: exact decimal digits, every int64. */ +void eigs_int_text(char *buf, size_t nbuf, int64_t i) { + snprintf(buf, nbuf, "%lld", (long long)i); +} + void eigs_num_text(char *buf, size_t nbuf, double n) { - /* Exact integers up to 2^53 (the largest integer all doubles represent - * exactly) print without a decimal point or exponent. The magnitude test - * runs BEFORE the cast — casting an out-of-range double is UB (#816). */ - if (fabs(n) < 9007199254740992.0 && n == (long long)n) { - snprintf(buf, nbuf, "%lld", (long long)n); + if (nbuf == 0) return; + if (isnan(n)) { snprintf(buf, nbuf, "nan"); return; } + int neg = signbit(n) != 0; + if (isinf(n)) { snprintf(buf, nbuf, "%sinf", neg ? "-" : ""); return; } + if (n == 0.0) { snprintf(buf, nbuf, "%s0.0", neg ? "-" : ""); return; } + /* In Python's fixed range every integral float has its exact integer + * digits followed by .0. The range check makes the cast safe. */ + if (fabs(n) < 1e16 && n == (int64_t)n) { + snprintf(buf, nbuf, "%lld.0", (long long)(int64_t)n); return; } - /* Otherwise the shortest representation that round-trips: try 15..17 - * significant digits and stop at the first that parses back to the same - * double. %.6g (the old `str of` default) silently truncated every float - * to 6 figures — lossy for the numerical/STEM workloads this language - * targets — and %.15g loses the 17th digit a double can need. */ - for (int prec = 15; prec <= 17; prec++) { - snprintf(buf, nbuf, "%.*g", prec, n); - if (strtod(buf, NULL) == n) return; + + /* The first decimal precision that round-trips is the shortest. The + * correctly-rounded %e candidate is closest, with decimal half-even + * ties. At a binary power of two the rounding interval is asymmetric: + * its closest decimal can fail while the adjacent decimal succeeds + * (2^-24 needs 5.960464477539063e-08). Check the neighbors too. */ + double magnitude = fabs(n); + uint64_t significand = 0; + int scale = 0; + /* Existence of a round-tripping decimal is monotone in precision: + * appending a zero preserves that decimal at the next precision. + * Most binary64 values need 16 or 17 digits; try 15 first, then 16/17. + * If 15 works, test 1 and binary-search the remaining smaller range. */ + int low = 1, high = 17, precision = 15; + while (low <= high) { + char rounded[32]; + snprintf(rounded, sizeof rounded, "%.*e", precision - 1, magnitude); + uint64_t nearest = 0; + const char *p = rounded; + while (*p != 'e' && *p != 'E') { + /* Ignore the locale's decimal separator. Candidate text below + * has an integer significand, so strtod needs no separator. */ + if (*p >= '0' && *p <= '9') nearest = nearest * 10 + (unsigned)(*p - '0'); + p++; + } + p++; + int exponent_negative = *p == '-'; + if (*p == '+' || *p == '-') p++; + int exponent = 0; + while (*p >= '0' && *p <= '9') exponent = exponent * 10 + (*p++ - '0'); + if (exponent_negative) exponent = -exponent; + int rounded_scale = exponent - precision + 1; + uint64_t minimum = 1; + for (int i = 1; i < precision; i++) minimum *= 10; + int found = 0; + for (int neighbor = 0; neighbor < 3; neighbor++) { + uint64_t candidate = neighbor == 0 ? nearest : + neighbor == 1 ? nearest - 1 : nearest + 1; + int candidate_scale = rounded_scale; + /* The predecessor of 1.00eE is 9.99e(E-1), where the decimal + * grid is ten times finer. Keep the actual p-digit neighbor. */ + if (neighbor == 1 && nearest == minimum) { + candidate = nearest * 10 - 1; + candidate_scale--; + } + if (candidate == 0) continue; + /* uint64 digits + e + signed int32 exponent + NUL: at most 33. */ + char decimal[40]; + snprintf(decimal, sizeof decimal, "%llue%+d", + (unsigned long long)candidate, candidate_scale); + if (strtod(decimal, NULL) == magnitude) { + significand = candidate; + scale = candidate_scale; + found = 1; + break; + } + } + if (found) { + high = precision - 1; + precision = precision == 15 ? 1 : (low + high) / 2; + } else { + low = precision + 1; + precision = precision >= 15 ? precision + 1 : (low + high) / 2; + } } + + while (significand % 10 == 0) { significand /= 10; scale++; } + char digits[24], text[32]; + snprintf(digits, sizeof digits, "%llu", (unsigned long long)significand); + int count = (int)strlen(digits); + int exponent = scale + count - 1; + int len = 0; + if (neg) text[len++] = '-'; + if (exponent >= -4 && exponent < 16) { + int point = exponent + 1; + if (point <= 0) { + text[len++] = '0'; + text[len++] = '.'; + for (int i = 0; i < -point; i++) text[len++] = '0'; + for (int i = 0; i < count; i++) text[len++] = digits[i]; + } else { + for (int i = 0; i < point || i < count; i++) { + if (i == point) text[len++] = '.'; + text[len++] = i < count ? digits[i] : '0'; + } + if (point >= count) { text[len++] = '.'; text[len++] = '0'; } + } + text[len] = '\0'; + } else { + text[len++] = digits[0]; + if (count > 1) { + text[len++] = '.'; + for (int i = 1; i < count; i++) text[len++] = digits[i]; + } + snprintf(text + len, sizeof text - (size_t)len, "e%+03d", exponent); + } + snprintf(buf, nbuf, "%s", text); } char* value_to_string(Value *v) { @@ -3381,8 +4091,12 @@ char* value_to_string(Value *v) { char buf[256]; switch (v->type) { case VAL_NULL: return xstrdup("null"); - case VAL_NUM: { - eigs_num_text(buf, sizeof(buf), VAL_NUM_RAW(v)); + case VAL_FLOAT: { + eigs_num_text(buf, sizeof(buf), VAL_F64_RAW(v)); + return xstrdup(buf); + } + case VAL_INT: { /* #1758 R1.2a: an int prints its exact digits */ + eigs_int_text(buf, sizeof(buf), VAL_I64_RAW(v)); return xstrdup(buf); } case VAL_STR: return xstrdup(v->data.str); @@ -4756,7 +5470,8 @@ static int gc_value_is_node(Value *v) { case VAL_FN: return 1; /* Leaves: no owned edges out, so no rows and never a cycle member. */ - case VAL_NUM: + case VAL_FLOAT: + case VAL_INT: case VAL_STR: case VAL_NULL: case VAL_BUILTIN: diff --git a/src/eigenscript.h b/src/eigenscript.h index 41358418e..c006b50d8 100644 --- a/src/eigenscript.h +++ b/src/eigenscript.h @@ -14,7 +14,7 @@ * defines both; the default build (none defined) is the pooled baseline. The * call sites below test the per-layer macros only — never EIGS_POOL_OFF * directly — so this is the single point of truth for the union. - * EIGS_POOL_OFF_NUM — Value NUM freelist (free_value/make_num/recycle_intermediate) + * EIGS_POOL_OFF_NUM — Value NUM freelist (free_value/make_float/recycle_intermediate) * EIGS_POOL_OFF_CALLENV — call-env recycling (vm_park_call_env/vm_take_call_env) */ #ifdef EIGS_POOL_OFF # ifndef EIGS_POOL_OFF_NUM @@ -152,6 +152,13 @@ static inline int eigs_name_is_reserved(const char *name) { #define MAX_PARAMS 16 #define MAX_MATCH_CASES 64 +/* #1758 R1.3: a source number retains its literal kind through lexing and + * parsing. Decimal integers outside int64 are floats; hex integers wrap. */ +typedef struct { + int is_int; + union { int64_t i64; double f64; } value; +} EigsNum; + /* ---- Tokenizer ---- */ typedef enum { @@ -180,12 +187,13 @@ typedef enum { TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ, TOK_PERCENT_EQ, TOK_AMP_EQ, TOK_BITOR_EQ, TOK_CARET_EQ, TOK_SHL_EQ, TOK_SHR_EQ, TOK_NEWLINE, TOK_INDENT, TOK_DEDENT, - TOK_EOF + TOK_EOF, + TOK_SLASH_SLASH, TOK_SLASH_SLASH_EQ /* #1758 N4/Q6: append; existing token IDs stay fixed */ } TokType; typedef struct { TokType type; - double num_val; + EigsNum number; char *str_val; int line; int col; /* 0-based column offset */ @@ -224,7 +232,7 @@ typedef enum { AST_TRY, AST_DICT, AST_DOT, AST_BREAK, AST_CONTINUE, AST_DOT_ASSIGN, AST_IMPORT, AST_MATCH, AST_LAMBDA, AST_UNOBSERVED, AST_INDEX_ASSIGN, AST_LIST_PATTERN_ASSIGN, AST_SLICE, - AST_BOOL /* #1637: `true`/`false`; the value is data.num (1 or 0) */ + AST_BOOL /* #1637: `true`/`false`; the value is data.boolean (1 or 0) */ } ASTType; typedef struct ASTNode ASTNode; @@ -241,7 +249,8 @@ struct ASTNode { * parenthesized literal list is a single call * argument, never spread (#355) */ union { - double num; + EigsNum number; + int boolean; char *str; struct { char *name; } ident; struct { char op[4]; ASTNode *left; ASTNode *right; uint8_t synth; } binop; /* synth: f-string `+` (#1425) */ @@ -279,25 +288,49 @@ struct ASTNode { /* ---- Value types ---- */ typedef enum { - VAL_NUM, VAL_STR, VAL_LIST, VAL_FN, VAL_BUILTIN, VAL_NULL, - /* #1690: keep the surviving type tag numbers unchanged. */ + /* #1758 R1.2a: a double. A number Value is a VAL_FLOAT or a + * VAL_INT; `type of` says "num" for both (decision (B)). */ + VAL_FLOAT, VAL_STR, VAL_LIST, VAL_FN, VAL_BUILTIN, VAL_NULL, + /* #1690: preserve surviving tags; #1758 appends VAL_INT below. */ VAL_DICT = 7, VAL_BUFFER, VAL_TEXT_BUILDER, /* #1637: a distinct boolean. Two immortal singletons (make_bool); the * slot layer carries it as the TAG_BOOL immediate. Arithmetic on it and * `==` against a number raise. */ - VAL_BOOL + VAL_BOOL, + /* #1758: an exact int64. Stack/container slots use an SMI when it fits; + * the Value API and out-of-SMI values use make_int (design 2.1). */ + VAL_INT, } ValType; typedef struct Value Value; typedef Value* (*BuiltinFn)(Value* arg); +/* #1758 R1.2: a number Value is a VAL_FLOAT or a VAL_INT. THE test a number + * test uses when an int belongs on the same path as a float (a builtin's + * number test admits an int and reads it through the bridge, design 2.7). */ +#define val_is_number(v) ((v)->type == VAL_FLOAT || (v)->type == VAL_INT) +/* #1758 N2 (design 7): an int and a float compare exactly, by value. Returns + * -1, 0 or 1 for i < f, i == f, i > f, and 2 when f is NaN (unordered: every + * comparison is false except !=). Lua's LTintfloat/EQintfloat give the same + * answers. Pure; neither operand is rounded through the other kind. */ +static inline int eigs_cmp_int_float(int64_t i, double f) { + if (f != f) return 2; + if (f >= 9223372036854775808.0) return -1; /* f above every int64 */ + if (f < -9223372036854775808.0) return 1; /* f below every int64 */ + double fl = floor(f); /* in [-2^63, 2^63): the cast is exact */ + int64_t il = (int64_t)fl; + if (i != il) return i < il ? -1 : 1; + return f != fl ? -1 : 0; /* i == floor(f) < f when f has a fraction */ +} /* #1637 round 4: the ONE raw way into a Value's number (an lvalue: also the - * write). Only for a Value whose type is proven VAL_NUM, in a function listed - * in tools/num_read_allowlist.txt with its use count. Everything else uses - * eigs_num_arg / eigs_list_num / eigs_opt_num. VAL_NUM_OFFSET is the same - * member for JIT-emitted loads and stores. */ -#define VAL_NUM_RAW(v) ((v)->data.num_) -#define VAL_NUM_OFFSET ((int32_t)offsetof(Value, data.num_)) + * write). Only for a Value whose type is proven (VAL_FLOAT for the double, + * VAL_INT for the int64), in a function listed in tools/num_read_allowlist.txt + * with its use count. Everything else uses eigs_float_arg / eigs_list_float / + * eigs_opt_float. VAL_F64_OFFSET is the double member for JIT-emitted loads and + * stores (#1758 R1.2). */ +#define VAL_F64_RAW(v) ((v)->data.f64_) +#define VAL_I64_RAW(v) ((v)->data.i64_) +#define VAL_F64_OFFSET ((int32_t)offsetof(Value, data.f64_)) /* EigsSlot union — full inline helpers in value_slot.h, which is * included below after the Value struct is fully declared. We need the @@ -439,13 +472,14 @@ struct Value { ValType type; union { /* #1637 round 4: renamed so no unchecked read compiles. A Value's - * number is read through the checking accessors (eigs_num_arg, - * eigs_list_num, eigs_opt_num), which raise on a bool or any other - * non-number, or through VAL_NUM_RAW where the type is already - * proven -- and every VAL_NUM_RAW use sits in a function on the + * number is read through the checking accessors (eigs_float_arg, + * eigs_list_float, eigs_opt_float), which raise on a bool or any other + * non-number, or through VAL_F64_RAW where the type is already + * proven -- and every VAL_F64_RAW use sits in a function on the * reviewed list tools/num_read_allowlist.txt, pinned by count * (tools/num_read_check.sh). */ - double num_; + double f64_; /* VAL_FLOAT (#1758 R1.2a) */ + int64_t i64_; /* VAL_INT (#1758) */ int boolean; /* VAL_BOOL: 0 or 1 (only the two singletons exist) */ /* VAL_STR payload: NUL-terminated bytes. * @@ -520,10 +554,10 @@ _Static_assert(sizeof(((Value *)0)->data.strv) <= sizeof(((Value *)0)->data.fn), * compiler instruments for ASan; every other build expands these to nothing, * so release objects are byte-identical. * - * Parked NUM (free_value / recycle_intermediate -> make_num): the whole Value + * Parked NUM (free_value / recycle_intermediate -> make_float): the whole Value * is poisoned EXCEPT the link word, the sizeof(Value *) bytes the freelist * threads its next pointer through (EIGS_NUM_LINK). Only the pool's own push, - * pop (make_num) and drain (eigs_thread_drain_caches) touch those bytes. In + * pop (make_float) and drain (eigs_thread_drain_caches) touch those bytes. In * every other build the link overlays the number itself (offset * offsetof(Value, data)), which would leave a stale NUMBER read -- the * commonest stale read of a recycled NUM -- undetectable; so under ASan the @@ -1394,30 +1428,55 @@ extern __thread EigsThread *eigs_current; * and false in every argument slot of every builtin must raise unless the * slot is on its reviewed any-value list. */ int eigs_arg_has_bool(const Value *arg); -/* #1637: THE checked number reads. C code turning a Value into a double goes - * through one of these (tools/num_read_check.sh enforces it: a raw - * `->data.num` read needs a VAL_NUM test in the same function, or a reviewed - * allowlist entry). A bool -- or any other non-number -- raises a type error - * naming `who` (a `builtin_` prefix is dropped) and reads as 0.0; the caller - * checks g_has_error before acting on the value. - * eigs_num_arg(v, who) v must be a number - * eigs_list_num(l, i, who) element i of the argument list must exist and - * be a number - * eigs_opt_num(v, &d, who) optional: absent/null -> 0 (use the default); - * a number -> 1 with *d set; anything else raises - * and returns 0 */ -double eigs_num_arg_slow(const Value *v, const char *who); -static inline double eigs_num_arg(const Value *v, const char *who) { - if (__builtin_expect(v && v->type == VAL_NUM, 1)) return VAL_NUM_RAW(v); - return eigs_num_arg_slow(v, who); -} -double eigs_list_num(const Value *list, int i, const char *who); -/* #1637 round 4: an ELEMENT a builtin reads as a number (a tensor cell, a - * byte). A bool raises in every strict mode; any other non-number raises - * under EIGS_STRICT and reads as the documented 0.0 under EIGS_STRICT=0. - * The caller checks g_has_error. */ -double eigs_elem_num(const Value *v, const char *who); -int eigs_opt_num(const Value *v, double *out, const char *who); +/* #1637/#1758: required, element and optional checked readers are split + * by numeric contract below. Raw payload macros require a reviewed kind + * proof in tools/num_read_allowlist.txt. Callers stop on g_has_error. */ +/* #1758 R1.6a: final numeric readers. All inputs are BORROWED, including + * on a raise: no reader allocates, retains, or releases its argument. Every + * floating input must be finite. Required/list reads reject non-numbers; + * element reads retain the legacy non-strict zero default (never for bool); + * optional reads return 0 for absent/null or a raise, leaving *out unchanged. + * A successful range/optional read returns 1. Callers stop on g_has_error. */ +EigsNum eigs_number_arg(const Value *v, const char *who); +EigsNum eigs_list_number(const Value *list, int i, const char *who); +EigsNum eigs_elem_number(const Value *v, const char *who); +int eigs_opt_number(const Value *v, EigsNum *out, const char *who); +double eigs_float_arg(const Value *v, const char *who); +double eigs_list_float(const Value *list, int i, const char *who); +double eigs_elem_float(const Value *v, const char *who); +int eigs_opt_float(const Value *v, double *out, const char *who); +int64_t eigs_int_arg(const Value *v, const char *who); +int64_t eigs_list_int(const Value *list, int i, const char *who); +int64_t eigs_elem_int(const Value *v, const char *who); +int eigs_opt_int(const Value *v, int64_t *out, const char *who); +uint8_t eigs_byte_arg(const Value *v, const char *who); +uint8_t eigs_list_byte(const Value *list, int i, const char *who); +uint8_t eigs_elem_byte(const Value *v, const char *who); +int eigs_opt_byte(const Value *v, uint8_t *out, const char *who); +int eigs_int_arg_range(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_list_int_range(const Value *list, int i, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_elem_int_range(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_opt_int_range(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); +/* Q7 geometry: clipped pixel bounds plus borrowed continuous coordinates. + * CIRCLE/RRECT callers iterate only y..y+h and clip each span to x..x+w. */ +typedef enum { EIGS_POINT, EIGS_RECT, EIGS_LINE, EIGS_CIRCLE, EIGS_RRECT } EigsShapeKind; +typedef struct { int x, y, w, h; } EigsRect; +typedef struct { + EigsShapeKind kind; + int empty, x, y, w, h, x2, y2; + double raw[5]; +} EigsShape; +int eigs_geom_shape(const Value *args, int first, EigsShapeKind kind, + const EigsRect *drawable, EigsShape *out, const char *who); +int eigs_bounded_arg(const Value *v, double lo, double hi, int *out, const char *who); +int eigs_list_bounded(const Value *list, int i, double lo, double hi, int *out, const char *who); +int eigs_elem_bounded(const Value *v, double lo, double hi, int *out, const char *who); +int eigs_opt_bounded(const Value *v, double lo, double hi, int *out, const char *who); +int eigs_magnitude_arg(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_list_magnitude(const Value *list, int i, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_elem_magnitude(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); +int eigs_opt_magnitude(const Value *v, int64_t lo, int64_t hi, int64_t *out, const char *who); + /* #1637: the builtin-call bool gate -- the structural half of "a bool is * not a number". Every call of a C builtin goes through eigs_call_builtin * (tools/num_read_check.sh refuses a bare `->data.builtin(` call). When the @@ -1504,7 +1563,7 @@ void eigs_bool_gate_exempt(BuiltinFn fn); /* a host function: never gated */ * simple early return. The raise still returns, so the caller stops before * consuming the coerced value. * - * These sites have no `return make_num(0)` to enumerate. A probe that + * These sites have no `return make_float(0)` to enumerate. A probe that * stays silent under strict is what tools/strict_differential.sh catches. */ /* PLACEMENT IS LOAD-BEARING: this RETURNS, so it must sit BEFORE anything the * function has allocated and still owns, or the raise abandons it. Put the @@ -1791,7 +1850,9 @@ void strbuf_free(strbuf *b); /* ---- Value constructors ---- */ -Value* make_num(double n); +Value* make_float(double n); +Value* make_float_permanent(double n); +Value* make_int(int64_t n); /* owned, unpooled int box; slots canonicalize small ints */ void recycle_intermediate(Value *v); Value* make_str(const char *s); Value* make_str_len(const char *s, size_t n); /* #1183: caller knows strlen(s) */ @@ -1801,7 +1862,6 @@ Value* make_null(void); /* #1637: the immortal true/false singletons (refcount no-ops, like null). */ Value* make_bool(int b); Value* make_list(int capacity); -Value* make_num_permanent(double n); /* unpooled make_num; needs no VM thread */ Value* make_list_heap(int capacity); Value* make_text_builder(void); Value* make_fn(const char *name, char **params, int param_count, Env *closure); @@ -1897,10 +1957,11 @@ void free_value(Value *v); * branch stays one call on a path a finite program never takes. */ void eigs_strict_nan_raise(const char *who); -/* Side-effect-free VALUE mapping of num_guard, for readers such as observer - * equality which must see language numbers without changing interpreter - * state or raising in strict mode. Keep its branches in sync with - * num_guard; tests/test_num_guard_value.c pins their bit-level results. */ +/* Legacy scalar VALUE mapping of num_guard, retained for the bit-level + * tests/test_num_guard_value.c fixture. Phase2 buffer/container equality uses + * the stored IEEE doubles directly (S10-D1), so runtime builds may not call + * this helper. Keep its mapping in sync with num_guard. */ +__attribute__((unused)) static inline double num_guard_value(double x) { if (x != x) return 0.0; if (x > EIGS_NUM_MAX) return EIGS_NUM_MAX; @@ -1921,22 +1982,6 @@ static inline double num_guard(double x) { return x; } -/* #1417: VAL_BUFFER storage is an unboxed numerical work area, so kernels - * can transiently leave an IEEE NaN or infinity in it. The language's - * finite-number invariant applies when an element crosses the buffer/scalar - * boundary: every scalar read goes through this helper. Keeping the guard at - * that boundary also lets bulk kernels retain their unboxed representation. */ -static inline double buffer_read_num(const Value *buffer, int64_t index) { - return num_guard(buffer->data.buffer.data[index]); -} - -/* Numeric byte consumers truncate and wrap modulo 256. Reduce in double - * first: even a normalized finite number can be outside the C int range. - * The caller must normalize the input and stop on a raised read first. */ -static inline unsigned char finite_num_to_byte(double x) { - return (unsigned char)((int)fmod(x, 256.0) & 0xFF); -} - /* #1740: a bitwise operand converts to int64 only if it is an EXACT integer * in [-2^63, 2^63) -- Lua 5.4's rule (manual 3.4.3). Returns 0 for a * fractional or out-of-range number; the caller raises `value`. Range test @@ -1949,15 +1994,24 @@ static inline int num_to_i64_exact(double x, int64_t *out) { *out = i; return 1; } -#define EIGS_NO_INT_REP_FMT "'%s': number %.17g has no integer representation" - -__attribute__((unused)) -static inline unsigned char buffer_read_byte(const Value *buffer, int64_t index) { - double x = buffer_read_num(buffer, index); - if (g_has_error) return 0; - return finite_num_to_byte(x); +/* #1758 N8: Lua 5.4 shifts operate on an unsigned 64-bit pattern. + * Bound the signed count BEFORE negating it (INT64_MIN is valid input). + * Convert the result pattern without an out-of-range unsigned-to-signed cast. */ +static inline int64_t eigs_bit_shift(int64_t a, int64_t count, int right) { + if (count <= -64 || count >= 64) return 0; + if (count < 0) { count = -count; right = !right; } + uint64_t bits = right ? (uint64_t)a >> count : (uint64_t)a << count; + return bits <= INT64_MAX ? (int64_t)bits : -1 - (int64_t)(UINT64_MAX - bits); } +/* Final buffer readers refuse non-finite payloads. A finite data read + * retains the current scalar guard; byte/index reads also require exactness. */ +double buffer_read_f64(const Value *buffer, int64_t index); +int buffer_byte_at(const Value *buffer, int64_t index, uint8_t *out, const char *who); +int buffer_index_at(const Value *buffer, int64_t index, int64_t *out, const char *who); + +#define EIGS_NO_INT_REP_FMT "'%s': number %.17g has no integer representation" + /* #971: num_guard for a builtin whose result CAN be NaN on the current tree * (`pow` of a negative base with a fractional exponent, `num of "nan"`, * `f64_from_bytes` of a NaN bit pattern, `matmul`'s inf-inf accumulation, @@ -2001,7 +2055,8 @@ static inline double num_guard_named(double x, const char *who) { * inside on GC-enabled / not-collecting / single-threaded. */ void gc_note_possible_root(Value *v); -static inline void val_incref(Value *v) { +/* Keep reference acquisition inline as the numeric slow paths grow (#1758). */ +static inline __attribute__((always_inline)) void val_incref(Value *v) { if (v && v->type != VAL_NULL && v->type != VAL_BOOL) { if (__builtin_expect(g_vm_multithreaded, 0)) __atomic_add_fetch(&v->refcount, 1, __ATOMIC_RELAXED); @@ -2130,7 +2185,7 @@ Value* env_get_hashed(Env *env, const char *name, uint32_t h); Value* env_get_local_hashed(Env *env, const char *name, uint32_t h); void env_set_local_hashed(Env *env, const char *name, uint32_t h, Value *val); /* Slot-flavored fast paths: take/produce EigsSlot directly so immediates - * never round-trip through make_num + val_decref. Reference-count + * never round-trip through make_float + val_decref. Reference-count * semantics match the Value* variants: env *borrows* the input slot and * incref's internally, *_get returns a slot the caller must slot_decref. */ /* #868/#908: how many assignments this binding has seen, for the `when ` @@ -2283,6 +2338,7 @@ char* value_to_string(Value *v); * `str of`, all three JSON encoders, the SIGUSR1 observer dump — so a copy * with a different precision cannot reappear. Needs 32 bytes. */ void eigs_num_text(char *buf, size_t nbuf, double n); +void eigs_int_text(char *buf, size_t nbuf, int64_t i); /* #1758 R1.2a: exact digits */ void eigs_json_escape_string(strbuf *out, const char *s); /* #880: decode a JSON string body (s[*pos] = first byte after the opening * quote) into `out`, leaving *pos past the closing quote. Returns whether @@ -2584,5 +2640,6 @@ void eigs_ensure_random_seeded(void); * entry points runtime code may use, so seeding and draws share one lock. */ double eigs_random_double(void); long eigs_random_long(void); +void eigs_seed_random(long seed); #endif /* EIGENSCRIPT_H */ diff --git a/src/eigs_embed.c b/src/eigs_embed.c index 52cdab69a..d9e78de3b 100644 --- a/src/eigs_embed.c +++ b/src/eigs_embed.c @@ -369,7 +369,8 @@ EigsValue *eigs_get_global(const char *name) { /* ---- Values ------------------------------------------------------- */ -EigsValue *eigs_value_new_num(double n) { return make_num(n); } +EigsValue *eigs_value_new_num(double n) { return make_float(n); } +EigsValue *eigs_value_new_int(int64_t n) { return make_int(n); } EigsValue *eigs_value_new_string(const char *s) { return make_str(s ? s : ""); } EigsValue *eigs_value_new_null(void) { return make_null(); } EigsValue *eigs_value_new_bool(int b) { return make_bool(b != 0); } @@ -382,7 +383,8 @@ void eigs_value_release(EigsValue *v) { if (v) val_decref(v); } EigsValueType eigs_value_type(EigsValue *v) { if (!v) return EIGS_TYPE_NULL; switch (v->type) { - case VAL_NUM: return EIGS_TYPE_NUM; + case VAL_FLOAT: return EIGS_TYPE_NUM; + case VAL_INT: return EIGS_TYPE_NUM; /* #1758 (Q3): one embed number type */ case VAL_STR: return EIGS_TYPE_STR; case VAL_LIST: return EIGS_TYPE_LIST; case VAL_DICT: return EIGS_TYPE_DICT; @@ -427,11 +429,39 @@ int eigs_value_as_bool(EigsValue *v) { * plausible false-as-zero. Other non-numbers keep the documented 0.0. Hosts * test eigs_value_type first, or read a bool with eigs_value_as_bool. */ double eigs_value_as_num(EigsValue *v) { - if (!v || v->type != VAL_NUM) { - embed_value_error(EK_TYPE, "eigs_value_as_num: expected a number"); - return (v && v->type == VAL_BOOL) ? NAN : 0.0; + /* Phase 2 (Q3): an int is a number, converted like lua_tonumber; a float + * is read raw. Anything else reports through #1932's pending error + * (attached threads only), keeping #1637's NaN for a bool and 0.0 + * otherwise. */ + if (v && v->type == VAL_INT) return (double)VAL_I64_RAW(v); + if (v && v->type == VAL_FLOAT) return VAL_F64_RAW(v); + embed_value_error(EK_TYPE, "eigs_value_as_num: expected a number"); + return (v && v->type == VAL_BOOL) ? NAN : 0.0; +} + +int64_t eigs_value_as_int(EigsValue *v, int *ok) { + if (ok) *ok = 0; + if (v && v->type == VAL_INT) { + if (ok) *ok = 1; + return VAL_I64_RAW(v); } - return VAL_NUM_RAW(v); + if (v && v->type == VAL_FLOAT) { + /* The exactness comparison must see subnormals even in a host + * callback using DAZ. Preserve host flags as well as its controls. */ + EmbedFenv fenv; + embed_fenv_enter(&fenv); + int64_t result = 0; + int exact = num_to_i64_exact(VAL_F64_RAW(v), &result); + embed_fenv_leave(&fenv); + if (ok) *ok = exact; + return exact ? result : 0; + } + embed_value_error(EK_TYPE, "eigs_value_as_int: expected a number"); + return 0; +} + +int eigs_value_is_int(EigsValue *v) { + return v && v->type == VAL_INT; } const char *eigs_value_as_string(EigsValue *v) { @@ -500,7 +530,8 @@ double eigs_value_buffer_get(EigsValue *v, int i) { i, v->data.buffer.count); return 0.0; } - return buffer_read_num(v, i); + /* R1.6z-D1: the public C API exposes stored data, bit for bit. */ + return v->data.buffer.data[i]; } void eigs_value_buffer_set(EigsValue *v, int i, double x) { @@ -632,7 +663,7 @@ void eigs_trace_record_nondet(const char *name, EigsValue *v) { /* #1637: EIGS_KIND(EIGS_TYPE_X) bits -> the runtime's ValType bits. */ int eigs_trace_declare_kind(const char *name, unsigned kinds) { static const struct { EigsValueType e; ValType v; } map[] = { - {EIGS_TYPE_NULL, VAL_NULL}, {EIGS_TYPE_NUM, VAL_NUM}, {EIGS_TYPE_STR, VAL_STR}, + {EIGS_TYPE_NULL, VAL_NULL}, {EIGS_TYPE_NUM, VAL_FLOAT}, {EIGS_TYPE_NUM, VAL_INT}, {EIGS_TYPE_STR, VAL_STR}, {EIGS_TYPE_LIST, VAL_LIST}, {EIGS_TYPE_DICT, VAL_DICT}, {EIGS_TYPE_BUFFER, VAL_BUFFER}, {EIGS_TYPE_BOOL, VAL_BOOL}, }; diff --git a/src/eigs_embed.h b/src/eigs_embed.h index 492aba30f..9dccd17ee 100644 --- a/src/eigs_embed.h +++ b/src/eigs_embed.h @@ -29,6 +29,7 @@ #define EIGS_EMBED_H #include +#include #ifdef __cplusplus extern "C" { @@ -148,7 +149,7 @@ EigsValue *eigs_get_global(const char *name); typedef enum { EIGS_TYPE_NULL = 0, - EIGS_TYPE_NUM, + EIGS_TYPE_NUM, /* int and float, like Lua's LUA_TNUMBER */ EIGS_TYPE_STR, EIGS_TYPE_LIST, EIGS_TYPE_DICT, @@ -159,6 +160,7 @@ typedef enum { } EigsValueType; EigsValue *eigs_value_new_num(double n); +EigsValue *eigs_value_new_int(int64_t n); /* exact signed int64 */ EigsValue *eigs_value_new_string(const char *s); EigsValue *eigs_value_new_null(void); EigsValue *eigs_value_new_bool(int b); /* #1637: the true/false value */ @@ -169,6 +171,11 @@ void eigs_value_release(EigsValue *v); EigsValueType eigs_value_type(EigsValue *v); double eigs_value_as_num(EigsValue *v); /* wrong type: sentinel + error; bool sentinel is NaN */ +/* Like lua_tointegerx: exact int64 from int or integral float. Numeric + * refusal returns 0 with *ok=0 and sets no error; a non-number also sets a + * type error. ok may be NULL. Existing pending errors are never cleared. */ +int64_t eigs_value_as_int(EigsValue *v, int *ok); +int eigs_value_is_int(EigsValue *v); /* kind only, like lua_isinteger */ int eigs_value_as_bool(EigsValue *v); /* wrong type: 0 + error */ const char *eigs_value_as_string(EigsValue *v); /* NULL if not str; borrowed */ @@ -196,6 +203,9 @@ EigsValue *eigs_value_new_buffer(int count); /* zeroed, 1-D (unshaped) */ * unchanged; inspect/clear the error through the error API above. A thread * with no attached state has no error channel and gets the sentinel alone. */ int eigs_value_buffer_len(EigsValue *v); /* wrong type: 0 + error */ +/* Valid reads return the stored double exactly: NaN payloads, infinities and + * signed zero included. No normalization, math-flag write or raise. This + * mirrors the setter's raw double store; C callers can test isfinite(). */ double eigs_value_buffer_get(EigsValue *v, int i); /* OOB: 0.0 + error */ void eigs_value_buffer_set(EigsValue *v, int i, double x); /* OOB: no-op + error */ diff --git a/src/embed_smoke.c b/src/embed_smoke.c index e87061e38..6b11fc4d7 100644 --- a/src/embed_smoke.c +++ b/src/embed_smoke.c @@ -43,6 +43,115 @@ static int old_task_ran = 0; } \ } while (0) +/* #1758 Q3: public C API, including boxed ints, exact conversion bounds, + * optional success flags, and the two distinct failure contracts. */ +static void test_embed_int(void) { + int before = failures, checks = 0; +#define INT_CHECK(cond, msg) do { checks++; CHECK((cond), (msg)); } while (0) + const int64_t ints[] = {0, 3, -3, INT64_C(9007199254740993), + INT64_MIN, INT64_MAX}; + for (size_t i = 0; i < sizeof ints / sizeof ints[0]; i++) { + EigsValue *v = eigs_value_new_int(ints[i]); + int ok = -1; + INT_CHECK(eigs_value_type(v) == EIGS_TYPE_NUM, "int has public number type"); + INT_CHECK(eigs_value_is_int(v), "int predicate includes boxed int64"); + INT_CHECK(eigs_value_as_int(v, &ok) == ints[i] && ok == 1, + "int round-trips all 64 bits"); + INT_CHECK(eigs_value_as_int(v, NULL) == ints[i], "int permits NULL flag"); + INT_CHECK(eigs_value_as_num(v) == (double)ints[i], "as_num converts int"); + INT_CHECK(!eigs_has_error(), "valid int access leaves no error"); + /* Exercise the owning container bridge with a genuinely boxed int. */ + EigsValue *list = eigs_value_new_list(1); + eigs_value_list_append(list, v); + eigs_value_release(v); + v = eigs_value_list_get(list, 0); + INT_CHECK(eigs_value_as_int(v, &ok) == ints[i] && ok == 1, + "list bridge preserves exact int after caller releases"); + eigs_value_release(v); + eigs_value_release(list); + } + const struct { double n; int ok; int64_t want; } floats[] = { + {3.0, 1, 3}, {-0.0, 1, 0}, {1.5, 0, 0}, + {-0x1p63, 1, INT64_MIN}, {0x1p63, 0, 0}, + {0x1.fffffffffffffp62, 1, INT64_MAX - 1023}, + {-0x1.0000000000001p63, 0, 0}, {0x1p-1074, 0, 0} + }; + for (size_t i = 0; i < sizeof floats / sizeof floats[0]; i++) { + EigsValue *v = eigs_value_new_num(floats[i].n); + int ok = -1; + INT_CHECK(eigs_value_type(v) == EIGS_TYPE_NUM, "float has public number type"); + INT_CHECK(!eigs_value_is_int(v), "integral float is never int kind"); + INT_CHECK(eigs_value_as_int(v, &ok) == floats[i].want && ok == floats[i].ok, + "float exact int64 conversion and refusal bounds"); + INT_CHECK(eigs_value_as_int(v, NULL) == floats[i].want, + "float permits NULL success flag"); + INT_CHECK(!eigs_has_error(), "numeric refusal sets no embed error"); + eigs_value_release(v); + } + /* #1969 census: as_int's float arm is a conversion boundary. Raw + * storage also exercises nonfinite payloads constructors can refuse. */ + const double raw[] = {0x1p-1074, 1.5, -0x1p63, 0x1p63, INFINITY, -INFINITY, NAN}; + const int modes[] = {FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO}; + fenv_t saved; + fegetenv(&saved); + for (size_t m = 0; m < sizeof modes / sizeof modes[0]; m++) { + for (size_t i = 0; i < sizeof raw / sizeof raw[0]; i++) { + EigsValue *v = eigs_value_new_num(0.0); + VAL_F64_RAW(v) = raw[i]; + fesetround(modes[m]); + feclearexcept(FE_ALL_EXCEPT); + feraiseexcept(FE_DIVBYZERO); +#if defined(__x86_64__) + _mm_setcsr(_mm_getcsr() | (1u << 15) | (1u << 6)); + unsigned csr = _mm_getcsr(); +#endif + int flags = fetestexcept(FE_ALL_EXCEPT), ok = -1; + int64_t got = eigs_value_as_int(v, &ok); + INT_CHECK(ok == (i == 2) && got == (i == 2 ? INT64_MIN : 0), + "as_int exactness survives every host mode and DAZ"); + INT_CHECK(fegetround() == modes[m] && fetestexcept(FE_ALL_EXCEPT) == flags, + "as_int restores host rounding and sticky flags"); +#if defined(__x86_64__) + INT_CHECK(_mm_getcsr() == csr, "as_int restores FTZ/DAZ and MXCSR flags"); +#endif + INT_CHECK(!eigs_has_error(), "raw numeric refusal sets no embed error"); + fesetenv(&saved); + eigs_value_release(v); + } + } + fesetenv(&saved); + EigsValue *wrong[] = {NULL, eigs_value_new_null(), eigs_value_new_bool(1), + eigs_value_new_string("3"), eigs_value_new_list(0), + eigs_value_new_dict(0)}; + for (size_t i = 0; i < sizeof wrong / sizeof wrong[0]; i++) { + int ok = -1; + INT_CHECK(!eigs_value_is_int(wrong[i]) && !eigs_has_error(), + "kind predicate is pure on non-numbers"); + INT_CHECK(eigs_value_as_int(wrong[i], &ok) == 0 && ok == 0, + "non-number fails with zero success flag"); + INT_CHECK(eigs_has_error() && strcmp(eigs_last_error_kind(), "type_mismatch") == 0 + && strstr(eigs_last_error_message(), "eigs_value_as_int"), + "non-number reports embed type error naming accessor"); + eigs_clear_error(); + INT_CHECK(eigs_value_as_int(wrong[i], NULL) == 0 && eigs_has_error(), + "wrong type with NULL flag still reports error"); + eigs_clear_error(); + eigs_value_release(wrong[i]); + } + EigsValue *fraction = eigs_value_new_num(1.5); + eigs_value_as_int(NULL, NULL); + char pending[160]; + snprintf(pending, sizeof pending, "%s", eigs_last_error_message()); + int ok = -1; + INT_CHECK(eigs_value_as_int(fraction, &ok) == 0 && ok == 0 && + strcmp(pending, eigs_last_error_message()) == 0, + "numeric refusal preserves an existing error"); + eigs_clear_error(); + eigs_value_release(fraction); + printf("gate.N7.embed-int: checks=%d failures=%d\n", checks, failures - before); +#undef INT_CHECK +} + static EigsValue *mark_old_task_ran(EigsValue *arg) { (void)arg; old_task_ran++; @@ -112,7 +221,77 @@ static void check_fenv_eval(const char *mode_name, int mode, const char *witness eigs_clear_error(); } +/* #1969 / Phase 2 N6: exact binary64 payloads cross the public eval + * boundary and print Python repr text. Expected strings are from Python's + * struct.unpack(">d", bytes.fromhex(bits)); no decimal parser is the oracle. */ +static void check_fenv_n6_text(void) { + static const struct { const char *bits, *source, *expected; } rows[] = { + {"3fd3333333333334", "print of (f64_from_bytes of [63, 211, 51, 51, 51, 51, 51, 52])", "0.30000000000000004\n"}, + {"bfd3333333333334", "print of (f64_from_bytes of [191, 211, 51, 51, 51, 51, 51, 52])", "-0.30000000000000004\n"}, + {"0000000000000001", "print of (f64_from_bytes of [0, 0, 0, 0, 0, 0, 0, 1])", "5e-324\n"}, + {"8000000000000001", "print of (f64_from_bytes of [128, 0, 0, 0, 0, 0, 0, 1])", "-5e-324\n"}, + }; + static const struct { const char *name; int rounding; int flush; } modes[] = { + {"FE_UPWARD", FE_UPWARD, 0}, + {"FE_DOWNWARD", FE_DOWNWARD, 0}, + {"FE_TOWARDZERO", FE_TOWARDZERO, 0}, +#if defined(__x86_64__) + {"FTZ/DAZ", FE_TONEAREST, 1}, +#endif + }; + fenv_t saved; + CHECK(fegetenv(&saved) == 0, "N6 saves the host environment"); + for (size_t m = 0; m < sizeof(modes) / sizeof(modes[0]); m++) { + for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); i++) { + CHECK(fesetenv(FE_DFL_ENV) == 0, "N6 initializes the host environment"); + CHECK(fesetround(modes[m].rounding) == 0, "N6 sets the host rounding mode"); + CHECK(feraiseexcept(FE_INVALID) == 0, "N6 seeds sticky flags"); +#if defined(__x86_64__) + if (modes[m].flush) _mm_setcsr(_mm_getcsr() | (1u << 15) | (1u << 6)); + unsigned before = _mm_getcsr(); +#endif + int flags = fetestexcept(FE_ALL_EXCEPT); + FILE *capture = tmpfile(); + int host_stdout = dup(STDOUT_FILENO); + CHECK(capture && host_stdout >= 0, "N6 opens stdout capture"); + if (!capture || host_stdout < 0) { + if (capture) fclose(capture); + if (host_stdout >= 0) close(host_stdout); + continue; + } + fflush(stdout); + int redirected = dup2(fileno(capture), STDOUT_FILENO); + CHECK(redirected >= 0, "N6 redirects stdout"); + if (redirected < 0) { fclose(capture); close(host_stdout); continue; } + EigsValue *v = eigs_eval_string(rows[i].source); + int restored = fegetround() == modes[m].rounding && + fetestexcept(FE_ALL_EXCEPT) == flags; +#if defined(__x86_64__) + restored = restored && _mm_getcsr() == before; +#endif + fflush(stdout); + CHECK(dup2(host_stdout, STDOUT_FILENO) >= 0, "N6 restores stdout"); + close(host_stdout); + rewind(capture); + char output[128]; + size_t length = fread(output, 1, sizeof(output) - 1, capture); + output[length] = '\0'; + CHECK(!ferror(capture), "N6 reads captured print output"); + fclose(capture); + int ok = v && strcmp(output, rows[i].expected) == 0 && restored; + CHECK(ok, "N6 exact-bit repr and host fenv restoration"); + printf("N6-FENV %s %s %s\n", modes[m].name, rows[i].bits, ok ? "PASS" : "FAIL"); + if (!ok) fprintf(stderr, "N6 expected <%s> got <%s>; restored=%d\n", + rows[i].expected, output, restored); + eigs_value_release(v); + eigs_clear_error(); + } + } + CHECK(fesetenv(&saved) == 0, "N6 restores the original host environment"); +} + static void test_fenv_boundary(void) { + check_fenv_n6_text(); EigsValue *v; check_fenv_default_fast_path(); check_fenv_eval("FE_UPWARD", FE_UPWARD, @@ -320,8 +499,8 @@ static void test_worker_exit_lifecycle(void) { CHECK(handle && handle->type == VAL_DICT, "exit fixture owns join handle"); if (handle && handle->type == VAL_DICT) { int found_id = 0, found_gen = 0; - f.handle_id = (int)eigs_slot_num(dict_get_slot(handle, "_handle_id", &found_id), "embed_smoke"); - f.handle_gen = (uint32_t)eigs_slot_num(dict_get_slot(handle, "_handle_gen", &found_gen), "embed_smoke"); + f.handle_id = (int)eigs_slot_float(dict_get_slot(handle, "_handle_id", &found_id), "embed_smoke"); + f.handle_gen = (uint32_t)eigs_slot_float(dict_get_slot(handle, "_handle_gen", &found_gen), "embed_smoke"); CHECK(found_id && found_gen, "join handle carries its id and generation"); pthread_t requester; int made = pthread_create(&requester, NULL, exit_after_join_claim, &f) == 0; @@ -400,6 +579,18 @@ static void tape_sink(const char *bytes, size_t len, void *ud) { g_tape_len += len; } +/* A tape assertion matches WHOLE lines. `want` is one or more complete lines, + * each ending in '\n', and it must begin at the start of a tape line. A bare + * strstr cannot tell `=100` from `=1000` or from v7's `=100.0`, so it passes + * on a v6 tape and on a v7 one alike. */ +static int tape_has_lines(const char *tape, const char *want) { + size_t wl = strlen(want); + if (wl == 0 || want[wl - 1] != '\n') return 0; /* not whole lines: refuse */ + for (const char *p = tape; (p = strstr(p, want)) != NULL; p++) + if (p == tape || p[-1] == '\n') return 1; + return 0; +} + /* Host function: adds two numbers. Multi-arg calls receive a VAL_LIST. */ static EigsValue *host_add(EigsValue *arg) { if (eigs_value_type(arg) != EIGS_TYPE_LIST) return eigs_value_new_null(); @@ -479,18 +670,19 @@ int main(void) { int line_save = g_trace_current_line; g_trace_current_line = 10; - SLOT_NUM_RAW(s) = 11.0; trace_assign(NM, s); + SLOT_F64_RAW(s) = 11.0; trace_assign(NM, s); g_trace_current_line = 20; - SLOT_NUM_RAW(s) = 22.0; trace_assign(NM, s); + SLOT_F64_RAW(s) = 22.0; trace_assign(NM, s); - CHECK(trace_query_prev(NM, &out) && SLOT_NUM_RAW(out) == 11.0, + CHECK(trace_query_prev(NM, &out) && SLOT_F64_RAW(out) == 11.0, "#830: prev of a name recorded by a non-compiler producer"); /* TEMPORAL-BACKWARD: at 15 the answer is the line-10 assignment. */ - CHECK(trace_query_at(0, NM, 15, &out) && SLOT_NUM_RAW(out) == 11.0, + CHECK(trace_query_at(0, NM, 15, &out) && SLOT_F64_RAW(out) == 11.0, "#830: what-at from a non-compiler producer (line-10 value)"); - CHECK(trace_query_at(0, NM, 99, &out) && SLOT_NUM_RAW(out) == 22.0, + CHECK(trace_query_at(0, NM, 99, &out) && SLOT_F64_RAW(out) == 22.0, "#830: what-at past the last non-compiler assignment"); - CHECK(trace_query_at(2, NM, 99, &out) && SLOT_NUM_RAW(out) == 2.0, + /* S9-K1: historical assignment counts are published as exact ints. */ + CHECK(trace_query_at(2, NM, 99, &out) && slot_is_smi(out) && slot_smi_val(out) == 2, "#830: when-at counts non-compiler assignments"); g_trace_current_line = line_save; @@ -499,6 +691,7 @@ int main(void) { /* #1969: every code-running boundary installs IEEE defaults and restores * all host controls and sticky flags, including on errors/re-entry. */ test_fenv_boundary(); + test_embed_int(); /* #1394: strictness is state-local embedder configuration. Reuse the * one-shot state already keeping the process alive, then create a staged @@ -786,9 +979,21 @@ int main(void) { CHECK(eigs_has_error() && strcmp(eigs_last_error_kind(), "index_range") == 0, "negative buffer set leaves index_range"); eigs_clear_error(); - eigs_value_buffer_set(buf, 2, INFINITY); - CHECK(eigs_value_buffer_get(buf, 2) == EIGS_NUM_MAX, - "buffer non-finite get is clamped"); + /* R1.6z-D1 point 2 / N11: the C getter preserves stored bits and + * leaves both the pending-error channel and math flags alone. */ + { + double stored = INFINITY; + unsigned saved_flags = g_math_flags; + eigs_value_buffer_set(buf, 2, stored); + g_math_flags = EIGS_MATH_UNDERFLOW; + double read_back = eigs_value_buffer_get(buf, 2); + CHECK(memcmp(&read_back, &stored, sizeof stored) == 0, + "R1.6z-D1 buffer non-finite get preserves stored bits"); + CHECK(!eigs_has_error(), "R1.6z-D1 buffer non-finite get never raises"); + CHECK(g_math_flags == EIGS_MATH_UNDERFLOW, + "R1.6z-D1 buffer non-finite get preserves math flags"); + g_math_flags = saved_flags; + } /* Host buffer visible to script — SAME object, no copy at the * boundary: the script's buf_set is visible back in C. */ @@ -810,15 +1015,25 @@ int main(void) { EigsValue *nf_raw = eigs_value_new_buffer(2); EigsValue *nf_finite = eigs_value_new_buffer(2); EigsValue *nf_later = eigs_value_new_buffer(2); + EigsValue *nf_finite3 = eigs_value_new_buffer(3); + EigsValue *nf_prefix = eigs_value_new_buffer(3); eigs_value_buffer_set(nf_raw, 0, NAN); eigs_value_buffer_set(nf_raw, 1, INFINITY); eigs_value_buffer_set(nf_finite, 0, 3.0); eigs_value_buffer_set(nf_finite, 1, 4.0); eigs_value_buffer_set(nf_later, 0, INFINITY); eigs_value_buffer_set(nf_later, 1, 4.0); + eigs_value_buffer_set(nf_finite3, 0, 3.0); + eigs_value_buffer_set(nf_finite3, 1, 4.0); + eigs_value_buffer_set(nf_finite3, 2, 5.0); + eigs_value_buffer_set(nf_prefix, 0, 3.0); + eigs_value_buffer_set(nf_prefix, 1, NAN); + eigs_value_buffer_set(nf_prefix, 2, INFINITY); eigs_set_global("nf_raw", nf_raw); eigs_set_global("nf_finite", nf_finite); eigs_set_global("nf_later", nf_later); + eigs_set_global("nf_finite3", nf_finite3); + eigs_set_global("nf_prefix", nf_prefix); /* #1926: f64_from_bytes refuses fewer than 8 bytes before reading * any, so its row keeps the NaN-then-infinity prefix in a full * 8-byte input. */ @@ -830,17 +1045,41 @@ int main(void) { eigs_value_release(nf_raw); eigs_value_release(nf_finite); eigs_value_release(nf_later); + eigs_value_release(nf_finite3); + eigs_value_release(nf_prefix); eigs_state_set_strict(st, 1); r = eigs_eval_string("nf_shaped is reshape of [nf_raw, 1, 2]"); CHECK(r != NULL && !eigs_has_error(), "#1417 prepare shaped materialization control"); eigs_value_release(r); + r = eigs_eval_string("num_type of (add of [9007199254740993, 1])"); + CHECK(r && eigs_value_type(r) == EIGS_TYPE_STR && + strcmp(eigs_value_as_string(r), "int") == 0, + "#1417 recursive failure prefix has a boxed int result"); + eigs_value_release(r); + /* R1.6z-D1: equality never crosses the scalar data boundary. */ + for (int mode = 0; mode <= 1; ++mode) { + eigs_state_set_strict(st, mode); + g_math_flags = 0; + r = eigs_eval_string("[nf_raw == nf_raw, nf_raw == nf_finite, " + "list_contains of [[nf_raw, nf_later], nf_finite], " + "list_index_of of [[nf_raw, nf_later], nf_finite]]"); + CHECK(r && !eigs_has_error() && r->type == VAL_LIST && + r->data.list.count == 4 && + slot_is_bool(list_slot(r, 0)) && !slot_as_bool(list_slot(r, 0)), + "R1.6z-D1 NaN buffer equality is false without a raise"); + CHECK(g_math_flags == 0, "R1.6z-D1 equality leaves math flags alone"); + eigs_value_release(r); + eigs_clear_error(); + } + eigs_state_set_strict(st, 1); const char *nf_reads[] = { - "nf_raw == nf_raw", "nf_raw == nf_finite", - "list_contains of [[nf_raw, nf_later], nf_finite]", - "list_index_of of [[nf_raw, nf_later], nf_finite]", "sum of nf_raw", "mean of nf_raw", "norm of nf_raw", "sum of ([nf_raw, nf_later])", + "sum of nf_shaped", + "sum of ([9223372036854775807, 1, nf_raw, nf_later])", + "sum of (negative of nf_raw)", + "sum of (negative of ([9007199254740993, nf_raw, nf_later]))", "mean of ([nf_raw, nf_later])", "norm of ([nf_raw, nf_later])", "dot of [nf_raw, nf_finite]", @@ -852,6 +1091,21 @@ int main(void) { "add of [nf_raw, [0, 0]]", "add of [[0, 0], nf_raw]", "subtract of [nf_raw, [0, 0]]", "subtract of [[0, 0], nf_raw]", "multiply of [nf_raw, [0, 0]]", "multiply of [[0, 0], nf_raw]", + /* #1417: stop after a buffer kernel raises too, including both + * operand orders, shaped broadcasts, truncation and recursion. + * The nested int prefix owns a boxed result before the failure. */ + "add of [nf_raw, nf_finite]", "add of [nf_finite, nf_raw]", + "subtract of [nf_raw, nf_finite]", "subtract of [nf_finite, nf_raw]", + "multiply of [nf_raw, nf_finite]", "multiply of [nf_finite, nf_raw]", + "add of [nf_raw, 1]", "add of [1, nf_raw]", + "subtract of [nf_raw, 1]", "subtract of [1, nf_raw]", + "multiply of [nf_raw, 1]", "multiply of [1, nf_raw]", + "add of [nf_shaped, nf_finite]", "add of [nf_finite, nf_shaped]", + "add of [nf_raw, nf_finite3]", "add of [nf_finite3, nf_raw]", + "add of [nf_prefix, nf_finite3]", + "add of [[9007199254740993, nf_raw, nf_later], [1, nf_finite, nf_finite]]", + "exp of nf_prefix", + "divide of [nf_raw, nf_finite]", "pow of [nf_raw, nf_finite]", "divide of [nf_raw, [1, 1]]", "divide of [[1, 1], nf_raw]", "pow of [nf_raw, [1, 1]]", "pow of [[1, 1], nf_raw]", "add of [nf_shaped, [[0, 0]]]", "add of [[[0, 0]], nf_shaped]", @@ -1238,10 +1492,11 @@ int main(void) { { static const char *const NM = "native_after_callback"; EigsSlot s; - SLOT_NUM_RAW(s) = 1441.0; + SLOT_F64_RAW(s) = 1441.0; trace_assign(NM, s); g_tape[g_tape_len < sizeof g_tape ? g_tape_len : sizeof g_tape - 1] = 0; - CHECK(strstr(g_tape, "S 0 0 0\nA 0 native_after_callback=1441\n") != NULL, + /* Tape v7 marks an integral float as a float: `1441.0`, never `1441`. */ + CHECK(tape_has_lines(g_tape, "S 0 0 0\nA 0 native_after_callback=1441.0\n"), "native assignment after callback carries native scope"); } @@ -1261,9 +1516,9 @@ int main(void) { eigs_set_trace_sink(NULL, NULL); CHECK(g_tape_len > 0, "sink captured tape bytes"); g_tape[g_tape_len < sizeof g_tape ? g_tape_len : sizeof g_tape - 1] = 0; - CHECK(strstr(g_tape, "N 0 host_sensor=100") != NULL, "tape has N record 100"); - CHECK(strstr(g_tape, "N 0 host_sensor=101") != NULL, "tape has N record 101"); - CHECK(strstr(g_tape, "A 0 s1=100") != NULL, "tape has assignment record"); + CHECK(tape_has_lines(g_tape, "N 0 host_sensor=100.0\n"), "tape has N record 100.0"); + CHECK(tape_has_lines(g_tape, "N 0 host_sensor=101.0\n"), "tape has N record 101.0"); + CHECK(tape_has_lines(g_tape, "A 0 s1=100.0\n"), "tape has assignment record"); /* recording stopped: another live read advances but adds no bytes */ size_t tape_frozen = g_tape_len; diff --git a/src/ext_db.c b/src/ext_db.c index 509017983..82028755c 100644 --- a/src/ext_db.c +++ b/src/ext_db.c @@ -56,8 +56,14 @@ static int db_build_query(Value *arg, const char **sql, int *nparams, types[i] = DB_PARAM_OID_BOOL; } else if (v && v->type == VAL_STR) { params[i] = v->data.str; - } else if (v && v->type == VAL_NUM) { - snprintf(numbuf[i], 64, "%g", eigs_num_arg(v, __func__)); + } else if (v && v->type == VAL_INT) { + eigs_int_text(numbuf[i], 64, VAL_I64_RAW(v)); + params[i] = numbuf[i]; + } else if (v && v->type == VAL_FLOAT) { + /* N6/N14: exact round-trip parameter text, shared with display/JSON. */ + double f = eigs_float_arg(v, __func__); + if (g_has_error) return 0; + eigs_num_text(numbuf[i], 64, f); params[i] = numbuf[i]; } else { rt_error(EK_TYPE, 0, "db: parameter %d is not a string, number or bool (got %s)", @@ -173,11 +179,7 @@ static int db_require_conn(void) { #define DB_OID_FLOAT8 701 #define DB_OID_NUMERIC 1700 -/* The largest integer a double represents exactly. A bigint past this cannot - * round-trip through an EigenScript number. */ -#define DB_EXACT_INT_MAX 9007199254740992LL - -typedef enum { DBT_STR, DBT_NUM, DBT_BOOL } DbColType; +typedef enum { DBT_STR, DBT_INT, DBT_FLOAT, DBT_BOOL } DbColType; /* ONE classifier, shared by db_query_json and db_query_value, so the two * cannot drift on what a column's type is. @@ -196,57 +198,36 @@ typedef enum { DBT_STR, DBT_NUM, DBT_BOOL } DbColType; * in to a number. int8 does NOT get that treatment: count(*), sum(int) and * every serial key are int8, so leaving it a string would leave the filed * bug unfixed for the majority of real numeric queries — instead it emits a - * number and RAISES on the values a double cannot represent (below). */ + * signed int64 without a conversion through double (N14, Q-DB253). */ static DbColType db_classify(unsigned int oid) { switch (oid) { case DB_OID_BOOL: return DBT_BOOL; case DB_OID_INT2: case DB_OID_INT4: case DB_OID_OID: - case DB_OID_INT8: + case DB_OID_INT8: return DBT_INT; case DB_OID_FLOAT4: - case DB_OID_FLOAT8: return DBT_NUM; + case DB_OID_FLOAT8: return DBT_FLOAT; default: return DBT_STR; /* numeric, text, date, json, ... */ } } -/* An exact-integer column whose value is past 2^53. Raising beats rounding: - * the value genuinely has no representation as an EigenScript number, and a - * silently rounded primary key is unrecoverable downstream. The message names - * the column and the fix. Returns 0 and raises; 1 when representable. */ -static int db_check_exact_int(unsigned int oid, const char *text, const char *colname) { - if (oid != DB_OID_INT8 && oid != DB_OID_INT4 && oid != DB_OID_INT2 && oid != DB_OID_OID) - return 1; - /* strtoLL, not strtod: converting to a double FIRST rounds 9007199254740993 - * to exactly 2^53, so a strtod-based comparison cannot see the one thing - * this check exists to detect. (It shipped that way to CI once — the live - * postgres job is what caught it.) An out-of-int64 literal saturates at - * LLONG_MAX, which is past the bound, so it still raises. */ - char *end = NULL; - long long v = strtoll(text, &end, 10); - if (end == text) return 1; /* not a plain integer literal; leave it */ - if (v > DB_EXACT_INT_MAX || v < -DB_EXACT_INT_MAX) { - rt_error(EK_VALUE, 0, - "db: column '%s' value %s exceeds the exact-integer range of a " - "number (2^53); select it as text (%s::text) to keep the digits", - colname ? colname : "?", text, colname ? colname : "col"); - return 0; - } - return 1; -} - -/* Emit one column value as JSON. Returns 0 when it raised. */ -static int db_emit_json_value(strbuf *sb, unsigned int oid, int isnull, - const char *text, const char *colname) { - if (isnull) { strbuf_append(sb, "null"); return 1; } +/* Emit one column value as JSON, retaining PostgreSQL's decimal text. */ +static void db_emit_json_value(strbuf *sb, unsigned int oid, int isnull, + const char *text) { + if (isnull) { strbuf_append(sb, "null"); return; } if (!text) text = ""; switch (db_classify(oid)) { case DBT_BOOL: /* libpq text mode renders bool as exactly "t" or "f". */ strbuf_append(sb, text[0] == 't' ? "true" : "false"); - return 1; - case DBT_NUM: - if (!db_check_exact_int(oid, text, colname)) return 0; + return; + case DBT_INT: + /* PostgreSQL's integer types fit int64 and arrive as exact decimal + * digits. Preserve that text; the N14 JSON reader produces ints. */ + strbuf_append(sb, text); + return; + case DBT_FLOAT: /* float8/float4 can be NaN / Infinity / -Infinity, which JSON has * no literal for. Emitting them as `null` would collide with SQL * NULL — the very confusion this issue removes — so they stay @@ -255,15 +236,14 @@ static int db_emit_json_value(strbuf *sb, unsigned int oid, int isnull, if (text[0] == 'N' || text[0] == 'I' || (text[0] == '-' && text[1] == 'I')) { eigs_json_escape_string(sb, text); - return 1; + return; } strbuf_append(sb, text); /* libpq's decimal text is valid JSON */ - return 1; + return; case DBT_STR: break; } eigs_json_escape_string(sb, text); - return 1; } /* ---- DB query builtins ---- */ @@ -293,17 +273,21 @@ Value* builtin_db_query_value(Value *arg) { unsigned int oid = (unsigned int)PQftype(res, 0); const char *text = PQgetvalue(res, 0, 0); - const char *colname = PQfname(res, 0); if (!text) text = ""; Value *result = NULL; switch (db_classify(oid)) { case DBT_BOOL: result = make_bool(text[0] == 't'); /* #1637 */ break; - case DBT_NUM: - if (!db_check_exact_int(oid, text, colname)) { PQclear(res); return make_str(""); } /* fs:STRICT db_check_exact_int raised EK_VALUE for a past-2^53 integer (ext_db.c:211) */ + case DBT_INT: + /* PostgreSQL validates its int2/int4/int8/oid text and range before + * sending it. Decode directly, including both int64 edges, rather + * than rounding through strtod (N14, Q-DB253). */ + result = make_int((int64_t)strtoll(text, NULL, 10)); + break; + case DBT_FLOAT: /* Same rule as the JSON emitter: float4/float8 can be NaN / - * Infinity / -Infinity, and make_num's num_guard would turn + * Infinity / -Infinity, and make_float's num_guard would turn * those into 0 and 1e308 — a silently wrong number. Hand back * the text so the caller sees what the column holds. */ if (text[0] == 'N' || text[0] == 'I' || @@ -311,7 +295,7 @@ Value* builtin_db_query_value(Value *arg) { result = make_str(text); break; } - result = make_num(strtod(text, NULL)); + result = make_float(strtod(text, NULL)); break; case DBT_STR: result = make_str(text); @@ -375,13 +359,8 @@ Value* builtin_db_query_json(Value *arg) { if (c > 0) strbuf_append(&sb, ", "); eigs_json_escape_string(&sb, PQfname(res, c)); strbuf_append(&sb, ": "); - if (!db_emit_json_value(&sb, (unsigned int)PQftype(res, c), - PQgetisnull(res, r, c), PQgetvalue(res, r, c), - PQfname(res, c))) { - strbuf_free(&sb); - PQclear(res); - return make_str("[]"); - } + db_emit_json_value(&sb, (unsigned int)PQftype(res, c), + PQgetisnull(res, r, c), PQgetvalue(res, r, c)); } strbuf_append_char(&sb, '}'); } diff --git a/src/ext_gfx.c b/src/ext_gfx.c index f97f4cc13..e5d1b54aa 100644 --- a/src/ext_gfx.c +++ b/src/ext_gfx.c @@ -110,6 +110,7 @@ static int g_ctype_locale_warned = 0; #endif static SDL_Window *g_window = NULL; static SDL_Renderer *g_renderer = NULL; +static int (*p_SDL_GetRendererOutputSize)(SDL_Renderer*, int*, int*); static int (*p_SDL_Init)(Uint32); static void (*p_SDL_Quit)(void); @@ -198,6 +199,7 @@ static int load_sdl2(void) { LOAD(SDL_GetTicks); LOAD(SDL_Delay); #undef LOAD /* Optional symbols — NULL is fine */ + p_SDL_GetRendererOutputSize = dlsym(g_sdl_lib, "SDL_GetRendererOutputSize"); p_SDL_GetModState = dlsym(g_sdl_lib, "SDL_GetModState"); p_SDL_GetMouseState = dlsym(g_sdl_lib, "SDL_GetMouseState"); p_SDL_RenderSetClipRect = dlsym(g_sdl_lib, "SDL_RenderSetClipRect"); @@ -374,6 +376,7 @@ static int g_ttf_font_count = 0; * existing fixed-width layouts don't overflow (scale 2 → 14 px). */ static int ttf_scale_to_px(int scale) { if (scale < 1) scale = 1; + if (scale > (INT_MAX - 2) / 6) return 0; return 6 * scale + 2; } @@ -429,6 +432,7 @@ static int ttf_available(void) { * (callers fall back to the bitmap path). */ static void* ttf_font_for_scale(int scale) { int px = ttf_scale_to_px(scale); + if (!px) return NULL; /* Q7: use bitmap geometry when the font API cannot hold the size. */ for (int i = 0; i < g_ttf_font_count; i++) if (g_ttf_fonts[i].px == px) return g_ttf_fonts[i].font; if (g_ttf_font_count >= TTF_FONT_CACHE_MAX) @@ -490,7 +494,7 @@ static const char* scancode_name(int sc) { /* ---- Builtins ---- */ -/* #1007: the drawing surface reads RUNS of list elements as `eigs_num_arg(&(), __func__)` +/* #1007: the drawing surface reads RUNS of list elements as `eigs_float_arg(&(), __func__)` * with no type check. `Value`'s union overlaps `double num` with * `char *str`, so an unchecked read reinterprets a pointer as a double * and then `(int)`-casts it — the read itself is the defect, which is why @@ -505,7 +509,7 @@ static int gfx_nums(Value *arg, int from, int to) { int cnt = arg->data.list.count; if (to > cnt) to = cnt; for (int i = from; i < to; i++) { - if (list_elem_type(arg, i) != VAL_NUM) return 0; + if ((list_elem_type(arg, i) != VAL_FLOAT && list_elem_type(arg, i) != VAL_INT)) return 0; } return 1; } @@ -518,7 +522,7 @@ static int gfx_nums(Value *arg, int from, int to) { static int gfx_list_all_num(Value *l) { if (!l || l->type != VAL_LIST) return 0; for (int i = 0; i < l->data.list.count; i++) { - if (list_elem_type(l, i) != VAL_NUM) return 0; + if ((list_elem_type(l, i) != VAL_FLOAT && list_elem_type(l, i) != VAL_INT)) return 0; } return 1; } @@ -548,16 +552,19 @@ Value* builtin_gfx_open(Value *arg) { * same value. */ ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 3, "gfx_open", "[number width, number height, title]", make_bool(0)); - /* #1007: width and height were read as `eigs_num_arg(&(), __func__)` with no type check + /* #1007: width and height were read as `eigs_float_arg(&(), __func__)` with no type check * while the title on the very next line WAS checked. Value's union * overlaps `double num` with `char *str`, so gfx_open of ["800","600",t] * reinterpreted a pointer as a double and int-cast it — in practice a * tiny denormal, so the window opened 0x0 and gfx_open answered 1. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), "gfx_open", "[number width, number height, title]", make_bool(0)); - int w = (int)eigs_list_num(arg, 0, __func__); - int h = (int)eigs_list_num(arg, 1, __func__); + int64_t width, height; + if (!eigs_list_magnitude(arg, 0, INT_MIN, INT_MAX, &width, "gfx_open") || + !eigs_list_magnitude(arg, 1, INT_MIN, INT_MAX, &height, "gfx_open")) + return make_null(); + int w = (int)width, h = (int)height; const char *title = list_elem_type(arg, 2) == VAL_STR ? list_get_ref(arg, 2)->data.str : "EigenScript"; gfx_select_title_locale(); @@ -613,6 +620,62 @@ Value* builtin_gfx_close(Value *arg) { return make_null(); /* fs:VOID gfx_close answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } + +/* Q7 helpers shared by the geometry builtins below. Output dimensions are + * renderer pixels (not window coordinates, which can differ on HiDPI). */ +static EigsRect gfx_drawable(void) { + EigsRect area = {0, 0, 0, 0}; + if (g_renderer && p_SDL_GetRendererOutputSize && + p_SDL_GetRendererOutputSize(g_renderer, &area.w, &area.h) != 0) + area.w = area.h = 0; + return area; +} + +static int gfx_colour(const Value *arg, int first, int alpha, int colour[4], + const char *who) { + colour[3] = 255; + for (int i = 0; i < 3; ++i) + if (!eigs_list_bounded(arg, first+i, 0, 255, &colour[i], who)) return 0; + return !alpha || arg->data.list.count <= first+3 || + eigs_list_bounded(arg, first+3, 0, 255, &colour[3], who); +} + +/* Derived edges may overflow outward when long double is binary64. + * Intersect edges separately, before subtracting to form a width: a huge + * circle can have left=-inf/right=+inf without ever forming -inf+inf. + * The stack list borrows only immediate float slots, and never escapes. */ +static int gfx_edges_ld(long double x, long double y, long double right, long double bottom, + const EigsRect *area, EigsShape *shape, const char *who) { + memset(shape, 0, sizeof *shape); + shape->empty = 1; + if (area->w <= 0 || area->h <= 0) return 1; + right = fminl((long double)area->x + area->w, right); + bottom = fminl((long double)area->y + area->h, bottom); + x = fmaxl(area->x, x); y = fmaxl(area->y, y); + if (x >= right || y >= bottom) return 1; + EigsSlot slots[4] = { slot_from_f64((double)x), slot_from_f64((double)y), + slot_from_f64((double)(right-x)), slot_from_f64((double)(bottom-y)) }; + Value args = { .type = VAL_LIST, .data.list = { .items = slots, .count = 4, .capacity = 4 } }; + return eigs_geom_shape(&args, 0, EIGS_RECT, area, shape, who); +} + +/* Callers have finite offsets or offsets that overflow toward +inf, and + * positive extents. In particular, framebuffer w*scale may be +inf while + * its finite origin still clips safely through the complete rectangle. */ +static int gfx_rect_ld(long double x, long double y, long double w, long double h, + const EigsRect *area, EigsShape *shape, const char *who) { + if (w <= 0 || h <= 0) { + memset(shape, 0, sizeof *shape); shape->empty = 1; return 1; + } + return gfx_edges_ld(x, y, x+w, y+h, area, shape, who); +} + +static void gfx_fill_shape(const EigsShape *shape) { + if (shape->empty) return; + SDL_Rect rect = {shape->x, shape->y, shape->w, shape->h}; + p_SDL_RenderFillRect(g_renderer, &rect); +} + /* gfx_clear of [r, g, b] */ Value* builtin_gfx_clear(Value *arg) { STRICT_LIST_MAX(arg, 3, "gfx_clear"); @@ -633,16 +696,12 @@ Value* builtin_gfx_clear(Value *arg) { * path of this builtin answers. */ ARG_GUARD(shaped && !gfx_nums(arg, 0, 3), "gfx_clear", "[number r, number g, number b]", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_clear answers null on every path -- this is the return value, not a stand-in for a rejected argument */ - int r = 0, g = 0, b = 0; - if (shaped) { - r = (int)eigs_list_num(arg, 0, __func__); - g = (int)eigs_list_num(arg, 1, __func__); - b = (int)eigs_list_num(arg, 2, __func__); - } - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); + int colour[4] = {0, 0, 0, 255}; + if (shaped && !gfx_colour(arg, 0, 0, colour, __func__)) return make_null(); + if (!g_renderer) return make_null(); /* fs:VOID no renderer; null is the result on every path. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], 255); p_SDL_RenderClear(g_renderer); - return make_null(); /* fs:VOID gfx_clear answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_rect of [x, y, w, h, r, g, b] or [x, y, w, h, r, g, b, a] */ @@ -653,19 +712,14 @@ Value* builtin_gfx_rect(Value *arg) { "gfx_rect", "[number x, number y, number w, number h, number r, number g, number b] and an optional number alpha", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_rect answers null on every path -- the return value, not a rejected-argument stand-in */ - SDL_Rect rect; - rect.x = (int)eigs_list_num(arg, 0, __func__); - rect.y = (int)eigs_list_num(arg, 1, __func__); - rect.w = (int)eigs_list_num(arg, 2, __func__); - rect.h = (int)eigs_list_num(arg, 3, __func__); - int r = (int)eigs_list_num(arg, 4, __func__); - int g = (int)eigs_list_num(arg, 5, __func__); - int b = (int)eigs_list_num(arg, 6, __func__); - int a = (arg->data.list.count >= 8) ? (int)eigs_list_num(arg, 7, __func__) : 255; - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, a); - p_SDL_RenderFillRect(g_renderer, &rect); - return make_null(); /* fs:VOID gfx_rect answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + int colour[4]; + if (!gfx_colour(arg, 4, 1, colour, __func__)) return make_null(); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_RECT, &area, &shape, __func__)) return make_null(); + if (!g_renderer || shape.empty) return make_null(); /* fs:EMPTY empty visible intersection draws nothing. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], colour[3]); + gfx_fill_shape(&shape); + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_line of [x1, y1, x2, y2, r, g, b] */ @@ -676,17 +730,14 @@ Value* builtin_gfx_line(Value *arg) { "gfx_line", "[number x1, number y1, number x2, number y2, number r, number g, number b]", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_line answers null on every path -- the return value, not a rejected-argument stand-in */ - int x1 = (int)eigs_list_num(arg, 0, __func__); - int y1 = (int)eigs_list_num(arg, 1, __func__); - int x2 = (int)eigs_list_num(arg, 2, __func__); - int y2 = (int)eigs_list_num(arg, 3, __func__); - int r = (int)eigs_list_num(arg, 4, __func__); - int g = (int)eigs_list_num(arg, 5, __func__); - int b = (int)eigs_list_num(arg, 6, __func__); - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); - p_SDL_RenderDrawLine(g_renderer, x1, y1, x2, y2); - return make_null(); /* fs:VOID gfx_line answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + int colour[4]; + if (!gfx_colour(arg, 4, 0, colour, __func__)) return make_null(); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_LINE, &area, &shape, __func__)) return make_null(); + if (!g_renderer || shape.empty) return make_null(); /* fs:EMPTY no visible segment. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], 255); + p_SDL_RenderDrawLine(g_renderer, shape.x, shape.y, shape.x2, shape.y2); + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_point of [x, y, r, g, b] */ @@ -696,15 +747,14 @@ Value* builtin_gfx_point(Value *arg) { || !gfx_nums(arg, 0, 5), "gfx_point", "[number x, number y, number r, number g, number b]", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_point answers null on every path -- the return value, not a rejected-argument stand-in */ - int x = (int)eigs_list_num(arg, 0, __func__); - int y = (int)eigs_list_num(arg, 1, __func__); - int r = (int)eigs_list_num(arg, 2, __func__); - int g = (int)eigs_list_num(arg, 3, __func__); - int b = (int)eigs_list_num(arg, 4, __func__); - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); - p_SDL_RenderDrawPoint(g_renderer, x, y); - return make_null(); /* fs:VOID gfx_point answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + int colour[4]; + if (!gfx_colour(arg, 2, 0, colour, __func__)) return make_null(); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_POINT, &area, &shape, __func__)) return make_null(); + if (!g_renderer || shape.empty) return make_null(); /* fs:EMPTY an outside point draws nothing. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], 255); + p_SDL_RenderDrawPoint(g_renderer, shape.x, shape.y); + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_circle of [cx, cy, radius, r, g, b] — filled circle via midpoint */ @@ -715,22 +765,28 @@ Value* builtin_gfx_circle(Value *arg) { "gfx_circle", "[number cx, number cy, number radius, number r, number g, number b] and an optional number alpha", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_circle answers null on every path -- the return value, not a rejected-argument stand-in */ - int cx = (int)eigs_list_num(arg, 0, __func__); - int cy = (int)eigs_list_num(arg, 1, __func__); - int radius = (int)eigs_list_num(arg, 2, __func__); - int r = (int)eigs_list_num(arg, 3, __func__); - int g = (int)eigs_list_num(arg, 4, __func__); - int b = (int)eigs_list_num(arg, 5, __func__); - int a = (arg->data.list.count >= 7) ? (int)eigs_list_num(arg, 6, __func__) : 255; - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, a); - /* Filled circle via horizontal lines */ - for (int dy = -radius; dy <= radius; dy++) { - int dx = (int)sqrt((double)(radius * radius - dy * dy)); - SDL_Rect row = { cx - dx, cy + dy, dx * 2 + 1, 1 }; - p_SDL_RenderFillRect(g_renderer, &row); - } - return make_null(); /* fs:VOID gfx_circle answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + int colour[4]; + if (!gfx_colour(arg, 3, 1, colour, __func__)) return make_null(); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_CIRCLE, &area, &shape, __func__)) return make_null(); + if (!g_renderer || shape.empty) return make_null(); /* fs:EMPTY no visible bounding box. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], colour[3]); + long double cx = shape.raw[0], cy = shape.raw[1], radius = shape.raw[2]; + for (int row = shape.y; row < shape.y + shape.h; ++row) { + /* Keep the visible row term after subtracting a far-away centre: + * cy==r==1e308 still has a wide chord at y=1, not a zero chord. */ + long double near = cy >= row ? (radius-cy)+row : (radius+cy)-row; + if (near < 0) continue; + long double dy = fabsl((long double)row-cy); + /* The chord is <=radius, even when r+dy or r*r overflows. + * Keep near's cancellation-safe value and factor the square root. */ + long double dx = radius == 0 ? 0 : + fminl(radius, sqrtl(near)*sqrtl(radius)*sqrtl(1+fminl(1,dy/radius))); + EigsShape span; + if (!gfx_edges_ld(cx-dx, row, cx+dx+1, row+1, &area, &span, __func__)) return make_null(); + gfx_fill_shape(&span); + } + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_rrect of [x, y, w, h, radius, r, g, b] or [..., a] @@ -742,41 +798,25 @@ Value* builtin_gfx_rrect(Value *arg) { "gfx_rrect", "[number x, number y, number w, number h, number radius, number r, number g, number b] and an optional number alpha", make_null()); - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_rrect answers null on every path -- the return value, not a rejected-argument stand-in */ - int x = (int)eigs_list_num(arg, 0, __func__); - int y = (int)eigs_list_num(arg, 1, __func__); - int w = (int)eigs_list_num(arg, 2, __func__); - int h = (int)eigs_list_num(arg, 3, __func__); - int rad = (int)eigs_list_num(arg, 4, __func__); - int r = (int)eigs_list_num(arg, 5, __func__); - int g = (int)eigs_list_num(arg, 6, __func__); - int b = (int)eigs_list_num(arg, 7, __func__); - int a = (arg->data.list.count >= 9) ? (int)eigs_list_num(arg, 8, __func__) : 255; - if (w <= 0 || h <= 0) return make_null(); /* fs:EMPTY a rectangle with no width or height covers no pixels, so drawing nothing IS the answer -- the degenerate-geometry identity, not a laundered argument (lib/ui layout produces zero-size rects routinely) */ - /* Clamp radius to half the smaller dimension */ - if (rad > w / 2) rad = w / 2; - if (rad > h / 2) rad = h / 2; - if (rad < 0) rad = 0; - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, a); - if (rad == 0) { - SDL_Rect rect = { x, y, w, h }; - p_SDL_RenderFillRect(g_renderer, &rect); - return make_null(); /* fs:VOID radius 0 took the plain-rect path and drew it; gfx_rrect answers null on every path -- this is the return value, not a stand-in for a rejected argument */ - } - /* Center body (between top and bottom rounded bands) */ - SDL_Rect center = { x, y + rad, w, h - 2 * rad }; - p_SDL_RenderFillRect(g_renderer, ¢er); - /* Top and bottom bands with rounded corners via scanlines */ - for (int dy = 0; dy < rad; dy++) { - int dx = (int)sqrt((double)(rad * rad - (rad - dy) * (rad - dy))); - /* Top band */ - SDL_Rect top_row = { x + rad - dx, y + dy, w - 2 * (rad - dx), 1 }; - p_SDL_RenderFillRect(g_renderer, &top_row); - /* Bottom band */ - SDL_Rect bot_row = { x + rad - dx, y + h - 1 - dy, w - 2 * (rad - dx), 1 }; - p_SDL_RenderFillRect(g_renderer, &bot_row); - } - return make_null(); /* fs:VOID gfx_rrect answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + int colour[4]; + if (!gfx_colour(arg, 5, 1, colour, __func__)) return make_null(); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_RRECT, &area, &shape, __func__)) return make_null(); + if (!g_renderer || shape.empty) return make_null(); /* fs:EMPTY no visible rounded rectangle. */ + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], colour[3]); + long double x = shape.raw[0], y = shape.raw[1]; + long double right = x+shape.raw[2], bottom = y+shape.raw[3]; + long double rad = fminl(fmaxl(0, shape.raw[4]), fminl(shape.raw[2]/2.0L, shape.raw[3]/2.0L)); + for (int row = shape.y; row < shape.y + shape.h; ++row) { + long double depth = row < y+rad ? row-y : row >= bottom-rad ? (bottom-1)-row : rad; + depth = fmaxl(0, fminl(rad, depth)); + long double chord = fminl(rad, sqrtl(depth)*sqrtl(2*rad-depth)); + long double inset = rad-chord; + EigsShape span; + if (!gfx_edges_ld(x+inset, row, right-inset, row+1, &area, &span, __func__)) return make_null(); + gfx_fill_shape(&span); + } + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_clip of [x, y, w, h] — set render clip rectangle. @@ -791,18 +831,15 @@ Value* builtin_gfx_clip(Value *arg) { || !gfx_nums(arg, 0, 4)), "gfx_clip", "[number x, number y, number w, number h] or null", make_null()); - if (!g_renderer || !p_SDL_RenderSetClipRect) return make_null(); /* fs:VOID no window / no SDL symbol: gfx_clip answers null on every path -- the return value, not a rejected-argument stand-in */ - if (clearing) { - p_SDL_RenderSetClipRect(g_renderer, NULL); - return make_null(); /* fs:VOID the clip was cleared -- gfx_clip's normal successful answer */ + EigsRect area = gfx_drawable(); EigsShape shape; + if (!clearing && !eigs_geom_shape(arg, 0, EIGS_RECT, &area, &shape, __func__)) return make_null(); + if (!g_renderer || !p_SDL_RenderSetClipRect) return make_null(); /* fs:VOID no renderer or optional clip API. */ + if (clearing) p_SDL_RenderSetClipRect(g_renderer, NULL); + else { + SDL_Rect clip = {shape.x, shape.y, shape.empty ? 0 : shape.w, shape.empty ? 0 : shape.h}; + p_SDL_RenderSetClipRect(g_renderer, &clip); } - SDL_Rect clip; - clip.x = (int)eigs_list_num(arg, 0, __func__); - clip.y = (int)eigs_list_num(arg, 1, __func__); - clip.w = (int)eigs_list_num(arg, 2, __func__); - clip.h = (int)eigs_list_num(arg, 3, __func__); - p_SDL_RenderSetClipRect(g_renderer, &clip); - return make_null(); /* fs:VOID gfx_clip answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_read of [x, y] — read back one rendered pixel as [r, g, b]. @@ -825,22 +862,25 @@ Value* builtin_gfx_read(Value *arg) { ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2 || !gfx_nums(arg, 0, 2), "gfx_read", "[number x, number y]", make_null()); + /* Deterministic refusals stay above TAKE, including nonfinite numbers. */ + (void)eigs_list_float(arg, 0, __func__); + if (g_has_error) return make_null(); + (void)eigs_list_float(arg, 1, __func__); + if (g_has_error) return make_null(); TRACE_NONDET_TAKE("gfx_read"); if (!g_renderer || !p_SDL_RenderReadPixels) TRACE_NONDET_RECORD("gfx_read", make_null()); - SDL_Rect r; - r.x = (int)eigs_list_num(arg, 0, __func__); - r.y = (int)eigs_list_num(arg, 1, __func__); - r.w = 1; - r.h = 1; + EigsRect area = gfx_drawable(); EigsShape shape; + if (!eigs_geom_shape(arg, 0, EIGS_POINT, &area, &shape, __func__)) return make_null(); + if (shape.empty) TRACE_NONDET_RECORD("gfx_read", make_null()); + SDL_Rect rect = {shape.x, shape.y, 1, 1}; Uint32 px = 0; - if (p_SDL_RenderReadPixels(g_renderer, &r, MY_SDL_PIXELFORMAT_ARGB8888, - &px, (int)sizeof(Uint32)) != 0) + if (p_SDL_RenderReadPixels(g_renderer, &rect, MY_SDL_PIXELFORMAT_ARGB8888, &px, sizeof px) != 0) TRACE_NONDET_RECORD("gfx_read", make_null()); Value *out = make_list(3); - list_append_owned(out, make_num((double)((px >> 16) & 0xFF))); - list_append_owned(out, make_num((double)((px >> 8) & 0xFF))); - list_append_owned(out, make_num((double)(px & 0xFF))); + list_append_owned(out, make_int((px >> 16) & 0xFF)); + list_append_owned(out, make_int((px >> 8) & 0xFF)); + list_append_owned(out, make_int(px & 0xFF)); TRACE_NONDET_RECORD("gfx_read", out); } @@ -883,39 +923,39 @@ Value* builtin_gfx_poll(Value *arg) { case MY_SDL_KEYDOWN: dict_set_owned(d, "type", make_str("keydown")); dict_set_owned(d, "key", make_str(scancode_name(ev.key.keysym.scancode))); - dict_set_owned(d, "scancode", make_num(ev.key.keysym.scancode)); + dict_set_owned(d, "scancode", make_int(ev.key.keysym.scancode)); poll_set_mods(d, ev.key.keysym.mod); break; case MY_SDL_KEYUP: dict_set_owned(d, "type", make_str("keyup")); dict_set_owned(d, "key", make_str(scancode_name(ev.key.keysym.scancode))); - dict_set_owned(d, "scancode", make_num(ev.key.keysym.scancode)); + dict_set_owned(d, "scancode", make_int(ev.key.keysym.scancode)); poll_set_mods(d, ev.key.keysym.mod); break; case MY_SDL_MOUSEMOTION: dict_set_owned(d, "type", make_str("mousemove")); - dict_set_owned(d, "x", make_num(ev.motion.x)); - dict_set_owned(d, "y", make_num(ev.motion.y)); + dict_set_owned(d, "x", make_int(ev.motion.x)); + dict_set_owned(d, "y", make_int(ev.motion.y)); poll_set_mods(d, poll_mod_state()); break; case MY_SDL_MOUSEBUTTONDOWN: dict_set_owned(d, "type", make_str("mousedown")); - dict_set_owned(d, "button", make_num(ev.button.button)); - dict_set_owned(d, "x", make_num(ev.button.x)); - dict_set_owned(d, "y", make_num(ev.button.y)); + dict_set_owned(d, "button", make_int(ev.button.button)); + dict_set_owned(d, "x", make_int(ev.button.x)); + dict_set_owned(d, "y", make_int(ev.button.y)); poll_set_mods(d, poll_mod_state()); break; case MY_SDL_MOUSEBUTTONUP: dict_set_owned(d, "type", make_str("mouseup")); - dict_set_owned(d, "button", make_num(ev.button.button)); - dict_set_owned(d, "x", make_num(ev.button.x)); - dict_set_owned(d, "y", make_num(ev.button.y)); + dict_set_owned(d, "button", make_int(ev.button.button)); + dict_set_owned(d, "x", make_int(ev.button.x)); + dict_set_owned(d, "y", make_int(ev.button.y)); poll_set_mods(d, poll_mod_state()); break; case MY_SDL_MOUSEWHEEL: dict_set_owned(d, "type", make_str("wheel")); - dict_set_owned(d, "x", make_num(ev.wheel.x)); - dict_set_owned(d, "y", make_num(ev.wheel.y)); + dict_set_owned(d, "x", make_int(ev.wheel.x)); + dict_set_owned(d, "y", make_int(ev.wheel.y)); /* #822: x/y are scroll DELTAS. Carry the pointer as mx/my so * a consumer can zoom about the cursor without shadowing * mousemove (stale before the first motion event). Queried @@ -925,8 +965,8 @@ Value* builtin_gfx_poll(Value *arg) { if (p_SDL_GetMouseState) { int wmx = 0, wmy = 0; p_SDL_GetMouseState(&wmx, &wmy); - dict_set_owned(d, "mx", make_num(wmx)); - dict_set_owned(d, "my", make_num(wmy)); + dict_set_owned(d, "mx", make_int(wmx)); + dict_set_owned(d, "my", make_int(wmy)); } poll_set_mods(d, poll_mod_state()); break; @@ -934,8 +974,8 @@ Value* builtin_gfx_poll(Value *arg) { /* SDL_WINDOWEVENT_SIZE_CHANGED = 6 (RESIZED is 5). */ if (ev.window.event == 6) { dict_set_owned(d, "type", make_str("resize")); - dict_set_owned(d, "w", make_num(ev.window.data1)); - dict_set_owned(d, "h", make_num(ev.window.data2)); + dict_set_owned(d, "w", make_int(ev.window.data1)); + dict_set_owned(d, "h", make_int(ev.window.data2)); } else { /* #1007: `d` is already allocated. Returning without * releasing it leaked 584 bytes / 6 allocations per @@ -957,15 +997,17 @@ Value* builtin_gfx_ticks(Value *arg) { (void)arg; TRACE_NONDET_TAKE("gfx_ticks"); TRACE_NONDET_RECORD("gfx_ticks", - make_num(g_sdl_lib ? (double)p_SDL_GetTicks() : 0.0)); + make_int(g_sdl_lib ? (int64_t)p_SDL_GetTicks() : 0)); } /* gfx_delay of ms */ Value* builtin_gfx_delay(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_NUM, "gfx_delay", "number milliseconds", + ARG_GUARD(!arg || !val_is_number(arg), "gfx_delay", "number milliseconds", make_null()); - Uint32 ms = (Uint32)eigs_num_arg(arg, __func__); - if (g_sdl_lib) p_SDL_Delay(ms); + int64_t milliseconds; + if (!eigs_magnitude_arg(arg, 0, UINT32_MAX, &milliseconds, "gfx_delay")) + return make_null(); + if (g_sdl_lib) p_SDL_Delay((Uint32)milliseconds); return make_null(); /* fs:VOID gfx_delay answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } @@ -1092,11 +1134,11 @@ Value* builtin_gfx_text(Value *arg) { * * THE OPTIONAL SCALE (slot 6) IS IN THIS GUARD ON PURPOSE, and that is * why gfx_text and gfx_text_width answer a wrong-typed scale - * DIFFERENTLY. gfx_text_width's slot carried `items[1]->type == VAL_NUM` + * DIFFERENTLY. gfx_text_width's slot carried `val_is_number(items[1])` * before #1007, so its wrong-typed scale is a COERCION and keeps * measuring at scale 1 (STRICT_REQUIRE there, byte-identical off the * flag). This one did not: - * int scale = (count >= 7) ? (int)eigs_num_arg(items[6], __func__) : 1; + * int scale = (count >= 7) ? (int)eigs_float_arg(items[6], __func__) : 1; * reads the union unchecked, so a string scale drew the glyph from a * reinterpreted `char *` — a subnormal that truncates to 0 and is then * clamped to 1, which is why it LOOKED like scale 1 while being a pun. @@ -1112,66 +1154,73 @@ Value* builtin_gfx_text(Value *arg) { "gfx_text", "[number x, number y, string text, number r, number g, number b] and an optional number scale", make_null()); + double x = eigs_list_float(arg, 0, __func__); + if (g_has_error) return make_null(); + double y = eigs_list_float(arg, 1, __func__); + if (g_has_error) return make_null(); + double scale = arg->data.list.count >= 7 ? eigs_list_float(arg, 6, __func__) : 1; + if (g_has_error) return make_null(); + if (scale < 1) scale = 1; + int colour[4]; + if (!gfx_colour(arg, 3, 0, colour, __func__)) return make_null(); + const char *text = list_get_ref(arg, 2)->data.str; if (__builtin_expect(g_replay_enabled, 0)) { - rt_error(EK_IO, 0, "gfx_text: not replayable under " - "EIGS_REPLAY (file-read boundary; see docs/TRACE.md)"); + rt_error(EK_IO, 0, "gfx_text: not replayable under EIGS_REPLAY (file-read boundary; see docs/TRACE.md)"); return make_null(); } - if (!g_renderer) return make_null(); /* fs:VOID no window open: gfx_text answers null on every path -- the return value, not a rejected-argument stand-in */ - int x = (int)eigs_list_num(arg, 0, __func__); - int y = (int)eigs_list_num(arg, 1, __func__); - const char *text = list_get_ref(arg, 2)->data.str; - int r = (int)eigs_list_num(arg, 3, __func__); - int g = (int)eigs_list_num(arg, 4, __func__); - int b = (int)eigs_list_num(arg, 5, __func__); - int scale = (arg->data.list.count >= 7) ? (int)eigs_list_num(arg, 6, __func__) : 1; - if (scale < 1) scale = 1; - - if (*text && ttf_available() && p_SDL_CreateTextureFromSurface - && p_SDL_FreeSurface && p_SDL_QueryTexture && p_SDL_DestroyTexture - && p_SDL_RenderCopy) { - void *font = ttf_font_for_scale(scale); + if (!g_renderer) return make_null(); /* fs:VOID no renderer. */ + EigsRect area = gfx_drawable(); + /* The integer TTF size API cannot express a fractional or enormous + * bitmap cell. Retain continuous geometry in the bitmap fallback. */ + if (*text && scale <= (INT_MAX-2)/6 && scale == floor(scale) && + ttf_available() && p_SDL_CreateTextureFromSurface && p_SDL_FreeSurface && + p_SDL_QueryTexture && p_SDL_DestroyTexture && p_SDL_RenderCopy) { + void *font = ttf_font_for_scale((int)scale); if (font) { - SDL_Color col = { (Uint8)r, (Uint8)g, (Uint8)b, 255 }; + SDL_Color col = {(Uint8)colour[0], (Uint8)colour[1], (Uint8)colour[2], 255}; void *surf = p_TTF_RenderUTF8_Blended(font, text, col); if (surf) { SDL_Texture *tex = p_SDL_CreateTextureFromSurface(g_renderer, surf); p_SDL_FreeSurface(surf); if (tex) { int tw = 0, th = 0; - p_SDL_QueryTexture(tex, NULL, NULL, &tw, &th); - SDL_Rect dst = { x, y, tw, th }; - p_SDL_RenderCopy(g_renderer, tex, NULL, &dst); + if (p_SDL_QueryTexture(tex, NULL, NULL, &tw, &th) == 0 && tw > 0 && th > 0) { + EigsShape shape; + if (!gfx_rect_ld(x, y, tw, th, &area, &shape, __func__)) { + p_SDL_DestroyTexture(tex); return make_null(); + } + if (!shape.empty) { + SDL_Rect dst = {shape.x, shape.y, shape.w, shape.h}; + SDL_Rect src = {(int)((long double)shape.x-roundl(x)), + (int)((long double)shape.y-roundl(y)), shape.w, shape.h}; + p_SDL_RenderCopy(g_renderer, tex, &src, &dst); + } + p_SDL_DestroyTexture(tex); + return make_null(); /* fs:VOID TTF rendering completed, including an empty intersection. */ + } p_SDL_DestroyTexture(tex); - return make_null(); /* fs:VOID the TTF path rendered the string; gfx_text answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } } } - /* fall through to the bitmap path on any failure */ } - - p_SDL_SetRenderDrawColor(g_renderer, r, g, b, 255); - int cx = x; - for (const char *p = text; *p; p++) { + p_SDL_SetRenderDrawColor(g_renderer, colour[0], colour[1], colour[2], 255); + long double cx = x; + for (const char *p = text; *p; ++p) { int ch = (unsigned char)*p; if (ch < 32 || ch > 126) ch = '?'; - const unsigned char *glyph = font5x7[ch - 32]; - for (int row = 0; row < 7; row++) { - unsigned char bits = glyph[row]; - for (int col = 0; col < 5; col++) { - if (bits & (0x10 >> col)) { - if (scale == 1) { - p_SDL_RenderDrawPoint(g_renderer, cx + col, y + row); - } else { - SDL_Rect pr = { cx + col * scale, y + row * scale, scale, scale }; - p_SDL_RenderFillRect(g_renderer, &pr); - } + const unsigned char *glyph = font5x7[ch-32]; + for (int row = 0; row < 7; ++row) + for (int col = 0; col < 5; ++col) + if (glyph[row] & (0x10 >> col)) { + EigsShape cell; + if (!gfx_rect_ld(cx+(long double)col*scale, (long double)y+(long double)row*scale, + scale, scale, &area, &cell, __func__)) return make_null(); + gfx_fill_shape(&cell); } - } - } - cx += (5 + 1) * scale; /* 5 pixel width + 1 pixel gap */ + cx += 6*(long double)scale; + if (cx >= (long double)area.x + area.w) break; } - return make_null(); /* fs:VOID gfx_text answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + return make_null(); /* fs:VOID graphics commands return null. */ } /* gfx_text_width of [text, scale?] (or of "text") — pixel width of `text` @@ -1190,16 +1239,19 @@ Value* builtin_gfx_text_width(Value *arg) { && list_elem_type(arg, 0) == VAL_STR) { text = list_get_ref(arg, 0)->data.str; if (arg->data.list.count >= 2) { - if (list_elem_type(arg, 1) == VAL_NUM) - scale = (int)eigs_list_num(arg, 1, __func__); - else + if ((list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT)) { + int64_t rounded; + if (!eigs_list_magnitude(arg, 1, INT_MIN, INT_MAX, &rounded, + "gfx_text_width")) return make_null(); + scale = (int)rounded; + } else bad_scale = 1; } } /* #1007: converted out of its #971 deferral marker. The 0 stays the non-strict * stand-in — tests/test_gfx_text.eigs pins `gfx_text_width of 5` at 0. */ ARG_GUARD(!text, "gfx_text_width", - "string text or [string text, number scale]", make_num(0)); + "string text or [string text, number scale]", make_int(0)); /* A wrong-typed SCALE is the COERCION shape, not a stand-in one: the * width for scale 1 is still computed and returned. STRICT_REQUIRE * leaves the default path byte-identical by construction. */ @@ -1207,16 +1259,21 @@ Value* builtin_gfx_text_width(Value *arg) { if (__builtin_expect(g_replay_enabled, 0)) { rt_error(EK_IO, 0, "gfx_text_width: not replayable under EIGS_REPLAY " "(font file-read boundary; see docs/TRACE.md)"); - return make_num(0); + return make_int(0); } if (scale < 1) scale = 1; if (*text && ttf_available()) { void *font = ttf_font_for_scale(scale); int w = 0, h = 0; if (font && p_TTF_SizeUTF8(font, text, &w, &h) == 0) - return make_num((double)w); + return make_int(w); + } + size_t length = strlen(text); + if (length > (uint64_t)INT64_MAX / 6 / (uint64_t)scale) { + rt_error(EK_VALUE, 0, "gfx_text_width: pixel width does not fit an int64"); + return make_null(); } - return make_num((double)strlen(text) * 6.0 * (double)scale); + return make_int((int64_t)length * 6 * scale); } /* gfx_text_height of scale? (number, [scale], or null → 1) — pixel line @@ -1229,27 +1286,33 @@ Value* builtin_gfx_text_height(Value *arg) { * 1 and the caller was told the height it did not ask for. There is no * stand-in to name (the height for scale 1 is still returned), so this * raises under strict and does nothing otherwise. */ - STRICT_REQUIRE(arg && arg->type != VAL_NULL && arg->type != VAL_NUM + STRICT_REQUIRE(arg && arg->type != VAL_NULL && !val_is_number(arg) && !(arg->type == VAL_LIST && arg->data.list.count >= 1 - && list_elem_type(arg, 0) == VAL_NUM), + && (list_elem_type(arg, 0) == VAL_FLOAT || list_elem_type(arg, 0) == VAL_INT)), "gfx_text_height", "number scale, [number scale] or null"); + if (arg && val_is_number(arg)) { + int64_t rounded; + if (!eigs_magnitude_arg(arg, INT_MIN, INT_MAX, &rounded, "gfx_text_height")) + return make_null(); + scale = (int)rounded; + } else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 1 + && (list_elem_type(arg, 0) == VAL_FLOAT || list_elem_type(arg, 0) == VAL_INT)) { + int64_t rounded; + if (!eigs_list_magnitude(arg, 0, INT_MIN, INT_MAX, &rounded, "gfx_text_height")) + return make_null(); + scale = (int)rounded; + } if (__builtin_expect(g_replay_enabled, 0)) { rt_error(EK_IO, 0, "gfx_text_height: not replayable under EIGS_REPLAY " "(font file-read boundary; see docs/TRACE.md)"); - return make_num(0); - } - if (arg && arg->type == VAL_NUM) { - scale = (int)eigs_num_arg(arg, __func__); - } else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 1 - && list_elem_type(arg, 0) == VAL_NUM) { - scale = (int)eigs_list_num(arg, 0, __func__); + return make_int(0); } if (scale < 1) scale = 1; if (ttf_available()) { void *font = ttf_font_for_scale(scale); - if (font) return make_num((double)p_TTF_FontHeight(font)); + if (font) return make_int(p_TTF_FontHeight(font)); } - return make_num(7.0 * (double)scale); + return make_int(INT64_C(7) * scale); } /* ---- Audio Builtins ---- */ @@ -1304,60 +1367,38 @@ static void audio_mix_callback(void *ud, uint8_t *stream, int len) { /* Convert samples (floats -1..1) to an owned int16 buffer. Accepts a * VAL_LIST or a VAL_BUFFER (#578 — the buffer is the type the language * recommends for bulk samples and what a DAW render produces; forcing a - * buffer→list conversion per play meant millions of appends + VAL_NUM + * buffer→list conversion per play meant millions of appends + VAL_FLOAT * allocations just to feed the mixer). Returns NULL on bad shape or an * over-64MB clip. */ static int16_t* audio_convert_samples(Value *samples, int *out_n) { if (!samples) return NULL; - if (samples->type == VAL_BUFFER) { - int n = samples->data.buffer.count; - if (n <= 0 || (double)n * sizeof(int16_t) > 64.0 * 1024.0 * 1024.0) return NULL; - int16_t *buf = xmalloc_array(n, sizeof(int16_t)); - for (int i = 0; i < n; i++) { - double s = buffer_read_num(samples, i); - if (g_has_error) { free(buf); return NULL; } - if (s > 1.0) s = 1.0; - if (s < -1.0) s = -1.0; - buf[i] = (int16_t)(s * 32767); - } - *out_n = n; - return buf; - } - if (samples->type != VAL_LIST) return NULL; - int n = samples->data.list.count; - if (n <= 0 || (double)n * sizeof(int16_t) > 64.0 * 1024.0 * 1024.0) return NULL; - int16_t *buf = xmalloc_array(n, sizeof(int16_t)); - for (int i = 0; i < n; i++) { - EigsSlot sample = list_slot(samples, i); - /* #1007: a non-number used to be COERCED to 0 here, so a wrong-typed - * sample list produced a valid buffer of silence and the caller's - * `if (!buf)` guard never saw it — audio_play answered with a real - * channel id and played nothing. Coercion is the one shape that - * cannot be caught downstream, so it is rejected at the element. */ - if (slot_type(sample) != VAL_NUM) { - /* Raise only under strict, exactly as ARG_GUARD does — this macro - * cannot be used here because the function answers int16_t*, not - * Value*. An unconditional raise turned a call that used to answer - * a channel id into a hard error in the DEFAULT build, which is - * the one thing the #971 reform promises not to do. - * Rejecting (rather than coercing) is unconditional, because the - * coerced result was the defect: the caller then answers 0, which - * is what BUILTINS.md already documents for audio_play ("0 on bad - * args") and what the old code could not produce. */ - if (g_strict) { - rt_error(EK_TYPE, 0, "audio samples: element %d is %s, expected a number\n", - i, val_type_name(slot_type(sample))); + int n=samples->type==VAL_BUFFER ? samples->data.buffer.count : + samples->type==VAL_LIST ? samples->data.list.count : 0; + if (n<=0 || (size_t)n>64u*1024u*1024u/sizeof(int16_t)) return NULL; + int16_t *buf=xmalloc_array(n,sizeof *buf); + for (int i=0;itype==VAL_BUFFER) { + /* R1.6d-D1: stored data crosses the final finite boundary once. */ + s=buffer_read_f64(samples,i); + } else { + EigsSlot sample=list_slot(samples,i); + if ((slot_type(sample)!=VAL_FLOAT && slot_type(sample)!=VAL_INT)) { + if (g_strict) rt_error(EK_TYPE,0,"audio samples: element %d is %s, expected a number\n",i,val_type_name(slot_type(sample))); + free(buf);return NULL; } - free(buf); - return NULL; + s=eigs_slot_float(sample,__func__); } - double s = eigs_slot_num(sample, __func__); - if (s > 1.0) s = 1.0; - if (s < -1.0) s = -1.0; - buf[i] = (int16_t)(s * 32767); + if (g_has_error) { free(buf);return NULL; } + /* Clamp before multiplying so even a finite 1e308 sample cannot + * overflow its conversion. Q7/Q4 then round the PCM integer. */ + if (s>1) s=1; + if (s< -1) s= -1; + int pcm; + if (!eigs_slot_bounded(slot_from_f64(s*32767),-32767,32767,&pcm,__func__)) { free(buf);return NULL; } + buf[i]=(int16_t)pcm; } - *out_n = n; - return buf; + *out_n=n;return buf; } /* Install a clip on a free (else oldest-finite, else oldest) channel. @@ -1435,31 +1476,34 @@ Value* builtin_audio_open(Value *arg) { && !(arg->type == VAL_LIST && arg->data.list.count >= 2), "audio_open", "[number freq, number channels] or null"); if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { - /* The same unchecked `eigs_num_arg(&(), __func__)` type-pun as gfx_open. That it is an + /* The same unchecked `eigs_float_arg(&(), __func__)` type-pun as gfx_open. That it is an * oversight rather than a convention is settled 180 lines down: - * audio_stream_open guards this identical pair with `type == VAL_NUM` + * audio_stream_open guards this identical pair with `(type == VAL_FLOAT || type == VAL_INT)` * before reading it. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM, - "audio_open", "[number freq, number channels]", make_num(0)); - - /* #1821: generator refusal must not depend on an untaped device rate. - * At every accepted integer rate, even the longest allowed generator - * duration has an int-sized sample count. Check the double before - * the cast below as well, so an enormous positive - * argument cannot invoke an out-of-range floating-to-integer conversion. */ - double requested_rate = eigs_list_num(arg, 0, __func__); - if (!(requested_rate <= (double)AUDIO_MAX_DEVICE_RATE)) { + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), + "audio_open", "[number freq, number channels]", make_int(0)); + } + /* R1.6e-D1: channels are exact counts in 1..255. Validate all + * deterministic arguments before SDL, device changes or a tape record. */ + int64_t freq = 44100, channels = 1; + if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { + if (!eigs_list_int_range(arg, 0, INT_MIN, INT_MAX, &freq, "audio_open") + || !eigs_list_int_range(arg, 1, 1, 255, &channels, "audio_open")) + return make_int(0); + /* #1821: generator refusal must not depend on an untaped device + * rate. N5 checked the rate exactly before this domain bound. */ + if (freq > AUDIO_MAX_DEVICE_RATE) { rt_error(EK_VALUE, 0, "audio_open: rate exceeds %d Hz (30-second generator limit)", AUDIO_MAX_DEVICE_RATE); return make_null(); } } - if (!g_sdl_lib) { if (!load_sdl2()) return make_num(0); } /* fs:ANSWER 0 is not a device id -- line 1095 returns the real one; libSDL2 unavailable is environment state, not an argument */ + if (!g_sdl_lib) { if (!load_sdl2()) return make_int(0); } /* fs:ANSWER 0 is not a device id -- line 1095 returns the real one; libSDL2 unavailable is environment state, not an argument */ if (!p_SDL_OpenAudioDevice) { fprintf(stderr, "audio_open: SDL2 audio symbols not available\n"); - return make_num(0); /* fs:ANSWER SDL2 audio symbols missing -- same no-device-id 0, detail on stderr above */ + return make_int(0); /* fs:ANSWER SDL2 audio symbols missing -- same no-device-id 0, detail on stderr above */ } p_SDL_Init(MY_SDL_INIT_AUDIO); @@ -1470,18 +1514,15 @@ Value* builtin_audio_open(Value *arg) { want.samples = 1024; want.callback = audio_mix_callback; - if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { - /* Type-checked at the top of the function, before the SDL load. */ - want.freq = (int)eigs_list_num(arg, 0, __func__); - want.channels = (int)eigs_list_num(arg, 1, __func__); - } + want.freq = (int)freq; + want.channels = (Uint8)channels; g_audio_device = p_SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); - if (g_audio_device < 2) { g_audio_device = 0; return make_num(0); } /* fs:ANSWER SDL_OpenAudioDevice failed; valid ids are >= 2, so 0 is the no-device result and g_audio_device is reset to match */ + if (g_audio_device < 2) { g_audio_device = 0; return make_int(0); } /* fs:ANSWER SDL_OpenAudioDevice failed; valid ids are >= 2, so 0 is the no-device result and g_audio_device is reset to match */ g_audio_freq = have.freq; g_audio_channels = have.channels; p_SDL_PauseAudioDevice(g_audio_device, 0); /* start playing */ - return make_num(g_audio_device); + return make_int(g_audio_device); } /* audio_close of null */ @@ -1500,11 +1541,14 @@ Value* builtin_audio_pause(Value *arg) { /* #1007, COERCION shape: a wrong-typed flag fell back to 1 (pause), so * `audio_pause of "off"` PAUSED the device and said nothing. Above the * device check, the [135] rule. */ - STRICT_REQUIRE(arg && arg->type != VAL_NULL && arg->type != VAL_NUM, + STRICT_REQUIRE(arg && arg->type != VAL_NULL && !val_is_number(arg), "audio_pause", "number flag (1 = pause, 0 = unpause) or null"); + int64_t pause = 1; + if (arg && val_is_number(arg) + && !eigs_int_arg_range(arg, INT_MIN, INT_MAX, &pause, "audio_pause")) + return make_null(); if (!g_audio_device) return make_null(); /* fs:VOID no device open: audio_pause answers null on every path -- the return value, not a rejected-argument stand-in */ - int pause = (arg && arg->type == VAL_NUM) ? (int)eigs_num_arg(arg, __func__) : 1; - p_SDL_PauseAudioDevice(g_audio_device, pause); + p_SDL_PauseAudioDevice(g_audio_device, (int)pause); return make_null(); /* fs:VOID audio_pause answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } @@ -1592,17 +1636,25 @@ Value* builtin_audio_capture_open(Value *arg) { && !(arg->type == VAL_LIST && arg->data.list.count >= 2), "audio_capture_open", "[number freq, number channels] or null"); if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM, - "audio_capture_open", "[number freq, number channels]", make_num(0)); + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), + "audio_capture_open", "[number freq, number channels]", make_int(0)); + } + /* R1.6e-D1: channels are exact counts in 1..255. Validate all + * deterministic arguments before SDL, device changes or a tape record. */ + int64_t freq = 44100, channels = 1; + if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { + if (!eigs_list_int_range(arg, 0, INT_MIN, INT_MAX, &freq, "audio_capture_open") + || !eigs_list_int_range(arg, 1, 1, 255, &channels, "audio_capture_open")) + return make_int(0); } TRACE_NONDET_TAKE("audio_capture_open"); if (!g_sdl_lib) { - if (!load_sdl2()) TRACE_NONDET_RECORD("audio_capture_open", make_num(0)); + if (!load_sdl2()) TRACE_NONDET_RECORD("audio_capture_open", make_int(0)); } if (!p_SDL_OpenAudioDevice || !p_SDL_DequeueAudio) { fprintf(stderr, "audio_capture_open: SDL2 capture symbols not available\n"); - TRACE_NONDET_RECORD("audio_capture_open", make_num(0)); + TRACE_NONDET_RECORD("audio_capture_open", make_int(0)); } p_SDL_Init(MY_SDL_INIT_AUDIO); @@ -1613,11 +1665,8 @@ Value* builtin_audio_capture_open(Value *arg) { want.samples = 1024; want.callback = NULL; /* queue mode: drain via SDL_DequeueAudio */ - if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { - /* Type-checked at the top of the function, before the SDL load. */ - want.freq = (int)eigs_list_num(arg, 0, __func__); - want.channels = (int)eigs_list_num(arg, 1, __func__); - } + want.freq = (int)freq; + want.channels = (Uint8)channels; if (g_capture_device) { p_SDL_CloseAudioDevice(g_capture_device); @@ -1626,10 +1675,10 @@ Value* builtin_audio_capture_open(Value *arg) { g_capture_device = p_SDL_OpenAudioDevice(NULL, 1, &want, &have, 0); if (g_capture_device < 2) { g_capture_device = 0; - TRACE_NONDET_RECORD("audio_capture_open", make_num(0)); + TRACE_NONDET_RECORD("audio_capture_open", make_int(0)); } p_SDL_PauseAudioDevice(g_capture_device, 0); /* start recording */ - TRACE_NONDET_RECORD("audio_capture_open", make_num(g_capture_device)); + TRACE_NONDET_RECORD("audio_capture_open", make_int(g_capture_device)); } /* audio_capture_read of null — drain accumulated samples since the @@ -1721,14 +1770,22 @@ Value* builtin_audio_stream_open(Value *arg) { && !(arg->type == VAL_LIST && arg->data.list.count >= 2), "audio_stream_open", "[number freq, number channels] or null"); if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM, - "audio_stream_open", "[number freq, number channels]", make_num(0)); + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), + "audio_stream_open", "[number freq, number channels]", make_int(0)); + } + /* R1.6e-D1: channels are exact counts in 1..255. Validate all + * deterministic arguments before SDL, device changes or a tape record. */ + int64_t freq = 44100, channels = 1; + if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2) { + if (!eigs_list_int_range(arg, 0, INT_MIN, INT_MAX, &freq, "audio_stream_open") + || !eigs_list_int_range(arg, 1, 1, 255, &channels, "audio_stream_open")) + return make_int(0); } - if (!g_sdl_lib) { if (!load_sdl2()) return make_num(0); } /* fs:ANSWER BUILTINS.md audio_stream_open: "0 when SDL/audio is unavailable"; libSDL2 absent is environment state */ + if (!g_sdl_lib) { if (!load_sdl2()) return make_int(0); } /* fs:ANSWER BUILTINS.md audio_stream_open: "0 when SDL/audio is unavailable"; libSDL2 absent is environment state */ if (!p_SDL_OpenAudioDevice || !p_SDL_QueueAudio) { fprintf(stderr, "audio_stream_open: SDL2 audio symbols not available\n"); - return make_num(0); /* fs:ANSWER SDL2 queue-audio symbols missing -- the same documented 0-when-unavailable result */ + return make_int(0); /* fs:ANSWER SDL2 queue-audio symbols missing -- the same documented 0-when-unavailable result */ } p_SDL_Init(MY_SDL_INIT_AUDIO); @@ -1739,23 +1796,19 @@ Value* builtin_audio_stream_open(Value *arg) { want.samples = 1024; want.callback = NULL; /* queue mode: feed via SDL_QueueAudio */ - if (arg && arg->type == VAL_LIST && arg->data.list.count >= 2 - && list_elem_type(arg, 0) == VAL_NUM - && list_elem_type(arg, 1) == VAL_NUM) { - want.freq = (int)eigs_list_num(arg, 0, __func__); - want.channels = (int)eigs_list_num(arg, 1, __func__); - } + want.freq = (int)freq; + want.channels = (Uint8)channels; if (g_stream_device) { p_SDL_CloseAudioDevice(g_stream_device); g_stream_device = 0; } g_stream_device = p_SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); - if (g_stream_device < 2) { g_stream_device = 0; return make_num(0); } /* fs:ANSWER SDL_OpenAudioDevice failed; valid ids are >= 2, so 0 is the documented unavailable result and g_stream_device is reset */ + if (g_stream_device < 2) { g_stream_device = 0; return make_int(0); } /* fs:ANSWER SDL_OpenAudioDevice failed; valid ids are >= 2, so 0 is the documented unavailable result and g_stream_device is reset */ g_stream_freq = have.freq; g_stream_channels = have.channels; p_SDL_PauseAudioDevice(g_stream_device, 0); /* start playing */ - return make_num(g_stream_device); + return make_int(g_stream_device); } /* audio_stream_push of samples — queue a block of float samples [-1, 1] @@ -1799,10 +1852,10 @@ Value* builtin_audio_stream_queued(Value *arg) { (void)arg; TRACE_NONDET_TAKE("audio_stream_queued"); if (!g_stream_device || !p_SDL_GetQueuedAudioSize) - TRACE_NONDET_RECORD("audio_stream_queued", make_num(0)); + TRACE_NONDET_RECORD("audio_stream_queued", make_int(0)); Uint32 bytes = p_SDL_GetQueuedAudioSize(g_stream_device); TRACE_NONDET_RECORD("audio_stream_queued", - make_num((double)(bytes / sizeof(int16_t)))); + make_int((int64_t)(bytes / sizeof(int16_t)))); } /* audio_stream_clear of null — drop any buffered audio on the live @@ -1838,16 +1891,19 @@ Value* builtin_audio_music_play(Value *arg) { /* A wrong-typed `loops` is the COERCION shape: it fell back to -1 * (forever), so a typo made the track loop rather than play once. */ STRICT_REQUIRE(arg->data.list.count >= 2 - && list_elem_type(arg, 1) != VAL_NUM, + && (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), "audio_music_play", "[string path, number loops]"); + int64_t loops = -1; + if (arg->data.list.count >= 2 + && (list_elem_type(arg, 1) == VAL_FLOAT || list_elem_type(arg, 1) == VAL_INT) + && !eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &loops, "audio_music_play")) + return make_bool(0); if (__builtin_expect(g_replay_enabled, 0)) { rt_error(EK_IO, 0, "audio_music_play: not replayable under EIGS_REPLAY " "(audio file-read boundary; see docs/TRACE.md)"); return make_bool(0); } const char *path = list_get_ref(arg, 0)->data.str; - int loops = (arg->data.list.count >= 2 && list_elem_type(arg, 1) == VAL_NUM) - ? (int)eigs_list_num(arg, 1, __func__) : -1; if (!load_sdl2()) return make_bool(0); /* fs:ANSWER the header's documented "0 on failure (missing mixer lib ...)" -- libSDL2 absent is environment state, not an argument */ p_SDL_Init(MY_SDL_INIT_AUDIO); /* ensure the audio subsystem is up */ if (!load_sdl_mixer()) return make_bool(0); /* fs:ANSWER SDL2_mixer not loadable -- the documented missing-mixer-lib 0 */ @@ -1869,7 +1925,7 @@ Value* builtin_audio_music_play(Value *arg) { p_SDL_GetError ? p_SDL_GetError() : "?"); return make_bool(0); /* fs:ANSWER Mix_LoadMUS failed -- the documented unreadable/undecodable-file 0 */ } - if (p_Mix_PlayMusic(g_music, loops) < 0) { + if (p_Mix_PlayMusic(g_music, (int)loops) < 0) { fprintf(stderr, "audio_music: play failed: %s\n", p_SDL_GetError ? p_SDL_GetError() : "?"); return make_bool(0); /* fs:ANSWER Mix_PlayMusic failed -- documented 0; line 1378 returns 1 only when the track is actually playing */ @@ -1882,18 +1938,18 @@ Value* builtin_audio_music_volume(Value *arg) { STRICT_LIST_MAX(arg, 1, "audio_music_volume"); /* #1007, COERCION shape: a wrong-typed volume fell through to v = 0 and * MUTED the music. Above the mixer-state check, the [135] rule. */ - STRICT_REQUIRE(!(arg && arg->type == VAL_NUM) + STRICT_REQUIRE(!(arg && val_is_number(arg)) && !(arg && arg->type == VAL_LIST && arg->data.list.count >= 1 - && list_elem_type(arg, 0) == VAL_NUM), + && (list_elem_type(arg, 0) == VAL_FLOAT || list_elem_type(arg, 0) == VAL_INT)), "audio_music_volume", "number volume 0..128 or [number volume]"); - if (!g_mixer_open || !p_Mix_VolumeMusic) return make_null(); /* fs:VOID no mixer open: audio_music_volume answers null on every path -- the return value, not a rejected-argument stand-in */ - int v = 0; - if (arg && arg->type == VAL_NUM) v = (int)eigs_num_arg(arg, __func__); - else if (arg && arg->type == VAL_LIST && arg->data.list.count >= 1 - && list_elem_type(arg, 0) == VAL_NUM) - v = (int)eigs_list_num(arg, 0, __func__); - if (v < 0) v = 0; - if (v > MY_MIX_MAX_VOLUME) v = MY_MIX_MAX_VOLUME; + int v=0; + if (arg && val_is_number(arg)) { + if (!eigs_bounded_arg(arg,0,MY_MIX_MAX_VOLUME,&v,"audio_music_volume")) return make_null(); + } else if (arg && arg->type==VAL_LIST && arg->data.list.count>=1 && + (list_elem_type(arg,0)==VAL_FLOAT || list_elem_type(arg,0)==VAL_INT)) { + if (!eigs_list_bounded(arg,0,0,MY_MIX_MAX_VOLUME,&v,"audio_music_volume")) return make_null(); + } + if (!g_mixer_open || !p_Mix_VolumeMusic) return make_null(); /* fs:VOID no music mixer */ p_Mix_VolumeMusic(v); return make_null(); /* fs:VOID audio_music_volume answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } @@ -1914,16 +1970,16 @@ Value* builtin_audio_play(Value *arg) { * -- so the guard would exist only where nothing runs it. */ STRICT_REQUIRE(gfx_bad_samples(arg), "audio_play", "a list or buffer of samples, or null"); - if (!g_audio_device) return make_num(0); /* fs:ANSWER BUILTINS.md audio_play: "0 on ... closed device"; channel ids are slot+1 >= 1 (line 1057), so 0 is not a channel */ + if (!g_audio_device) return make_int(0); /* fs:ANSWER BUILTINS.md audio_play: "0 on ... closed device"; channel ids are slot+1 >= 1 (line 1057), so 0 is not a channel */ int n = 0; int16_t *buf = audio_convert_samples(arg, &n); if (!buf) { /* fs:CHANNEL post-raise placeholder for a wrong-typed sample list. */ - if (g_has_error) return make_num(0); + if (g_has_error) return make_int(0); /* fs:ANSWER an empty or over-64MB clip queues nothing. */ - return make_num(0); + return make_int(0); } - return make_num(audio_install_channel(buf, n, 1)); + return make_int(audio_install_channel(buf, n, 1)); } /* audio_play_loop of [samples, loops] — play `samples` `loops` times on @@ -1937,19 +1993,13 @@ Value* builtin_audio_play_loop(Value *arg) { * soft, and it moves BELOW the guard so the guard is reachable on a * machine with no audio device (the [135] rule). */ ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2 - || list_elem_type(arg, 1) != VAL_NUM, - "audio_play_loop", "[samples, number loops]", make_num(0)); - /* #152: NaN/huge casts are UB; -1 is the one negative with meaning. */ - double loops_d = eigs_list_num(arg, 1, __func__); - int loops; - if (loops_d == -1.0) loops = -1; - else { - ARG_GUARD(isnan(loops_d) || loops_d < 1.0 || loops_d > 10000.0, - "audio_play_loop", - "[samples, number loops] with loops == -1 or 1..10000", - make_num(0)); - loops = (int)loops_d; - } + || (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), + "audio_play_loop", "[samples, number loops]", make_int(0)); + int64_t exact_loops; + if (!eigs_list_int_range(arg,1,INT_MIN,INT_MAX,&exact_loops,"audio_play_loop")) return make_int(0); + int loops=(int)exact_loops; + ARG_GUARD(loops != -1 && (loops<1 || loops>10000),"audio_play_loop", + "[samples, number loops] with loops == -1 or 1..10000",make_int(0)); /* #1007 round 3: the samples slot, beside the loops slot and above the * device check for the same reachability reason. */ /* #1637: a non-number sample element is refused here too, above the @@ -1960,16 +2010,16 @@ Value* builtin_audio_play_loop(Value *arg) { || (list_elem_type(arg, 0) == VAL_LIST && !gfx_list_all_num(samples)), "audio_play_loop", "[list or buffer of samples, number loops]"); - if (!g_audio_device) return make_num(0); /* fs:ANSWER BUILTINS.md audio_play_loop: "0 on ... closed device"; channel ids are slot+1 >= 1, so 0 is not a channel */ + if (!g_audio_device) return make_int(0); /* fs:ANSWER BUILTINS.md audio_play_loop: "0 on ... closed device"; channel ids are slot+1 >= 1, so 0 is not a channel */ int n = 0; int16_t *buf = audio_convert_samples(samples, &n); if (!buf) { /* fs:CHANNEL post-raise placeholder for a wrong-typed sample list. */ - if (g_has_error) return make_num(0); + if (g_has_error) return make_int(0); /* fs:ANSWER an empty or over-64MB clip queues nothing. */ - return make_num(0); + return make_int(0); } - return make_num(audio_install_channel(buf, n, loops)); + return make_int(audio_install_channel(buf, n, loops)); } /* audio_volume of [channel, vol] — live per-channel volume, 0.0..4.0 @@ -1978,56 +2028,59 @@ Value* builtin_audio_volume(Value *arg) { STRICT_LIST_MAX(arg, 2, "audio_volume"); /* #1007: the mixed condition split, as its #971 deferral marker asked. */ ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2 - || list_elem_type(arg, 0) != VAL_NUM - || list_elem_type(arg, 1) != VAL_NUM, - "audio_volume", "[number channel, number volume]", make_num(0)); - if (!g_audio_device) return make_num(0); /* fs:ANSWER 0 means "that channel is not playing", and with no device open no channel is */ - int c = (int)eigs_slot_num(list_slot(arg, 0), "audio_volume") - 1; - if (c < 0 || c >= AUDIO_MAX_CHANNELS) return make_num(0); /* fs:ANSWER 0 means "that channel is not playing" -- the same value line 1452 returns for an inactive in-range channel; an out-of-range id is definitionally inactive */ - double vol = eigs_slot_num(list_slot(arg, 1), "audio_volume"); - if (isnan(vol) || vol < 0.0) vol = 0.0; - if (vol > 4.0) vol = 4.0; + || (list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) + || (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), + "audio_volume", "[number channel, number volume]", make_int(0)); + int64_t channel; + if (!eigs_list_int_range(arg,0,INT_MIN,INT_MAX,&channel,"audio_volume")) return make_int(0); + int vol; + if (!eigs_list_bounded(arg,1,0,4,&vol,"audio_volume")) return make_int(0); + if (!g_audio_device || channel<1 || channel>AUDIO_MAX_CHANNELS) return make_int(0); /* fs:ANSWER inactive channel */ + int c=(int)channel-1; p_SDL_LockAudioDevice(g_audio_device); int ok = g_audio_ch[c].active; if (ok) g_audio_ch[c].volume = vol; p_SDL_UnlockAudioDevice(g_audio_device); - return make_num(ok); + return make_int(ok); } /* audio_stop of channel — stop one mixer channel. Returns 1, or 0 on a * bad/inactive channel. */ Value* builtin_audio_stop(Value *arg) { /* #1007: the mixed condition split, as its #971 deferral marker asked. */ - ARG_GUARD(!arg || arg->type != VAL_NUM, "audio_stop", "number channel", - make_num(0)); - if (!g_audio_device) return make_num(0); /* fs:ANSWER 0 means "the channel was not active", and with no device open none is */ - int c = (int)eigs_num_arg(arg, __func__) - 1; - if (c < 0 || c >= AUDIO_MAX_CHANNELS) return make_num(0); /* fs:ANSWER 0 means "the channel was not active" -- the same value line 1465 returns for an inactive in-range channel */ + ARG_GUARD(!arg || !val_is_number(arg), "audio_stop", "number channel", + make_int(0)); + int64_t channel = 0; + if (!eigs_int_arg_range(arg, INT_MIN, INT_MAX, &channel, "audio_stop")) + return make_int(0); + if (!g_audio_device) return make_int(0); /* fs:ANSWER 0 means "the channel was not active", and with no device open none is */ + if (channel < 1 || channel > AUDIO_MAX_CHANNELS) return make_int(0); /* fs:ANSWER 0 means "the channel was not active" -- an out-of-range id is inactive */ + int c = (int)channel - 1; p_SDL_LockAudioDevice(g_audio_device); int ok = g_audio_ch[c].active; g_audio_ch[c].active = 0; p_SDL_UnlockAudioDevice(g_audio_device); - return make_num(ok); + return make_int(ok); } /* audio_queue_size of null — bytes queued */ Value* builtin_audio_queue_size(Value *arg) { (void)arg; - if (!g_audio_device) return make_num(0); /* fs:ANSWER 0 bytes queued: with no device no channel holds unplayed samples, which is exactly the total lines 1477-1485 sum for an idle device */ + if (!g_audio_device) return make_int(0); /* fs:ANSWER 0 bytes queued: with no device no channel holds unplayed samples, which is exactly the total lines 1477-1485 sum for an idle device */ /* Mixer equivalent of the old queued-bytes count: unplayed bytes * summed over active FINITE channels (infinite loops would be * infinity; they are excluded so refill-style polls terminate). */ - double bytes = 0; + int64_t bytes = 0; p_SDL_LockAudioDevice(g_audio_device); for (int c = 0; c < AUDIO_MAX_CHANNELS; c++) { AudioChannel *ch = &g_audio_ch[c]; if (!ch->active || ch->loops < 0) continue; - double rem = (double)(ch->count - ch->pos) - + (double)(ch->loops - 1) * (double)ch->count; - bytes += rem * (double)sizeof(int16_t); + int64_t rem = (int64_t)(ch->count - ch->pos) + + ((int64_t)ch->loops - 1) * (int64_t)ch->count; + bytes += rem * (int64_t)sizeof(int16_t); } p_SDL_UnlockAudioDevice(g_audio_device); - return make_num(bytes); + return make_int(bytes); } /* audio_clear of null — stop all mixer channels and free their clips */ @@ -2047,10 +2100,11 @@ static int audio_generator_sample_count(double duration, int rate, rt_error(EK_VALUE, 0, "%s: duration exceeds the 30 second limit", who); return 0; } - if (!(samples <= (double)INT_MAX)) { - rt_error(EK_VALUE, 0, "%s: duration produces a sample count that does not fit in int", who); + int64_t rounded; + if (!eigs_slot_magnitude(slot_from_f64(samples), INT_MIN, INT_MAX, &rounded, who)) return 0; - } + /* RULINGS B5: Q7 validates the rounded count here; conversion itself + * remains truncation until R2.6's #1746 residue migration. */ if (samples <= 0.0) { *count = 0; return 1; @@ -2082,13 +2136,14 @@ Value* builtin_audio_sine(Value *arg) { * Seven builtins share the shape; the list was derived by running each * candidate twice and diffing (a per-run difference IS the disclosure), * not by reading names — audio_gain was missed by the reading. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT), "audio_sine", "[number freq, number duration, number amplitude]", make_list(0)); - double freq = eigs_list_num(arg, 0, __func__); - double dur = eigs_list_num(arg, 1, __func__); - double amp = eigs_list_num(arg, 2, __func__); + double freq = eigs_list_float(arg, 0, __func__); + double dur = eigs_list_float(arg, 1, __func__); + double amp = eigs_list_float(arg, 2, __func__); + if (g_has_error) return make_null(); int rate = g_audio_freq > 0 ? g_audio_freq : 44100; int n; if (!audio_generator_sample_count(dur, rate, "audio_sine", &n)) return make_null(); @@ -2098,7 +2153,7 @@ Value* builtin_audio_sine(Value *arg) { for (int i = 0; i < n; i++) { double t = (double)i / rate; double s = sin(2.0 * M_PI * freq * t) * amp; - list_append_owned(list, make_num(s)); + list_append_owned(list, make_float(s)); } return list; } @@ -2118,13 +2173,14 @@ Value* builtin_audio_saw(Value *arg) { * pointer value, disclosed into script-visible data and varying per run. * All six generators share the shape; guarding one would have left the * class open. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT), "audio_saw", "[number freq, number duration, number amplitude]", make_list(0)); - double freq = eigs_list_num(arg, 0, __func__); - double dur = eigs_list_num(arg, 1, __func__); - double amp = eigs_list_num(arg, 2, __func__); + double freq = eigs_list_float(arg, 0, __func__); + double dur = eigs_list_float(arg, 1, __func__); + double amp = eigs_list_float(arg, 2, __func__); + if (g_has_error) return make_null(); int rate = g_audio_freq > 0 ? g_audio_freq : 44100; int n; if (!audio_generator_sample_count(dur, rate, "audio_saw", &n)) return make_null(); @@ -2134,7 +2190,7 @@ Value* builtin_audio_saw(Value *arg) { for (int i = 0; i < n; i++) { double t = (double)i / rate; double s = 2.0 * (t * freq - floor(t * freq + 0.5)) * amp; - list_append_owned(list, make_num(s)); + list_append_owned(list, make_float(s)); } return list; } @@ -2154,13 +2210,14 @@ Value* builtin_audio_square(Value *arg) { * pointer value, disclosed into script-visible data and varying per run. * All six generators share the shape; guarding one would have left the * class open. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT), "audio_square", "[number freq, number duration, number amplitude]", make_list(0)); - double freq = eigs_list_num(arg, 0, __func__); - double dur = eigs_list_num(arg, 1, __func__); - double amp = eigs_list_num(arg, 2, __func__); + double freq = eigs_list_float(arg, 0, __func__); + double dur = eigs_list_float(arg, 1, __func__); + double amp = eigs_list_float(arg, 2, __func__); + if (g_has_error) return make_null(); int rate = g_audio_freq > 0 ? g_audio_freq : 44100; int n; if (!audio_generator_sample_count(dur, rate, "audio_square", &n)) return make_null(); @@ -2170,7 +2227,7 @@ Value* builtin_audio_square(Value *arg) { for (int i = 0; i < n; i++) { double t = (double)i / rate; double s = (sin(2.0 * M_PI * freq * t) >= 0 ? 1.0 : -1.0) * amp; - list_append_owned(list, make_num(s)); + list_append_owned(list, make_float(s)); } return list; } @@ -2191,17 +2248,18 @@ Value* builtin_audio_sweep(Value *arg) { * pointer value, disclosed into script-visible data and varying per run. * All six generators share the shape; guarding one would have left the * class open. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM || - list_elem_type(arg, 3) != VAL_NUM || - list_elem_type(arg, 4) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT) || + (list_elem_type(arg, 3) != VAL_FLOAT && list_elem_type(arg, 3) != VAL_INT) || + (list_elem_type(arg, 4) != VAL_FLOAT && list_elem_type(arg, 4) != VAL_INT), "audio_sweep", "[number freq_start, number freq_end, number duration, number amplitude, number waveform]", make_list(0)); - double f0 = eigs_list_num(arg, 0, __func__); - double f1 = eigs_list_num(arg, 1, __func__); - double dur = eigs_list_num(arg, 2, __func__); - double amp = eigs_list_num(arg, 3, __func__); - int wave = (int)eigs_list_num(arg, 4, __func__); + double f0 = eigs_list_float(arg, 0, __func__); + double f1 = eigs_list_float(arg, 1, __func__); + double dur = eigs_list_float(arg, 2, __func__); + double amp = eigs_list_float(arg, 3, __func__); + int64_t wave = eigs_list_int(arg, 4, "audio_sweep"); + if (g_has_error) return make_null(); int rate = g_audio_freq > 0 ? g_audio_freq : 44100; int n; if (!audio_generator_sample_count(dur, rate, "audio_sweep", &n)) return make_null(); @@ -2219,7 +2277,7 @@ Value* builtin_audio_sweep(Value *arg) { s = sin(2.0 * M_PI * phase) * amp; else s = (2.0 * phase - 1.0) * amp; - list_append_owned(list, make_num(s)); + list_append_owned(list, make_float(s)); } return list; } @@ -2303,11 +2361,12 @@ Value* builtin_audio_noise(Value *arg) { * pointer value, disclosed into script-visible data and varying per run. * All six generators share the shape; guarding one would have left the * class open. */ - ARG_GUARD(list_elem_type(arg, 0) != VAL_NUM || - list_elem_type(arg, 1) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 0) != VAL_FLOAT && list_elem_type(arg, 0) != VAL_INT) || + (list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), "audio_noise", "[number duration, number amplitude]", make_list(0)); - double dur = eigs_list_num(arg, 0, __func__); - double amp = eigs_list_num(arg, 1, __func__); + double dur = eigs_list_float(arg, 0, __func__); + double amp = eigs_list_float(arg, 1, __func__); + if (g_has_error) return make_null(); int rate = g_audio_freq > 0 ? g_audio_freq : 44100; int n; /* #1767's refusal (a duration over 30 s, or a sample count that does not @@ -2338,7 +2397,7 @@ Value* builtin_audio_noise(Value *arg) { list = make_list(n); for (int i = 0; i < n; i++) { double s = (eigs_random_double() * 2.0 - 1.0) * amp; - list_append_owned(list, make_num(s)); + list_append_owned(list, make_float(s)); } } /* Every live answer is recorded, the empty one included: replay takes a @@ -2370,12 +2429,13 @@ Value* builtin_audio_mix(Value *arg) { Value *out = make_list(n); for (int i = 0; i < n; i++) { - double sa = (i < a->data.list.count && list_elem_type(a, i) == VAL_NUM) ? eigs_slot_num(list_slot(a, i), __func__) : 0; - double sb = (i < b->data.list.count && list_elem_type(b, i) == VAL_NUM) ? eigs_slot_num(list_slot(b, i), __func__) : 0; + double sa = (i < a->data.list.count && (list_elem_type(a, i) == VAL_FLOAT || list_elem_type(a, i) == VAL_INT)) ? eigs_list_float(a, i, "audio_mix") : 0; + double sb = (i < b->data.list.count && (list_elem_type(b, i) == VAL_FLOAT || list_elem_type(b, i) == VAL_INT)) ? eigs_list_float(b, i, "audio_mix") : 0; + if (g_has_error) { val_decref(out); return make_list(0); } double mixed = sa + sb; if (mixed > 1.0) mixed = 1.0; if (mixed < -1.0) mixed = -1.0; - list_append_owned(out, make_num(mixed)); + list_append_owned(out, make_float(mixed)); } return out; } @@ -2401,18 +2461,20 @@ Value* builtin_audio_gain(Value *arg) { * `char *` into script-visible data exactly as audio_sweep did. Note the * element read on the next line was ALREADY type-checked — the argument * beside it was not, which is the same next-line asymmetry as gfx_open. */ - ARG_GUARD(list_elem_type(arg, 1) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT), "audio_gain", "[samples, number volume]", make_list(0)); - double vol = eigs_list_num(arg, 1, __func__); + double vol = eigs_list_float(arg, 1, "audio_gain"); + if (g_has_error) return make_list(0); int n = samples->data.list.count; Value *out = make_list(n); for (int i = 0; i < n; i++) { - double s = (list_elem_type(samples, i) == VAL_NUM) ? eigs_slot_num(list_slot(samples, i), __func__) : 0; + double s = ((list_elem_type(samples, i) == VAL_FLOAT || list_elem_type(samples, i) == VAL_INT)) ? eigs_list_float(samples, i, "audio_gain") : 0; + if (g_has_error) { val_decref(out); return make_list(0); } s *= vol; if (s > 1.0) s = 1.0; if (s < -1.0) s = -1.0; - list_append_owned(out, make_num(s)); + list_append_owned(out, make_float(s)); } return out; } @@ -2432,10 +2494,10 @@ Value* builtin_audio_envelope(Value *arg) { * pointer value, disclosed into script-visible data and varying per run. * All six generators share the shape; guarding one would have left the * class open. */ - ARG_GUARD(list_elem_type(arg, 1) != VAL_NUM || - list_elem_type(arg, 2) != VAL_NUM || - list_elem_type(arg, 3) != VAL_NUM || - list_elem_type(arg, 4) != VAL_NUM, + ARG_GUARD((list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT) || + (list_elem_type(arg, 2) != VAL_FLOAT && list_elem_type(arg, 2) != VAL_INT) || + (list_elem_type(arg, 3) != VAL_FLOAT && list_elem_type(arg, 3) != VAL_INT) || + (list_elem_type(arg, 4) != VAL_FLOAT && list_elem_type(arg, 4) != VAL_INT), "audio_envelope", "[samples, number attack, number decay, number sustain, number release]", make_list(0)); ARG_GUARD(list_elem_type(arg, 0) != VAL_LIST, "audio_envelope", "[list samples, number attack, number decay, number sustain, number release]", @@ -2446,18 +2508,25 @@ Value* builtin_audio_envelope(Value *arg) { * nothing. */ STRICT_REQUIRE(!gfx_list_all_num(samples), "audio_envelope", "[list of numbers, number attack, number decay, number sustain, number release]"); - double attack = eigs_list_num(arg, 1, __func__); - double decay = eigs_list_num(arg, 2, __func__); - double sustain = eigs_list_num(arg, 3, __func__); - double release = eigs_list_num(arg, 4, __func__); + double attack = eigs_list_float(arg, 1, __func__); + double decay = eigs_list_float(arg, 2, __func__); + double sustain = eigs_list_float(arg, 3, __func__); + double release = eigs_list_float(arg, 4, __func__); int n = samples->data.list.count; int rate = g_audio_freq > 0 ? g_audio_freq : 44100; - int a_samples = (int)(attack * rate); - int d_samples = (int)(decay * rate); - int r_samples = (int)(release * rate); - int s_start = a_samples + d_samples; - int r_start = n - r_samples; + if (g_has_error) return make_null(); + int64_t a_samples, d_samples, r_samples; + if (!eigs_slot_magnitude(slot_from_f64(attack * rate), INT_MIN, INT_MAX, + &a_samples, "audio_envelope") || + !eigs_slot_magnitude(slot_from_f64(decay * rate), INT_MIN, INT_MAX, + &d_samples, "audio_envelope") || + !eigs_slot_magnitude(slot_from_f64(release * rate), INT_MIN, INT_MAX, + &r_samples, "audio_envelope")) return make_null(); + /* Wider intermediates keep two valid C-int durations from overflowing + * while locating the sustain/release boundaries. */ + int64_t s_start = a_samples + d_samples; + int64_t r_start = (int64_t)n - r_samples; if (r_start < s_start) r_start = s_start; Value *out = make_list(n); @@ -2478,13 +2547,22 @@ Value* builtin_audio_envelope(Value *arg) { double frac = (r_samples > 0) ? (double)(i - r_start) / r_samples : 1.0; env = sustain * (1.0 - frac); } - double s = (list_elem_type(samples, i) == VAL_NUM) ? eigs_slot_num(list_slot(samples, i), __func__) : 0; + double s = ((list_elem_type(samples, i) == VAL_FLOAT || list_elem_type(samples, i) == VAL_INT)) ? eigs_list_float(samples, i, __func__) : 0; + if (g_has_error) { val_decref(out); return make_null(); } s *= env; - list_append_owned(out, make_num(s)); + list_append_owned(out, make_float(s)); } return out; } +/* The complete caller census is gfx_fb and ppu_render_frame (round e). + * Every call tests g_has_error before publishing the byte or its effects. */ +static uint8_t gfx_byte_at(const Value *buffer, int64_t index, const char *who) { + uint8_t byte = 0; + (void)buffer_byte_at(buffer, index, &byte, who); + return byte; +} + /* gfx_fb of [buffer, width, height, x, y, scale] * Blit a framebuffer (VAL_BUFFER of width*height palette indices 0-3) to the * renderer as a scaled texture. One C call replaces width*height draw calls. @@ -2501,58 +2579,81 @@ Value* builtin_gfx_fb(Value *arg) { "gfx_fb", "[buffer fb, number w, number h, number x, number y, number scale]", make_null()); - Value *buf = list_get_ref(arg, 0); - int w = (int)eigs_list_num(arg, 1, __func__); - int h = (int)eigs_list_num(arg, 2, __func__); - int dx = (int)eigs_list_num(arg, 3, __func__); - int dy = (int)eigs_list_num(arg, 4, __func__); - int sc = (int)eigs_list_num(arg, 5, __func__); - /* A non-positive dimension is DEGENERATE geometry (nothing to blit), - * the same verdict gfx_rrect gives a zero-size rectangle. A buffer - * SHORTER than w * h is an argument mismatch and is loud -- still above - * the SDL check so it is reachable without libSDL2 (the [135] rule); - * the reads it needs are type-checked by the guard above. */ - if (w <= 0 || h <= 0 || sc <= 0) return make_null(); /* fs:EMPTY a zero or negative width/height/scale covers no pixels, so drawing nothing IS the answer -- the same degenerate-geometry identity gfx_rrect gives */ - ARG_GUARD(buf->data.buffer.count < w * h, - "gfx_fb", - "[buffer fb, number w, number h, number x, number y, number scale] with len of fb >= w * h", - make_null()); + Value *buf = list_get_ref(arg, 0); + int64_t wi, hi; + if (!eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &wi, __func__) || + !eigs_list_int_range(arg, 2, INT_MIN, INT_MAX, &hi, __func__)) return make_null(); + double dx = eigs_list_float(arg, 3, __func__); + if (g_has_error) return make_null(); + double dy = eigs_list_float(arg, 4, __func__); + if (g_has_error) return make_null(); + double scale = eigs_list_float(arg, 5, __func__); + if (g_has_error) return make_null(); + if (wi <= 0 || hi <= 0 || scale <= 0) return make_null(); /* fs:EMPTY degenerate framebuffer geometry. */ + int64_t total = wi*hi; + ARG_GUARD(total > buf->data.buffer.count, "gfx_fb", "len of fb >= w * h", make_null()); if (!g_renderer || !p_SDL_CreateTexture || !p_SDL_UpdateTexture || !p_SDL_RenderCopy) - return make_null(); /* fs:VOID no window / no SDL texture symbols: gfx_fb answers null on every path -- the return value, not a rejected-argument stand-in */ - - /* Recreate texture if size changed */ - if (!g_fb_texture || g_fb_w != w || g_fb_h != h) { - if (g_fb_texture) p_SDL_DestroyTexture(g_fb_texture); - g_fb_texture = p_SDL_CreateTexture(g_renderer, - MY_SDL_PIXELFORMAT_ARGB8888, MY_SDL_TEXTUREACCESS_STREAMING, w, h); - if (!g_fb_texture) return make_null(); /* fs:ANSWER SDL_CreateTexture failed -- environment/driver state, not an argument; nothing can be blitted so null is the result */ - g_fb_w = w; - g_fb_h = h; - } - - /* GB shade palette: index → ARGB */ - static const Uint32 palette[4] = { - 0xFFFFFFFF, /* 0 = white */ - 0xFFAAAAAA, /* 1 = light */ - 0xFF555555, /* 2 = dark */ - 0xFF000000 /* 3 = black */ - }; - - /* Convert buffer → ARGB pixel array */ - int total = w * h; - Uint32 *pixels = xmalloc_array(total, sizeof(Uint32)); - for (int i = 0; i < total; i++) { - int idx = buffer_read_byte(buf, i); + return make_null(); /* fs:VOID no renderer or optional texture API. */ + int w = (int)wi, h = (int)hi; + /* Preserve the source layout and validate every source byte, even if + * clipping later hides it. Q1 requires an exact byte at the data boundary. */ + static const Uint32 palette[4] = {0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000}; + Uint32 *pixels = xmalloc_array((size_t)total, sizeof *pixels); + for (int64_t i = 0; i < total; ++i) { + int idx = gfx_byte_at(buf, i, __func__); if (g_has_error) { free(pixels); return make_null(); } pixels[i] = palette[idx & 3]; } - - p_SDL_UpdateTexture(g_fb_texture, NULL, pixels, w * (int)sizeof(Uint32)); + EigsRect area = gfx_drawable(); EigsShape shape; + if (!gfx_rect_ld(dx, dy, wi*(long double)scale, hi*(long double)scale, &area, &shape, __func__)) { + free(pixels); return make_null(); + } + if (shape.empty) { free(pixels); return make_null(); } /* fs:EMPTY source is valid but destination has no visible pixels. */ + long double rx = roundl(dx), ry = roundl(dy); + long double rw = roundl((long double)dx+wi*(long double)scale)-rx; + long double rh = roundl((long double)dy+hi*(long double)scale)-ry; + if (scale == floor(scale) && rx >= area.x && ry >= area.y && + rx+rw <= (long double)area.x+area.w && ry+rh <= (long double)area.y+area.h && + rw > 0 && rh > 0 && rw <= INT_MAX && rh <= INT_MAX && wi <= INT_MAX/(int)sizeof *pixels) { + if (!g_fb_texture || g_fb_w != w || g_fb_h != h) { + if (g_fb_texture) p_SDL_DestroyTexture(g_fb_texture); + g_fb_texture = p_SDL_CreateTexture(g_renderer, MY_SDL_PIXELFORMAT_ARGB8888, + MY_SDL_TEXTUREACCESS_STREAMING, w, h); + if (!g_fb_texture) { free(pixels); return make_null(); } /* fs:ANSWER SDL texture allocation failed. */ + g_fb_w = w; g_fb_h = h; + } + p_SDL_UpdateTexture(g_fb_texture, NULL, pixels, w*(int)sizeof *pixels); + SDL_Rect dst = {(int)rx, (int)ry, (int)rw, (int)rh}; + p_SDL_RenderCopy(g_renderer, g_fb_texture, NULL, &dst); + } else { + /* A destination outside SDL's integer rectangle domain is sampled + * only over visible pixels. Inverse source mapping avoids clipping + * and stretching the complete source into the visible destination. + * ceil(pixel+0.5)-1 selects the source cell whose independently + * rounded right edge contains this pixel, including exact Q4 ties. */ + for (int y = shape.y; y < shape.y+shape.h; ++y) { + long double sy = ceill(((long double)y+0.5L-dy)/scale)-1; + if (sy < 0 || sy >= hi) continue; + int x = shape.x; + while (x < shape.x+shape.w) { + long double sx = ceill(((long double)x+0.5L-dx)/scale)-1; + if (sx < 0 || sx >= wi) { ++x; continue; } + Uint32 pixel = pixels[(int64_t)sy*wi+(int64_t)sx]; + int end = x+1; + while (end < shape.x+shape.w) { + long double next = ceill(((long double)end+0.5L-dx)/scale)-1; + if (next < 0 || next >= wi || pixels[(int64_t)sy*wi+(int64_t)next] != pixel) break; + ++end; + } + p_SDL_SetRenderDrawColor(g_renderer, (pixel>>16)&255, (pixel>>8)&255, pixel&255, 255); + SDL_Rect run = {x, y, end-x, 1}; + p_SDL_RenderFillRect(g_renderer, &run); + x = end; + } + } + } free(pixels); - - SDL_Rect dst = { dx, dy, w * sc, h * sc }; - p_SDL_RenderCopy(g_renderer, g_fb_texture, NULL, &dst); - return make_null(); /* fs:VOID gfx_fb answers null on every path -- this is the return value, not a stand-in for a rejected argument */ + return make_null(); /* fs:VOID graphics commands return null. */ } /* ================================================================ @@ -2582,7 +2683,7 @@ Value* builtin_ppu_render_frame(Value *arg) { double *fb = fb_v->data.buffer.data; - int lcdc = buffer_read_byte(mem_v, 0xFF40); + int lcdc = gfx_byte_at(mem_v, 0xFF40, __func__); if (g_has_error) return make_null(); if (!(lcdc & 0x80)) { /* LCD off — blank */ @@ -2590,19 +2691,19 @@ Value* builtin_ppu_render_frame(Value *arg) { return make_null(); /* fs:VOID the LCD is off, so the frame was blanked; ppu_render_frame answers null on every path -- this is the return value, not a stand-in for a rejected argument */ } - int scy = buffer_read_byte(mem_v, 0xFF42); + int scy = gfx_byte_at(mem_v, 0xFF42, __func__); if (g_has_error) return make_null(); - int scx = buffer_read_byte(mem_v, 0xFF43); + int scx = gfx_byte_at(mem_v, 0xFF43, __func__); if (g_has_error) return make_null(); - int bgp_raw = buffer_read_byte(mem_v, 0xFF47); + int bgp_raw = gfx_byte_at(mem_v, 0xFF47, __func__); if (g_has_error) return make_null(); - int obp0_raw = buffer_read_byte(mem_v, 0xFF48); + int obp0_raw = gfx_byte_at(mem_v, 0xFF48, __func__); if (g_has_error) return make_null(); - int obp1_raw = buffer_read_byte(mem_v, 0xFF49); + int obp1_raw = gfx_byte_at(mem_v, 0xFF49, __func__); if (g_has_error) return make_null(); - int wy = buffer_read_byte(mem_v, 0xFF4A); + int wy = gfx_byte_at(mem_v, 0xFF4A, __func__); if (g_has_error) return make_null(); - int wx = buffer_read_byte(mem_v, 0xFF4B); + int wx = gfx_byte_at(mem_v, 0xFF4B, __func__); if (g_has_error) return make_null(); /* Decode palettes */ @@ -2646,7 +2747,7 @@ Value* builtin_ppu_render_frame(Value *arg) { if (tile_x != prev_tile_x) { prev_tile_x = tile_x; - int tile_num = buffer_read_byte(mem_v, bg_map_base + map_row + tile_x); + int tile_num = gfx_byte_at(mem_v, bg_map_base + map_row + tile_x, __func__); if (g_has_error) return make_null(); int tile_addr; if (unsigned_mode) @@ -2655,9 +2756,9 @@ Value* builtin_ppu_render_frame(Value *arg) { tile_addr = (tile_num >= 128) ? 0x8800 + (tile_num - 128) * 16 + tile_row * 2 : 0x9000 + tile_num * 16 + tile_row * 2; - tile_lo = buffer_read_byte(mem_v, tile_addr); + tile_lo = gfx_byte_at(mem_v, tile_addr, __func__); if (g_has_error) return make_null(); - tile_hi = buffer_read_byte(mem_v, tile_addr + 1); + tile_hi = gfx_byte_at(mem_v, tile_addr + 1, __func__); if (g_has_error) return make_null(); } @@ -2688,7 +2789,7 @@ Value* builtin_ppu_render_frame(Value *arg) { int wtx = win_x >> 3; if (wtx != prev_wtx) { prev_wtx = wtx; - int wtn = buffer_read_byte(mem_v, win_map_base + win_map_row + wtx); + int wtn = gfx_byte_at(mem_v, win_map_base + win_map_row + wtx, __func__); if (g_has_error) return make_null(); int wta; if (unsigned_mode) @@ -2697,9 +2798,9 @@ Value* builtin_ppu_render_frame(Value *arg) { wta = (wtn >= 128) ? 0x8800 + (wtn - 128) * 16 + win_row * 2 : 0x9000 + wtn * 16 + win_row * 2; - wtile_lo = buffer_read_byte(mem_v, wta); + wtile_lo = gfx_byte_at(mem_v, wta, __func__); if (g_has_error) return make_null(); - wtile_hi = buffer_read_byte(mem_v, wta + 1); + wtile_hi = gfx_byte_at(mem_v, wta + 1, __func__); if (g_has_error) return make_null(); } int wbit = 7 - (win_x & 7); @@ -2718,16 +2819,16 @@ Value* builtin_ppu_render_frame(Value *arg) { for (int i = 0; i < 40 && spr_count < 10; i++) { int oam = 0xFE00 + i * 4; - int sy = buffer_read_byte(mem_v, oam) - 16; + int sy = gfx_byte_at(mem_v, oam, __func__) - 16; if (g_has_error) return make_null(); - int sx = buffer_read_byte(mem_v, oam + 1) - 8; + int sx = gfx_byte_at(mem_v, oam + 1, __func__) - 8; if (g_has_error) return make_null(); if (ly >= sy && ly < sy + sprite_h) { sprites[spr_count].sx = sx; sprites[spr_count].sy = sy; - sprites[spr_count].tile = buffer_read_byte(mem_v, oam + 2); + sprites[spr_count].tile = gfx_byte_at(mem_v, oam + 2, __func__); if (g_has_error) return make_null(); - sprites[spr_count].flags = buffer_read_byte(mem_v, oam + 3); + sprites[spr_count].flags = gfx_byte_at(mem_v, oam + 3, __func__); if (g_has_error) return make_null(); sprites[spr_count].oam_idx = i; spr_count++; @@ -2764,9 +2865,9 @@ Value* builtin_ppu_render_frame(Value *arg) { } int taddr = 0x8000 + tile * 16 + row * 2; - int slo = buffer_read_byte(mem_v, taddr); + int slo = gfx_byte_at(mem_v, taddr, __func__); if (g_has_error) return make_null(); - int shi = buffer_read_byte(mem_v, taddr + 1); + int shi = gfx_byte_at(mem_v, taddr + 1, __func__); if (g_has_error) return make_null(); for (int col = 0; col < 8; col++) { diff --git a/src/ext_http.c b/src/ext_http.c index ccfda9242..c5526be02 100644 --- a/src/ext_http.c +++ b/src/ext_http.c @@ -586,25 +586,37 @@ Value* builtin_http_early_bind(Value *arg) { } arg = list_get_view(arg, 0, &port_view); } - if (arg && arg->type != VAL_NULL && arg->type != VAL_NUM) { + if (arg && arg->type != VAL_NULL && !val_is_number(arg)) { rt_error(EK_TYPE, 0, "http_early_bind: port must be a number or null"); return make_null(); } + int64_t port_value = 5000; + if (arg && arg->type != VAL_NULL + && !eigs_int_arg_range(arg, 0, 65535, &port_value, "http_early_bind")) return make_null(); + int port = (int)port_value; if (g_server.early_bind_fd >= 0 || g_server.serving || g_server.response_header_rejected) { rt_error(EK_VALUE, 0, "http_early_bind: server already bound/serving or http_response_header rejected"); return make_null(); } - int port = 5000; - double port_d = 0; - if (eigs_opt_num(arg, &port_d, "http_serve")) port = (int)port_d; /* #1637: a bool raises */ - if (g_has_error) return make_null(); + const char *env_port = getenv("PORT"); - if (env_port && atoi(env_port) > 0) { - port = atoi(env_port); + char *port_end = NULL; + errno = 0; + long override = env_port ? strtol(env_port, &port_end, 10) : 0; + if (env_port && port_end != env_port && override > 0) { + if (errno == ERANGE || override > 65535) { + rt_error(EK_VALUE, 0, "http_early_bind: PORT outside [0, 65535]"); + return make_null(); + } + port = (int)override; printf("[deploy] PORT env=%s, binding port %d\n", env_port, port); } else { printf("[deploy] No PORT env, using default %d\n", port); } + if (port < 0 || port > 65535) { + rt_error(EK_VALUE, 0, "http_early_bind: port outside [0, 65535]"); + return make_null(); + } char cwd[512]; if (getcwd(cwd, sizeof(cwd))) { printf("[deploy] cwd=%s\n", cwd); @@ -651,6 +663,25 @@ Value* builtin_http_early_bind(Value *arg) { } Value* builtin_http_serve(Value *arg) { + int64_t port_value = 5000; + if (arg && arg->type != VAL_NULL + && !eigs_int_arg_range(arg, 0, 65535, &port_value, "http_serve")) return make_null(); + int port = (int)port_value; + const char *env_port = getenv("PORT"); + char *port_end = NULL; + errno = 0; + long override = env_port ? strtol(env_port, &port_end, 10) : 0; + if (env_port && port_end != env_port && override > 0) { + if (errno == ERANGE || override > 65535) { + rt_error(EK_VALUE, 0, "http_serve: PORT outside [0, 65535]"); + return make_null(); + } + port = (int)override; + } + if (port < 0 || port > 65535) { + rt_error(EK_VALUE, 0, "http_serve: port outside [0, 65535]"); + return make_null(); + } if (__builtin_expect(g_replay_enabled, 0)) { rt_error(EK_IO, 0, "http_serve: not replayable under EIGS_REPLAY " "(server/static-file boundary; see docs/TRACE.md)"); @@ -667,14 +698,7 @@ Value* builtin_http_serve(Value *arg) { rt_error(EK_VALUE, 0, "http_response_header: rejected configuration; http_serve cannot start"); return make_null(); } - int port = 5000; - double port_d = 0; - if (eigs_opt_num(arg, &port_d, "http_serve")) port = (int)port_d; /* #1637: a bool raises */ - if (g_has_error) return make_null(); - const char *env_port = getenv("PORT"); - if (env_port && atoi(env_port) > 0) { - port = atoi(env_port); - } + printf("Starting HTTP server on port %d...\n", port); fflush(stdout); http_serve_blocking(port); @@ -951,7 +975,8 @@ Value* builtin_shared_set(Value *arg) { * as 0 (so the first incr inserts the key with `delta` as its value); * an existing non-numeric value is a usage error, returns null without * mutating. Subject to the same byte cap as shared_set. Returns the new - * value on success, null on bad args / over-cap / type mismatch. */ + * value on success, null on bad args / over-cap / type mismatch. Ints add + * exactly and int64 overflow raises without mutating the counter (A). */ Value* builtin_shared_incr(Value *arg) { STRICT_LIST_MAX(arg, 2, "shared_incr"); ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, @@ -959,39 +984,60 @@ Value* builtin_shared_incr(Value *arg) { Value *key_v = list_get_view(arg, 0, &(EigsView){{0}}); Value *delta_v = list_get_view(arg, 1, &(EigsView){{0}}); ARG_GUARD(key_v->type != VAL_STR, "shared_incr", "a string key", make_null()); - ARG_GUARD(delta_v->type != VAL_NUM, "shared_incr", "a number delta", make_null()); + ARG_GUARD(!val_is_number(delta_v), "shared_incr", "a number delta", make_null()); Server *s = eigs_http_active; if (!s) return make_null(); - double delta = eigs_num_arg(delta_v, __func__); + EigsNum delta = eigs_number_arg(delta_v, __func__); + if (g_has_error) return make_null(); long cap = shared_max_bytes(); pthread_mutex_lock(&s->shared_mu); int idx = shared_find(s, key_v->data.str); - double cur = 0; + EigsNum cur = { .is_int = 1, .value = { .i64 = 0 } }; if (idx >= 0) { int pos = 0; Value *parsed = eigs_json_parse_root(s->shared[idx].json, &pos); /* #777 */ /* Read the number out BEFORE dropping the ref — decref may free it — * and drop it on the mismatch path too, which is the one an early * return makes easy to miss. */ - int bad = (!parsed || parsed->type != VAL_NUM); - if (!bad) cur = eigs_num_arg(parsed, __func__); + int bad = (!parsed || !val_is_number(parsed)); + if (!bad) cur = eigs_number_arg(parsed, __func__); val_decref(parsed); - if (bad) { + if (bad || g_has_error) { + pthread_mutex_unlock(&s->shared_mu); + return make_null(); + } + } + Value *new_v; + if (cur.is_int && delta.is_int) { + int64_t new_val; + if (__builtin_add_overflow(cur.value.i64, delta.value.i64, &new_val)) { pthread_mutex_unlock(&s->shared_mu); + rt_error(EK_VALUE, 0, "shared_incr: integer overflow in '+'"); return make_null(); } + new_v = make_int(new_val); + } else { + double cur_f = cur.is_int ? (double)cur.value.i64 : cur.value.f64; + double delta_f = delta.is_int ? (double)delta.value.i64 : delta.value.f64; + new_v = make_float(cur_f + delta_f); + } + if (g_has_error) { + val_decref(new_v); + pthread_mutex_unlock(&s->shared_mu); + return make_null(); } - double new_val = cur + delta; - /* eigs_json_encode borrows its argument, so the Value handed to it is - * ours to release. */ - Value *new_v = make_num(new_val); + /* eigs_json_encode borrows new_v. Keep its owned ref for the return value + * on success and release it on every refusal before leaving the lock. */ char *new_json = eigs_json_encode(new_v); - val_decref(new_v); /* A number can't exceed the depth bound, but don't leave a bare strlen on * a documented-nullable return. */ - if (!new_json) { pthread_mutex_unlock(&s->shared_mu); return make_null(); } + if (!new_json) { + val_decref(new_v); + pthread_mutex_unlock(&s->shared_mu); + return make_null(); + } long new_json_len = (long)strlen(new_json); long key_len = (long)val_str_len(key_v); long old_bytes = (idx >= 0) ? (long)strlen(s->shared[idx].json) : 0; @@ -1000,6 +1046,7 @@ Value* builtin_shared_incr(Value *arg) { if (s->shared_bytes + delta_bytes > cap) { pthread_mutex_unlock(&s->shared_mu); free(new_json); + val_decref(new_v); return make_null(); } if (idx >= 0) { @@ -1017,7 +1064,7 @@ Value* builtin_shared_incr(Value *arg) { } s->shared_bytes += delta_bytes; pthread_mutex_unlock(&s->shared_mu); - return make_num(new_val); + return new_v; } Value* builtin_shared_get(Value *arg) { @@ -1046,9 +1093,9 @@ Value* builtin_shared_has(Value *arg) { } Value* builtin_shared_delete(Value *arg) { - ARG_GUARD(!arg || arg->type != VAL_STR, "shared_delete", "a string key", make_num(0)); + ARG_GUARD(!arg || arg->type != VAL_STR, "shared_delete", "a string key", make_int(0)); Server *s = eigs_http_active; - if (!s) return make_num(0); /* fs:ANSWER 0 means "nothing removed" -- the same value line 761 returns via `removed` when the key is absent */ + if (!s) return make_int(0); /* fs:ANSWER 0 means "nothing removed" -- the same value line 761 returns via `removed` when the key is absent */ pthread_mutex_lock(&s->shared_mu); int idx = shared_find(s, arg->data.str); int removed = 0; @@ -1064,7 +1111,7 @@ Value* builtin_shared_delete(Value *arg) { removed = 1; } pthread_mutex_unlock(&s->shared_mu); - return make_num(removed); + return make_int(removed); } Value* builtin_shared_keys(Value *arg) { @@ -1083,11 +1130,11 @@ Value* builtin_shared_keys(Value *arg) { Value* builtin_shared_size(Value *arg) { (void)arg; Server *s = eigs_http_active; - if (!s) return make_num(0); /* fs:ANSWER 0 is the store's key count, the same quantity line 784 returns; with no active server the store is empty */ + if (!s) return make_int(0); /* fs:ANSWER 0 is the store's key count, the same quantity line 784 returns; with no active server the store is empty */ pthread_mutex_lock(&s->shared_mu); int n = s->shared_count; pthread_mutex_unlock(&s->shared_mu); - return make_num(n); + return make_int(n); } Value* builtin_shared_clear(Value *arg) { diff --git a/src/ext_net.c b/src/ext_net.c index def69a3fb..63d713288 100644 --- a/src/ext_net.c +++ b/src/ext_net.c @@ -74,16 +74,15 @@ static Value* net_bytes_to_buffer(const unsigned char *bytes, int n) { * opens named a different connection's fd and read/wrote it silently. The * packing is `slot + gen * HANDLE_TABLE_SIZE`: slot is 1..255, gen starts at * 1, so every live handle is >= 257 and a bare small integer (a forged or - * hand-computed id) decodes to gen 0, which no live slot has. Doubles carry - * 53 bits exactly, so the packed form is exact until a single slot has been - * reused 2^45 times. */ -static double net_pack(int idx, uint32_t gen) { - return (double)idx + (double)gen * (double)HANDLE_TABLE_SIZE; + * hand-computed id) decodes to gen 0, which no live slot has. The packed + * integer fits int64 for every uint32 generation. */ +static int64_t net_pack(int idx, uint32_t gen) { + return (int64_t)idx + (int64_t)gen * HANDLE_TABLE_SIZE; } -static EigsNetSock* net_unpack(double packed, int *out_idx, uint32_t *out_gen) { - if (!(packed > 0) || packed > 9007199254740992.0) return NULL; - long long p = (long long)packed; +static EigsNetSock* net_unpack(int64_t packed, int *out_idx, uint32_t *out_gen) { + if (packed <= 0 || packed / HANDLE_TABLE_SIZE > UINT32_MAX) return NULL; + int64_t p = packed; int idx = (int)(p % HANDLE_TABLE_SIZE); uint32_t gen = (uint32_t)(p / HANDLE_TABLE_SIZE); if (out_idx) *out_idx = idx; @@ -91,7 +90,7 @@ static EigsNetSock* net_unpack(double packed, int *out_idx, uint32_t *out_gen) { return (EigsNetSock*)handle_lookup(idx, gen, HANDLE_NET, NULL); } -static double net_register_sock(int fd, EigsNetSockKind kind) { +static int64_t net_register_sock(int fd, EigsNetSockKind kind) { EigsNetSock *s = xmalloc(sizeof(EigsNetSock)); s->fd = fd; s->kind = kind; @@ -103,14 +102,13 @@ static double net_register_sock(int fd, EigsNetSockKind kind) { rt_error(EK_LIMIT, 0, "net: handle table full (max %d live threads/channels/" "tasks/sockets)", HANDLE_TABLE_SIZE - 1); - return -1.0; + return -1; } return net_pack(id, gen); } -static EigsNetSock* net_lookup(Value *v, EigsNetSockKind kind) { - if (!v || v->type != VAL_NUM) return NULL; - EigsNetSock *s = net_unpack(eigs_num_arg(v, __func__), NULL, NULL); +static EigsNetSock* net_lookup(int64_t handle, EigsNetSockKind kind) { + EigsNetSock *s = net_unpack(handle, NULL, NULL); if (!s || s->kind != kind) return NULL; return s; } @@ -139,13 +137,22 @@ static int net_require_list(Value *arg, int min, int max, const char *fn) { return 1; } -static int net_num_at(Value *arg, int idx, int fallback) { - if (!arg || arg->type != VAL_LIST || idx >= arg->data.list.count) - return fallback; - /* #1637: null keeps the fallback, a number is read, anything else (a - * bool) raises; callers check g_has_error after their reads. */ - double d = 0; - return eigs_slot_opt_num(list_slot(arg, idx), &d, "net") ? (int)d : fallback; +/* Optional integer counts and continuous poll durations have different + * contracts. Both validate before a tape take or any socket operation. */ +static int net_count_at(Value *arg, int idx, int fallback, const char *who) { + if (idx >= arg->data.list.count) return fallback; + int64_t value = fallback; + eigs_opt_int_range(list_get_view(arg, idx, &(EigsView){{0}}), + INT_MIN, INT_MAX, &value, who); + return (int)value; +} + +static int net_timeout_at(Value *arg, int idx, const char *who) { + if (idx >= arg->data.list.count) return -1; + int64_t value = -1; + eigs_opt_magnitude(list_get_view(arg, idx, &(EigsView){{0}}), + INT_MIN, INT_MAX, &value, who); + return (int)value; } /* ---- builtins ----------------------------------------------------- */ @@ -155,14 +162,13 @@ static int net_num_at(Value *arg, int idx, int fallback) { * ephemeral port — read it back with net_port. Binds 0.0.0.0, matching * ext_http's server posture (SECURITY.md: the script is trusted). */ Value* builtin_net_listen(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "net_listen: expected a port number"); return make_null(); } + int64_t port; + if (!eigs_int_arg_range(arg, 0, 65535, &port, "net_listen")) return make_null(); TRACE_NONDET_TAKE("net_listen"); - int port = (int)eigs_num_arg(arg, __func__); - if (port < 0 || port > 65535) - TRACE_NONDET_RECORD("net_listen", make_null()); int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) TRACE_NONDET_RECORD("net_listen", make_null()); int one = 1; @@ -176,27 +182,29 @@ Value* builtin_net_listen(Value *arg) { close(fd); TRACE_NONDET_RECORD("net_listen", make_null()); } - double id = net_register_sock(fd, NET_SOCK_LISTENER); + int64_t id = net_register_sock(fd, NET_SOCK_LISTENER); if (id < 0) TRACE_NONDET_RECORD("net_listen", make_null()); - TRACE_NONDET_RECORD("net_listen", make_num(id)); + TRACE_NONDET_RECORD("net_listen", make_int(id)); } /* net_port of listener_id → the locally bound port (the kernel's pick * for a port-0 listen), or null on a bad/closed handle. Kernel-chosen, * so it rides the tape like every other environment outcome. */ Value* builtin_net_port(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "net_port: expected a listener handle"); return make_null(); } + int64_t handle = eigs_int_arg(arg, "net_port"); + if (g_has_error) return make_null(); TRACE_NONDET_TAKE("net_port"); - EigsNetSock *s = net_lookup(arg, NET_SOCK_LISTENER); + EigsNetSock *s = net_lookup(handle, NET_SOCK_LISTENER); if (!s) TRACE_NONDET_RECORD("net_port", make_null()); struct sockaddr_in addr; socklen_t len = sizeof(addr); if (getsockname(s->fd, (struct sockaddr*)&addr, &len) < 0) TRACE_NONDET_RECORD("net_port", make_null()); - TRACE_NONDET_RECORD("net_port", make_num(ntohs(addr.sin_port))); + TRACE_NONDET_RECORD("net_port", make_int(ntohs(addr.sin_port))); } /* net_accept of listener_id @@ -208,25 +216,27 @@ Value* builtin_net_accept(Value *arg) { int timeout_ms = -1; if (arg && arg->type == VAL_LIST) { if (!net_require_list(arg, 2, 2, "net_accept")) return make_null(); - timeout_ms = net_num_at(arg, 1, -1); + timeout_ms = net_timeout_at(arg, 1, "net_accept"); if (g_has_error) return make_null(); /* #1637: a bool timeout raised */ arg = list_get_view(arg, 0, &listener_view); } - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "net_accept: expected a listener handle"); return make_null(); } + int64_t handle = eigs_int_arg(arg, "net_accept"); + if (g_has_error) return make_null(); TRACE_NONDET_TAKE("net_accept"); - EigsNetSock *s = net_lookup(arg, NET_SOCK_LISTENER); + EigsNetSock *s = net_lookup(handle, NET_SOCK_LISTENER); if (!s) TRACE_NONDET_RECORD("net_accept", make_null()); if (!net_wait_readable(s->fd, timeout_ms)) TRACE_NONDET_RECORD("net_accept", make_null()); int cfd; do { cfd = accept(s->fd, NULL, NULL); } while (cfd < 0 && errno == EINTR); if (cfd < 0) TRACE_NONDET_RECORD("net_accept", make_null()); - double id = net_register_sock(cfd, NET_SOCK_CONN); + int64_t id = net_register_sock(cfd, NET_SOCK_CONN); if (id < 0) TRACE_NONDET_RECORD("net_accept", make_null()); - TRACE_NONDET_RECORD("net_accept", make_num(id)); + TRACE_NONDET_RECORD("net_accept", make_int(id)); } /* net_dial of [host, port] @@ -241,14 +251,14 @@ Value* builtin_net_dial(Value *arg) { rt_error(EK_TYPE, 0, "net_dial: expected [host, port] with a string host"); return make_null(); } - int port = net_num_at(arg, 1, -1); - int timeout_ms = net_num_at(arg, 2, -1); + int64_t port; + if (!eigs_list_int_range(arg, 1, 0, 65535, &port, "net_dial")) return make_null(); + int timeout_ms = net_timeout_at(arg, 2, "net_dial"); if (g_has_error) return make_null(); /* #1637: before the tape boundary */ TRACE_NONDET_TAKE("net_dial"); - if (port < 0 || port > 65535) TRACE_NONDET_RECORD("net_dial", make_null()); char portstr[8]; - snprintf(portstr, sizeof(portstr), "%d", port); + snprintf(portstr, sizeof(portstr), "%d", (int)port); struct addrinfo hints = {0}, *res = NULL; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -277,9 +287,9 @@ Value* builtin_net_dial(Value *arg) { TRACE_NONDET_RECORD("net_dial", make_null()); } fcntl(fd, F_SETFL, flags); - double id = net_register_sock(fd, NET_SOCK_CONN); + int64_t id = net_register_sock(fd, NET_SOCK_CONN); if (id < 0) TRACE_NONDET_RECORD("net_dial", make_null()); - TRACE_NONDET_RECORD("net_dial", make_num(id)); + TRACE_NONDET_RECORD("net_dial", make_int(id)); } /* net_recv of [conn_id, max_bytes] @@ -291,8 +301,10 @@ Value* builtin_net_dial(Value *arg) { Value* builtin_net_recv(Value *arg) { if (!net_require_list(arg, 2, 3, "net_recv")) return make_null(); Value *conn = list_get_view(arg, 0, &(EigsView){{0}}); - int max = net_num_at(arg, 1, 0); - int timeout_ms = net_num_at(arg, 2, -1); + int64_t handle = eigs_int_arg(conn, "net_recv"); + if (g_has_error) return make_null(); + int max = net_count_at(arg, 1, 0, "net_recv"); + int timeout_ms = net_timeout_at(arg, 2, "net_recv"); if (g_has_error) return make_null(); /* #1637 */ /* A zero-byte recv() returns 0 — indistinguishable from EOF — so a * senseless max is a shape error, decided before the tape boundary. */ @@ -301,7 +313,7 @@ Value* builtin_net_recv(Value *arg) { return make_null(); } TRACE_NONDET_TAKE("net_recv"); - EigsNetSock *s = net_lookup(conn, NET_SOCK_CONN); + EigsNetSock *s = net_lookup(handle, NET_SOCK_CONN); if (!s) TRACE_NONDET_RECORD("net_recv", make_null()); if (max > NET_RECV_MAX) max = NET_RECV_MAX; if (!net_wait_readable(s->fd, timeout_ms)) @@ -321,18 +333,18 @@ Value* builtin_net_recv(Value *arg) { * for proc_write). */ /* Borrow the already converted bytes. The caller owns any workspace across * the early replay return as well as every live send outcome. */ -static Value* net_send_bytes(Value *conn, const unsigned char *bytes, size_t len) { +static Value* net_send_bytes(int64_t handle, const unsigned char *bytes, size_t len) { TRACE_NONDET_TAKE("net_send"); - EigsNetSock *s = net_lookup(conn, NET_SOCK_CONN); - if (!s) TRACE_NONDET_RECORD("net_send", make_num(-1)); + EigsNetSock *s = net_lookup(handle, NET_SOCK_CONN); + if (!s) TRACE_NONDET_RECORD("net_send", make_int(-1)); size_t sent = 0; while (sent < len) { ssize_t n = send(s->fd, bytes + sent, len - sent, MSG_NOSIGNAL); if (n < 0 && errno == EINTR) continue; - if (n <= 0) TRACE_NONDET_RECORD("net_send", make_num(-1)); + if (n <= 0) TRACE_NONDET_RECORD("net_send", make_int(-1)); sent += (size_t)n; } - TRACE_NONDET_RECORD("net_send", make_num((double)sent)); + TRACE_NONDET_RECORD("net_send", make_int((int64_t)sent)); } Value* builtin_net_send(Value *arg) { @@ -344,6 +356,8 @@ Value* builtin_net_send(Value *arg) { rt_error(EK_TYPE, 0, "net_send: data must be a string, buffer, or byte list"); return make_null(); } + int64_t handle = eigs_int_arg(conn, "net_send"); + if (g_has_error) return make_null(); /* Read errors are deterministic argument errors: stop before the tape * boundary in both capture and replay, before any socket work. */ const unsigned char *bytes; @@ -357,17 +371,21 @@ Value* builtin_net_send(Value *arg) { : data->data.list.count; owned = xmalloc(n > 0 ? (size_t)n : 1); for (int i = 0; i < n; i++) { - double dv = data->type == VAL_BUFFER - ? buffer_read_num(data, i) - : eigs_slot_elem_num(list_slot(data, i), "net_send"); /* #1637 round 4 */ - if (g_has_error) { free(owned); return make_null(); } - owned[i] = finite_num_to_byte(dv); + if (data->type == VAL_BUFFER) { + if (!buffer_byte_at(data, i, &owned[i], "net_send")) { + free(owned); + return make_null(); + } + } else { + owned[i] = eigs_elem_byte(list_get_view(data, i, &(EigsView){{0}}), "net_send"); + if (g_has_error) { free(owned); return make_null(); } + } } bytes = owned; len = (size_t)n; } - Value *result = net_send_bytes(conn, bytes, len); + Value *result = net_send_bytes(handle, bytes, len); free(owned); return result; } @@ -376,13 +394,15 @@ Value* builtin_net_send(Value *arg) { * idempotent bookkeeping, and under EIGS_REPLAY no handle exists so it * is a natural no-op (the audio_capture_close shape). */ Value* builtin_net_close(Value *arg) { - if (!arg || arg->type != VAL_NUM) { + if (!arg || !val_is_number(arg)) { rt_error(EK_TYPE, 0, "net_close: expected a socket handle"); return make_null(); } int nidx = 0; uint32_t ngen = 0; - EigsNetSock *s = net_unpack(eigs_num_arg(arg, __func__), &nidx, &ngen); + int64_t handle = eigs_int_arg(arg, "net_close"); + if (g_has_error) return make_null(); + EigsNetSock *s = net_unpack(handle, &nidx, &ngen); if (s) { close(s->fd); free(s); diff --git a/src/ext_store.c b/src/ext_store.c index a5e6cf12b..ca7d4abf5 100644 --- a/src/ext_store.c +++ b/src/ext_store.c @@ -6,6 +6,7 @@ #include "ext_store_internal.h" #include "builtins_internal.h" #include "trace.h" +#include #if EIGENSCRIPT_FREESTANDING /* The page store is file-backed end to end — carved out of the @@ -154,14 +155,14 @@ static int store_nonfinite_sentinel(const char *s, double *out) { return 0; } -/* Buffer elements use the 17-significant-digit form, as trace.c's buffer tape - * encoding does: %.17g is the shortest width that round-trips a double - * bit-exactly, and a buffer holding samples or weights would otherwise come - * back subtly altered. (VAL_NUM's own %d/%.15g encoding is untouched.) */ +/* N6/N14: all finite record numbers share the one shortest writer. + * The existing non-finite buffer sentinels stay strings until N13. */ static void store_json_encode_buf_elem(strbuf *out, double d) { if (isnan(d)) { strbuf_append(out, "\"nan\""); return; } if (isinf(d)) { strbuf_append(out, d < 0 ? "\"-inf\"" : "\"inf\""); return; } - strbuf_append_fmt(out, "%.17g", d); + char nb[32]; + eigs_num_text(nb, sizeof nb, d); + strbuf_append(out, nb); } static void store_json_encode(Value *v, strbuf *out); @@ -171,12 +172,20 @@ static void store_json_encode(Value *v, strbuf *out); * inline number; format it through the same branch and format strings used by * the historical boxed-number path so persisted bytes remain identical. */ static void store_json_encode_slot(EigsSlot slot, strbuf *out) { - if (slot_is_num(slot)) { - double n = eigs_slot_num(slot, __func__); - if (fabs(n) < 2147483648.0 && n == (int)n) - strbuf_append_fmt(out, "%d", (int)n); - else - strbuf_append_fmt(out, "%.15g", n); + if (slot_is_f64(slot)) { + /* This is persisted float data, not a finite builtin argument. + * Keep the existing NaN/inf spelling until N13 (R2.3). */ + double n = SLOT_F64_RAW(slot); + char nb[32]; + eigs_num_text(nb, sizeof nb, n); + strbuf_append(out, nb); + return; + } + if (slot_is_smi(slot)) { + /* #1758: an int is written as its exact digits (design 7). */ + char nb[24]; + eigs_int_text(nb, sizeof nb, slot_smi_val(slot)); + strbuf_append(out, nb); return; } store_json_encode(eigs_slot_ref(slot), out); @@ -188,18 +197,19 @@ static void store_json_encode(Value *v, strbuf *out) { return; } switch (v->type) { - case VAL_NUM: { - double n = eigs_num_arg(v, __func__); - /* #816: magnitude BEFORE the narrowing cast (same class as - * #695) — converting a double beyond int's range is UB, and - * the old `n == (int)n && fabs(n) < 1e15` order ran the cast - * first. The bound is int's own range: integral values beyond - * it never took the %d path anyway (the equality failed), so - * output is unchanged and the cast is now always defined. */ - if (fabs(n) < 2147483648.0 && n == (int)n) - strbuf_append_fmt(out, "%d", (int)n); - else - strbuf_append_fmt(out, "%.15g", n); + case VAL_FLOAT: { + /* The switch proves VAL_FLOAT; serialization also accepts the + * historical non-finite payloads until N13 (R2.3). */ + double n = VAL_F64_RAW(v); + char nb[32]; + eigs_num_text(nb, sizeof nb, n); + strbuf_append(out, nb); + break; + } + case VAL_INT: { /* #1758 R1.2a: an int encodes as its exact digits */ + char nb[24]; + eigs_int_text(nb, sizeof(nb), VAL_I64_RAW(v)); + strbuf_append(out, nb); break; } case VAL_STR: { @@ -319,11 +329,23 @@ static Value* store_json_parse_string(const char *s, int *pos) { } static Value* store_json_parse_number(const char *s, int *pos) { + const char *start = s + *pos; char *end = NULL; - double val = strtod(s + *pos, &end); - if (end == s + *pos) return NULL; /* no digits consumed */ + double val = strtod(start, &end); + if (end == start) return NULL; /* no digits consumed */ *pos = (int)(end - s); - return make_num(val); + + /* #1758 N14: decimal integer text within int64 decodes exactly. A + * decimal point or exponent keeps the float kind even for a whole value. + * Keep strtod's existing token span and float fallback (including the + * guard in make_float); float text itself changes in R1.7, not here. */ + char *int_end = NULL; + errno = 0; + long long integer = strtoll(start, &int_end, 10); + if (int_end == end && errno != ERANGE && + integer >= INT64_MIN && integer <= INT64_MAX) + return make_int((int64_t)integer); + return make_float(val); } static Value* store_json_parse_array(const char *s, int *pos) { @@ -359,21 +381,21 @@ static Value* store_buffer_from_tag(Value *dict) { Value *rv = list_get_view(shape, 0, &(EigsView){{0}}); Value *cv = list_get_view(shape, 1, &(EigsView){{0}}); - if (!rv || rv->type != VAL_NUM || !cv || cv->type != VAL_NUM) return NULL; - - /* Range-gate BEFORE narrowing. A stored shape is whatever double the file - * holds, and (int)d is undefined when d is outside int's range (C11 - * 6.3.1.4p1) — reachable here with e.g. [1e300, 0]. The negated form also - * rejects a NaN, which fails every comparison. Only once the value is - * known in-range is the exact `==` below meaningful: it is an INTEGRALITY - * test (is this dimension a whole number?), so exact equality is the - * correct operator and a tolerance would be the bug — 2.0000000000000004 - * is not a row count. This also establishes rows/cols >= 0. */ - double rd = eigs_num_arg(rv, __func__), cd = eigs_num_arg(cv, __func__); - if (!(rd >= 0 && rd <= (double)INT_MAX)) return NULL; - if (!(cd >= 0 && cd <= (double)INT_MAX)) return NULL; - int rows = (int)rd, cols = (int)cd; - if (rd != (double)rows || cd != (double)cols) return NULL; + if (!rv || !val_is_number(rv) || !cv || !val_is_number(cv)) return NULL; + + /* This is an untrusted codec tag, so an invalid dimension leaves it a + * user dict, as before. Read integer text without passing through double; + * float dimensions must convert exactly before the C int range check. */ + EigsNum rn = eigs_number_arg(rv, __func__); + EigsNum cn = eigs_number_arg(cv, __func__); + if (g_has_error) return NULL; + int64_t ri, ci; + if (rn.is_int) ri = rn.value.i64; + else if (!num_to_i64_exact(rn.value.f64, &ri)) return NULL; + if (cn.is_int) ci = cn.value.i64; + else if (!num_to_i64_exact(cn.value.f64, &ci)) return NULL; + if (ri < 0 || ri > INT_MAX || ci < 0 || ci > INT_MAX) return NULL; + int rows = (int)ri, cols = (int)ci; /* Accept exactly the (rows, cols, count) triples the buffer constructors * can produce, no more. rows>0 is a real 2-D shape, so rows*cols must be @@ -389,7 +411,7 @@ static Value* store_buffer_from_tag(Value *dict) { for (int i = 0; i < count; i++) { Value *e = list_get_view(body, i, &(EigsView){{0}}); if (!e) return NULL; - if (e->type == VAL_NUM) continue; + if (val_is_number(e)) continue; if (e->type == VAL_STR && store_nonfinite_sentinel(e->data.str, NULL)) continue; return NULL; } @@ -404,7 +426,7 @@ static Value* store_buffer_from_tag(Value *dict) { for (int i = 0; i < count; i++) { Value *e = list_get_view(body, i, &(EigsView){{0}}); double d = 0; - if (e->type == VAL_NUM) d = eigs_num_arg(e, __func__); + if (val_is_number(e)) d = eigs_float_arg(e, __func__); else store_nonfinite_sentinel(e->data.str, &d); buf->data.buffer.data[i] = d; } @@ -742,12 +764,17 @@ static int store_load_catalog(Store *store) { } Value *rv = store_dict_get_view(col, "root", &(EigsView){{0}}); Value *nv = store_dict_get_view(col, "next_id", &(EigsView){{0}}); - if (!rv || rv->type != VAL_NUM || !nv || nv->type != VAL_NUM) { + if (!rv || !val_is_number(rv) || !nv || !val_is_number(nv)) { fprintf(stderr, "store_open: collection '%s' missing root or next_id\n", name); free(visited); return -1; } - uint32_t root = (uint32_t)eigs_num_arg(rv, __func__); + int64_t root_id; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &root_id, "store_open")) { + free(visited); + return -1; + } + uint32_t root = (uint32_t)root_id; if (root == 0 || root >= store->page_count) { fprintf(stderr, "store_open: collection '%s' has invalid root %u\n", name, root); free(visited); @@ -809,15 +836,21 @@ static int store_load_catalog(Store *store) { * store's slot; a forged handle (tests/test_handle_forge.eigs) carries no * generation and reads as gen 0, which no live slot ever has. `out_id` is set * even on failure so the refusal can name the slot. */ -static Store* get_store_why(Value *v, int *why, int *out_id) { +/* R1.6f-D1: this reader owns all nine store_arg callers, including the + * already-b key/record operations. Refuse numeric fields before handle lookup. */ +static Store* get_store_why(Value *v, int *why, int *out_id, const char *who) { *why = HANDLE_CLAIM_BADVALUE; *out_id = 0; if (!v || v->type != VAL_DICT) return NULL; Value *sv = store_dict_get_view(v, "_store_id", &(EigsView){{0}}); - if (!sv || sv->type != VAL_NUM) return NULL; - *out_id = (int)eigs_num_arg(sv, __func__); + if (!sv || !val_is_number(sv)) return NULL; + int64_t id, generation = 0; + if (!eigs_int_arg_range(sv, INT_MIN, INT_MAX, &id, who)) return NULL; + *out_id = (int)id; Value *gv = store_dict_get_view(v, "_store_gen", &(EigsView){{0}}); - uint32_t gen = (gv && gv->type == VAL_NUM) ? (uint32_t)eigs_num_arg(gv, __func__) : 0; + if (gv && val_is_number(gv) + && !eigs_int_arg_range(gv, 0, UINT32_MAX, &generation, who)) return NULL; + uint32_t gen = (uint32_t)generation; return (Store*)handle_lookup(*out_id, gen, HANDLE_STORE, why); } @@ -831,8 +864,8 @@ static Store* get_store_why(Value *v, int *why, int *out_id) { * tenth cannot re-open the hole by forgetting to check. */ static Store* store_arg(Value *v, const char *who) { int why = 0, id = 0; - Store *st = get_store_why(v, &why, &id); - if (!st) handle_raise_unresolved(who, "store", id, why, "closed"); + Store *st = get_store_why(v, &why, &id, who); + if (!st && !g_has_error) handle_raise_unresolved(who, "store", id, why, "closed"); return st; } @@ -955,8 +988,8 @@ static Value* builtin_store_open(Value *arg) { return make_null(); } Value *handle = make_dict(8); - dict_set_owned(handle, "_store_id", make_num((double)hid)); - dict_set_owned(handle, "_store_gen", make_num((double)sgen)); + dict_set_owned(handle, "_store_id", make_int(hid)); + dict_set_owned(handle, "_store_gen", make_int(sgen)); dict_set_owned(handle, "_type", make_str("eigenstore")); return handle; } @@ -969,10 +1002,12 @@ static Value* builtin_store_close(Value *arg) { /* Release handle BEFORE freeing memory to prevent use-after-free * if another thread calls get_store() concurrently. */ Value *id_val = (arg && arg->type == VAL_DICT) ? store_dict_get_view(arg, "_store_id", &(EigsView){{0}}) : NULL; - int hid = (id_val && id_val->type == VAL_NUM) ? (int)eigs_num_arg(id_val, __func__) : -1; + int64_t hid; + if (!eigs_int_arg_range(id_val, INT_MIN, INT_MAX, &hid, "store_close")) return make_null(); Value *g_val = (arg && arg->type == VAL_DICT) ? store_dict_get_view(arg, "_store_gen", &(EigsView){{0}}) : NULL; - uint32_t hgen = (g_val && g_val->type == VAL_NUM) ? (uint32_t)eigs_num_arg(g_val, __func__) : 0; - handle_release(hid, hgen); + int64_t hgen; + if (!eigs_int_arg_range(g_val, 0, UINT32_MAX, &hgen, "store_close")) return make_null(); + handle_release((int)hid, (uint32_t)hgen); store->dirty = 1; /* Force flush */ store_flush_catalog(store); fclose(store->fp); @@ -1017,8 +1052,8 @@ static Value* builtin_store_put(Value *arg) { root_page = store_alloc_page(store); next_id = 1; col_info = make_dict(4); - dict_set_owned(col_info, "root", make_num(root_page)); - dict_set_owned(col_info, "next_id", make_num(next_id)); + dict_set_owned(col_info, "root", make_int(root_page)); + dict_set_owned(col_info, "next_id", make_int(next_id)); dict_set_owned(store->catalog, collection, col_info); /* adopt make_dict ref */ store->dirty = 1; @@ -1032,8 +1067,13 @@ static Value* builtin_store_put(Value *arg) { } else { Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); Value *nv = store_dict_get_view(col_info, "next_id", &(EigsView){{0}}); - root_page = (uint32_t)(rv ? eigs_num_arg(rv, __func__) : 0); - next_id = (int)(nv ? eigs_num_arg(nv, __func__) : 1); + int64_t root = 0, next = 1; + if (rv && !eigs_int_arg_range(rv, 0, UINT32_MAX, &root, "store_put")) + return make_null(); + if (nv && !eigs_int_arg_range(nv, INT_MIN, INT_MAX, &next, "store_put")) + return make_null(); + root_page = (uint32_t)root; + next_id = (int)next; } /* Determine key */ @@ -1045,7 +1085,7 @@ static Value* builtin_store_put(Value *arg) { } else { snprintf(key_buf, STORE_MAX_KEY_LEN, "%d", next_id); next_id++; - dict_set_owned(col_info, "next_id", make_num(next_id)); + dict_set_owned(col_info, "next_id", make_int(next_id)); store->dirty = 1; } @@ -1169,8 +1209,10 @@ static Value* builtin_store_get(Value *arg) { if (key_val->type == VAL_STR) { strncpy(key_buf, key_val->data.str, STORE_MAX_KEY_LEN - 1); key_buf[STORE_MAX_KEY_LEN - 1] = '\0'; - } else if (key_val->type == VAL_NUM) { - snprintf(key_buf, STORE_MAX_KEY_LEN, "%d", (int)eigs_num_arg(key_val, __func__)); + } else if (val_is_number(key_val)) { + int64_t key = eigs_int_arg(key_val, "store_get"); + if (g_has_error) return make_null(); + eigs_int_text(key_buf, sizeof(key_buf), key); } else { return make_null(); } @@ -1179,7 +1221,9 @@ static Value* builtin_store_get(Value *arg) { if (!col_info || col_info->type != VAL_DICT) return make_null(); Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); if (!rv) return make_null(); - uint32_t pg = (uint32_t)eigs_num_arg(rv, __func__); + int64_t root; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &root, "store_get")) return make_null(); + uint32_t pg = (uint32_t)root; size_t target_key_len = strlen(key_buf); @@ -1237,7 +1281,9 @@ static int store_locate(Store *store, const char *collection, if (!col_info || col_info->type != VAL_DICT) return 0; Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); if (!rv) return 0; - uint32_t pg = (uint32_t)eigs_num_arg(rv, __func__); + int64_t root; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &root, "store_locate")) return 0; + uint32_t pg = (uint32_t)root; size_t target_key_len = strlen(key_buf); for (int _hops = 0; pg != 0 && _hops < STORE_MAX_PAGE_HOPS; _hops++) { @@ -1333,8 +1379,10 @@ static Value* builtin_store_delete(Value *arg) { if (key_val->type == VAL_STR) { strncpy(key_buf, key_val->data.str, STORE_MAX_KEY_LEN - 1); key_buf[STORE_MAX_KEY_LEN - 1] = '\0'; - } else if (key_val->type == VAL_NUM) { - snprintf(key_buf, STORE_MAX_KEY_LEN, "%d", (int)eigs_num_arg(key_val, __func__)); + } else if (val_is_number(key_val)) { + int64_t key = eigs_int_arg(key_val, "store_delete"); + if (g_has_error) return make_bool(0); + eigs_int_text(key_buf, sizeof(key_buf), key); } else { /* Reaching the else arm IS the rejection: the key is neither a string * nor a number. Guarded in place with a constant condition so the @@ -1380,7 +1428,9 @@ static Value* builtin_store_query(Value *arg) { if (!col_info || col_info->type != VAL_DICT) return make_list(0); Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); if (!rv) return make_list(0); - uint32_t pg = (uint32_t)eigs_num_arg(rv, __func__); + int64_t root; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &root, "store_query")) return make_list(0); + uint32_t pg = (uint32_t)root; Value *results = make_list(16); @@ -1415,7 +1465,7 @@ static Value* builtin_store_count(Value *arg) { /* fs:CHANNEL the arity/type failure is already signalled by the * unconditional rt_error above (EK_TYPE latch, catchable — asserted by * CV2-71); this return is the post-raise placeholder, not a count. */ - return make_num(0); + return make_int(0); } Store *store = store_arg(list_get_view(arg, 0, &(EigsView){{0}}), "store_count"); /* fs:CHANNEL store_arg is resolve-OR-RAISE (#1146): it has already @@ -1423,24 +1473,26 @@ static Value* builtin_store_count(Value *arg) { * than unwinding, so this return is the post-raise placeholder, not a * count of 0. Distinct from the two documented-answer zeros below, which * are store_count's real result for an unknown collection. */ - if (!store) return make_num(0); - if (replay_blocks("store_count", "store")) return make_num(0); + if (!store) return make_int(0); + if (replay_blocks("store_count", "store")) return make_int(0); Value *col_val = list_get_view(arg, 1, &(EigsView){{0}}); ARG_GUARD(!col_val || col_val->type != VAL_STR, - "store_count", "[store handle, string collection]", make_num(0)); + "store_count", "[store handle, string collection]", make_int(0)); const char *collection = col_val->data.str; Value *col_info = store_dict_get_view(store->catalog, collection, &(EigsView){{0}}); /* fs:ANSWER a collection that is not in the catalog holds no records, so * its count is 0 — the documented result for an unknown collection. */ - if (!col_info || col_info->type != VAL_DICT) return make_num(0); + if (!col_info || col_info->type != VAL_DICT) return make_int(0); Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); /* fs:ANSWER a catalog entry with no root page has no record pages, so the * count is 0. */ - if (!rv) return make_num(0); - uint32_t pg = (uint32_t)eigs_num_arg(rv, __func__); + if (!rv) return make_int(0); + int64_t page_id; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &page_id, "store_count")) return make_null(); + uint32_t pg = (uint32_t)page_id; - int count = 0; + int64_t count = 0; for (int _hops = 0; pg != 0 && _hops < STORE_MAX_PAGE_HOPS; _hops++) { Page page; if (store_read_page(store, pg, &page) != 0) break; @@ -1449,13 +1501,13 @@ static Value* builtin_store_count(Value *arg) { int offset = 0; for (int i = 0; i < (int)page.count; i++) { StoreRecord rec; - if (store_record_next(&page, &offset, &rec) != 0) return make_num(count); + if (store_record_next(&page, &offset, &rec) != 0) return make_int(count); if (rec.key_len == 0) continue; /* deleted */ count++; } pg = page.next_page; if (pg >= store->page_count) break; } - return make_num(count); + return make_int(count); } /* store_update([handle, collection, key, record]) -> 1 or 0 */ @@ -1489,8 +1541,10 @@ static Value* builtin_store_update(Value *arg) { if (key_val->type == VAL_STR) { strncpy(key_buf, key_val->data.str, STORE_MAX_KEY_LEN - 1); key_buf[STORE_MAX_KEY_LEN - 1] = '\0'; - } else if (key_val->type == VAL_NUM) { - snprintf(key_buf, STORE_MAX_KEY_LEN, "%d", (int)eigs_num_arg(key_val, __func__)); + } else if (val_is_number(key_val)) { + int64_t key = eigs_int_arg(key_val, "store_update"); + if (g_has_error) return make_bool(0); + eigs_int_text(key_buf, sizeof(key_buf), key); } else { /* Reaching the else arm IS the rejection: the key is neither a string * nor a number. Guarded in place with a constant condition so the @@ -1631,7 +1685,9 @@ static Value* builtin_store_drop(Value *arg) { /* Mark all pages in chain as free */ Value *rv = store_dict_get_view(col_info, "root", &(EigsView){{0}}); if (rv) { - uint32_t pg = (uint32_t)eigs_num_arg(rv, __func__); + int64_t page_id; + if (!eigs_int_arg_range(rv, 0, UINT32_MAX, &page_id, "store_drop")) return make_null(); + uint32_t pg = (uint32_t)page_id; for (int _hops = 0; pg != 0 && _hops < STORE_MAX_PAGE_HOPS; _hops++) { Page page; if (store_read_page(store, pg, &page) != 0) break; diff --git a/src/freestanding/mini_fmt.c b/src/freestanding/mini_fmt.c index e369699a5..5de34b6b2 100644 --- a/src/freestanding/mini_fmt.c +++ b/src/freestanding/mini_fmt.c @@ -258,6 +258,8 @@ static void fmt_f(Sink *s, const Spec *sp, const BigDec *b) { emit_padded(s, &sp2, sign, "", body, n, 0); } +/* N6 uses this correctly-rounded %e operation to select shortest decimal + * candidates. Keep printf semantics here; eigs_num_text owns language text. */ static void fmt_e(Sink *s, const Spec *sp, const BigDec *b, int upper) { int P = (sp->prec >= 0) ? sp->prec : 6; int is_zero; diff --git a/src/jit.c b/src/jit.c index 6913a5af2..71f54976f 100644 --- a/src/jit.c +++ b/src/jit.c @@ -745,8 +745,7 @@ static int jit_supported_prefix(const struct EigsChunk *chunk, } else if (op == OP_DUP2) { i += 1; ops++; non_line_ops++; } else if (op == OP_ADD || op == OP_SUB || - op == OP_MUL || op == OP_DIV || - op == OP_MOD) { + op == OP_MUL || op == OP_DIV) { i += 1; ops++; non_line_ops++; *has_bail_op = 1; /* Stage 5e: the tracked-operand loaders + commit decrefs @@ -761,13 +760,7 @@ static int jit_supported_prefix(const struct EigsChunk *chunk, *extra_size += 320; /* Stage 5e loaders + decrefs */ /* #1637: the result is the false/true TAG_BOOL immediate, so * OP_POP after a comparison remains a no-op peephole. */ - } else if (op == OP_BAND || op == OP_BOR || op == OP_BXOR || - op == OP_SHL || op == OP_SHR) { - i += 1; ops++; non_line_ops++; - *has_bail_op = 1; - /* Result is a double (whole-number-valued), so a subsequent - * OP_POP stays a no-op peephole. */ - } else if (op == OP_NEG || op == OP_NOT || op == OP_BNOT) { + } else if (op == OP_NEG || op == OP_NOT) { i += 1; ops++; non_line_ops++; *has_bail_op = 1; } else if (op == OP_JUMP || op == OP_JUMP_BACK) { @@ -1737,18 +1730,6 @@ static uint8_t *emit_cvttsd2si_xmm0_r8d(uint8_t *w) { static uint8_t *emit_cvtsi2sd_r8d_xmm1(uint8_t *w) { *w++ = 0xF2; *w++ = 0x41; *w++ = 0x0F; *w++ = 0x2A; *w++ = 0xC8; return w; } -/* cvttsd2si %xmm0, %edx (4 bytes) — 32-bit, mirrors `(int)val_s.d`. - * No longer used by OP_INDEX_SET (buffer stores keep the raw double now) - * but kept for future int-conversion fast paths. */ -__attribute__((unused)) -static uint8_t *emit_cvttsd2si_xmm0_edx(uint8_t *w) { - *w++ = 0xF2; *w++ = 0x0F; *w++ = 0x2C; *w++ = 0xD0; return w; -} -/* cvtsi2sd %edx, %xmm0 (4 bytes) — 32-bit source. */ -__attribute__((unused)) -static uint8_t *emit_cvtsi2sd_edx_xmm0(uint8_t *w) { - *w++ = 0xF2; *w++ = 0x0F; *w++ = 0x2A; *w++ = 0xC2; return w; -} /* jp rel32 (6 bytes): 0F 8A — jump if PF=1 (unordered ucomisd). */ static uint8_t *emit_jp_rel32(uint8_t *w, uint8_t **patch) { *w++ = 0x0F; *w++ = 0x8A; *patch = w; @@ -2357,9 +2338,9 @@ static uint8_t *emit_mov_disp32_rdi_to_rdx(uint8_t *w, int32_t disp) { /* Stage 5e: numeric-operand loader for the arith/compare templates. * - * Accepts what the interpreter's slot_as_double accepts MINUS the + * Accepts what the interpreter's slot_f64_value accepts MINUS the * NUM_REUSE candidates: an immediate double, or a heap/tracked pointer - * to a VAL_NUM with refcount >= 2 (refcount 1 would make the + * to a VAL_FLOAT with refcount >= 2 (refcount 1 would make the * interpreter's NUM_REUSE in-place branch fire, so it routes to the * interpreter to keep observer-value identity semantics exact). * @@ -2383,17 +2364,17 @@ static uint8_t *emit_load_numeric_operand(uint8_t *w, int is_b, /* Tag 0xFFFD+ (reserved/sentinel): not a pointer we may chase. */ w = emit_cmp_imm32_esi(w, 0xFFFD); w = emit_jae_rel32(w, &lp[*lp_n]); (*lp_n)++; - /* Heap or tracked pointer: must be a non-REUSE VAL_NUM. */ + /* Heap or tracked pointer: must be a non-REUSE VAL_FLOAT. */ w = is_b ? emit_mov_rax_rdi(w) : emit_mov_rdx_rdi(w); w = emit_shl_16_rdi(w); w = emit_shr_16_rdi(w); w = emit_cmpl_imm32_disp32_rdi(w, (int32_t)offsetof(Value, type), - (uint32_t)VAL_NUM); + (uint32_t)VAL_FLOAT); w = emit_jne_rel32(w, &lp[*lp_n]); (*lp_n)++; w = emit_cmpl_imm32_disp32_rdi(w, (int32_t)offsetof(Value, refcount), 2); w = emit_jl_rel32(w, &lp[*lp_n]); (*lp_n)++; - w = is_b ? emit_mov_disp32_rdi_to_rax(w, (int32_t)VAL_NUM_OFFSET) - : emit_mov_disp32_rdi_to_rdx(w, (int32_t)VAL_NUM_OFFSET); + w = is_b ? emit_mov_disp32_rdi_to_rax(w, (int32_t)VAL_F64_OFFSET) + : emit_mov_disp32_rdi_to_rdx(w, (int32_t)VAL_F64_OFFSET); /* .imm: bits (immediate or loaded data.num) → xmm. */ { int rel = (int)(w - imm_after); @@ -2847,10 +2828,10 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, Value *v = chunk->constants[idx]; uint64_t bits; int needs_incref = 0; - if (v->type == VAL_NUM) { + if (v->type == VAL_FLOAT) { /* Immediate-num: same shape as NUM_ZERO. (#262 Step E: a * constant num never carries observer state.) */ - memcpy(&bits, &VAL_NUM_RAW(v), 8); + memcpy(&bits, &VAL_F64_RAW(v), 8); } else { /* Heap slot: TAG_HEAP | payload. */ bits = 0xFFFB000000000000ULL | @@ -3438,7 +3419,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, } else if (op == OP_SET_LOCAL) { /* Stage 5e: mirror CASE(SET_LOCAL) exactly, including its * in-place branch — writing an immediate into a slot whose - * existing Value is an exclusive non-observed VAL_NUM + * existing Value is an exclusive non-observed VAL_FLOAT * rewrites data.num instead of swapping pointers. This is * load-bearing beyond speed: the observer's g_last_observer * can point at that Value, and the swap path would free it @@ -3447,9 +3428,9 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, * tos imm? no → .swap * old slot ptr? no → .plain (imm/sentinel old: * incref+decref both no-op) - * old is VAL_NUM && rc==1 && obs_age==0? + * old is VAL_FLOAT && rc==1 && obs_age==0? * no → .swap - * in-place: VAL_NUM_RAW(old) = tos bits; → .done + * in-place: VAL_F64_RAW(old) = tos bits; → .done * .plain: values[slot] = tos; → .done * .swap: values[slot] = tos; incref tos; decref old * .done: */ @@ -3490,13 +3471,13 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, w = emit_shl_16_rdi(w); w = emit_shr_16_rdi(w); w = emit_cmpl_imm32_disp32_rdi(w, (int32_t)offsetof(Value, type), - (uint32_t)VAL_NUM); + (uint32_t)VAL_FLOAT); w = emit_jne_rel32(w, &swap_p[swap_n]); swap_n++; w = emit_cmpl_imm32_disp32_rdi(w, (int32_t)offsetof(Value, refcount), 1); w = emit_jne_rel32(w, &swap_p[swap_n]); swap_n++; /* #262 Step E: obs_age guard removed — nums are never tracked. */ - /* in-place: VAL_NUM_RAW(existing) = tos bits. */ - w = emit_mov_rax_to_disp32_rdi(w, (int32_t)VAL_NUM_OFFSET); + /* in-place: VAL_F64_RAW(existing) = tos bits. */ + w = emit_mov_rax_to_disp32_rdi(w, (int32_t)VAL_F64_OFFSET); w = emit_jmp_rel32(w, &done_p[done_n]); done_n++; /* .plain: store only (both refcount sides are no-ops). */ for (int k = 0; k < plain_n; k++) patch_rel32(plain_p[k], w); @@ -3563,7 +3544,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, if (ab) { JIT_BAIL_AND_RETURN(); } - /* Divisor zero check. The VM compares `VAL_NUM_RAW(b) != 0.0`, + /* Divisor zero check. The VM compares `VAL_F64_RAW(b) != 0.0`, * which under IEEE-754 treats +0.0 and -0.0 as equal — so * we must also bail when b == -0.0 (bits = 0x8000…0). * Strategy: copy b's bits to %rsi (dead scratch after the @@ -3614,7 +3595,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, } else if (op == OP_ADD || op == OP_SUB || op == OP_MUL || op == OP_DIV) { /* Stage 5e: operands may be immediates OR heap/tracked - * VAL_NUM pointers with refcount >= 2 (the loader bails + * VAL_FLOAT pointers with refcount >= 2 (the loader bails * the rc==1 case so the interpreter's NUM_REUSE branch * keeps its in-place semantics). All guard failures route * through one per-op trampoline so the epilogue patch @@ -3679,7 +3660,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, i += 1; } else if (op == OP_EQ || op == OP_NE || op == OP_LT || op == OP_GT || op == OP_LE || op == OP_GE) { - /* Stage 5e: tracked/heap VAL_NUM operands accepted via the + /* Stage 5e: tracked/heap VAL_FLOAT operands accepted via the * shared loader (rc==1 bails — NUM_CMP/EQ/NE all have the * same NUM_REUSE branch as ARITH_FAST). One trampoline * slot. NaN inputs would give incorrect comparison results @@ -4461,7 +4442,7 @@ static void jit_compile_to_thunk(struct EigsChunk *chunk, uint16_t idx = (uint16_t)(chunk->code[op_start + 1] | ((uint16_t)chunk->code[op_start + 2] << 8)); Value *v = chunk->constants[idx]; - last_imm = (v && v->type == VAL_NUM); + last_imm = (v && v->type == VAL_FLOAT); break; } case OP_LINE: case OP_BINARY_LINE: case OP_BINARY_LINE_END: diff --git a/src/jit_smoke.c b/src/jit_smoke.c index b6be2bb6b..c56fe2fe9 100644 --- a/src/jit_smoke.c +++ b/src/jit_smoke.c @@ -100,7 +100,7 @@ int jit_helper_local_idx_get(int slot, int idx) { VM *vm = eigs_current->vm; index_receipt.calls++; if (slot != 0 || idx != 0 || vm->sp != 0) index_receipt.bad++; - vm->stack[vm->sp++] = index_receipt.status ? slot_null() : slot_from_num(42); + vm->stack[vm->sp++] = index_receipt.status ? slot_null() : slot_from_f64(42); return index_receipt.status; } @@ -135,22 +135,22 @@ int jit_helper_iter_next(void) { VM *vm = eigs_current->vm; reader_receipt.calls++; if (reader_receipt.kind != 1 || vm->sp != 1 || - SLOT_NUM_RAW(vm->stack[0]) != 0 || vm->frames[0].ip != reader_receipt.ip || + SLOT_F64_RAW(vm->stack[0]) != 0 || vm->frames[0].ip != reader_receipt.ip || vm->current_line != 71) reader_receipt.bad++; if (reader_receipt.status != 1) - vm->stack[vm->sp++] = slot_from_num(reader_receipt.status ? 0 : 42); + vm->stack[vm->sp++] = slot_from_f64(reader_receipt.status ? 0 : 42); return reader_receipt.status; } int jit_helper_index_get(void) { VM *vm = eigs_current->vm; reader_receipt.calls++; if (reader_receipt.kind != 0 || vm->sp != 2 || - SLOT_NUM_RAW(vm->stack[0]) != 0 || SLOT_NUM_RAW(vm->stack[1]) != 1 || + SLOT_F64_RAW(vm->stack[0]) != 0 || SLOT_F64_RAW(vm->stack[1]) != 1 || vm->frames[0].ip != reader_receipt.ip || vm->current_line != 71) reader_receipt.bad++; vm->sp -= 2; - vm->stack[vm->sp++] = slot_from_num(reader_receipt.status ? 0 : 42); + vm->stack[vm->sp++] = slot_from_f64(reader_receipt.status ? 0 : 42); return reader_receipt.status; } /* Remaining Stage 4q/4v helpers are linker-immediate stubs. */ @@ -171,11 +171,11 @@ static struct { void jit_helper_set_name(struct EigsChunk *chunk, int idx) { store_receipt.calls++; if (chunk != store_receipt.chunk || idx != 0 || g_vm.sp != 3 || - SLOT_NUM_RAW(g_vm.stack[0]) != 42 || SLOT_NUM_RAW(g_vm.stack[1]) != 0 || - SLOT_NUM_RAW(g_vm.stack[2]) != 1 || g_vm.frames[0].ip != store_receipt.ip) + SLOT_F64_RAW(g_vm.stack[0]) != 42 || SLOT_F64_RAW(g_vm.stack[1]) != 0 || + SLOT_F64_RAW(g_vm.stack[2]) != 1 || g_vm.frames[0].ip != store_receipt.ip) store_receipt.bad = 1; for (int i = 0; i < 3; i++) { - if (SLOT_NUM_RAW(store_receipt.values[i]) != 10 + i || + if (SLOT_F64_RAW(store_receipt.values[i]) != 10 + i || store_receipt.counts[i] != 17) store_receipt.bad = 1; } @@ -328,7 +328,7 @@ static int run_store_cases(void) { for (size_t c = 0; c < sizeof cases / sizeof cases[0]; c++) { row = cases[c].name; for (int i = 0; i < 3; i++) { - values[i] = slot_from_num(10 + i); + values[i] = slot_from_f64(10 + i); counts[i] = 17; } env[0].parent = cases[c].parent < 0 ? NULL : &env[cases[c].parent]; @@ -342,7 +342,7 @@ static int run_store_cases(void) { ic.walk_depth = cases[c].depth; ic.slot_idx = 0; vm->sp = 1; - vm->stack[0] = slot_from_num(42); + vm->stack[0] = slot_from_f64(42); vm->stack[1] = vm->stack[2] = slot_null(); vm->frame_count = 1; vm->frames[0].chunk = &chunk; @@ -359,15 +359,15 @@ static int run_store_cases(void) { STORE_ASSERT(store_receipt.calls == cases[c].helper); STORE_ASSERT(store_receipt.bad == 0); STORE_ASSERT(vm->sp == 3); - STORE_ASSERT(SLOT_NUM_RAW(vm->stack[0]) == 42 && SLOT_NUM_RAW(vm->stack[1]) == 0); - STORE_ASSERT(SLOT_NUM_RAW(vm->stack[2]) == 1); + STORE_ASSERT(SLOT_F64_RAW(vm->stack[0]) == 42 && SLOT_F64_RAW(vm->stack[1]) == 0); + STORE_ASSERT(SLOT_F64_RAW(vm->stack[2]) == 1); STORE_ASSERT(vm->frames[0].ip == store_receipt.ip); STORE_ASSERT((osr ? chunk.jit_osr[0].advance : chunk.jit_advance) == 5); STORE_ASSERT((osr ? chunk.jit_advance : chunk.jit_osr[0].advance) == -99); STORE_ASSERT(state->builtin_env == &env[2]); for (int i = 0; i < 3; i++) { int stored = !cases[c].helper && i == target; - STORE_ASSERT(SLOT_NUM_RAW(values[i]) == (stored ? 1 : 10 + i)); + STORE_ASSERT(SLOT_F64_RAW(values[i]) == (stored ? 1 : 10 + i)); STORE_ASSERT(counts[i] == 17 + stored); } helpers += store_receipt.calls; @@ -443,7 +443,7 @@ static int run_index_bail_cases(void) { advance == expected && index_receipt.calls == 1 && index_receipt.bad == 0 && vm->sp == (status ? 1 : 2) && (status ? slot_is_null(vm->stack[0]) : - SLOT_NUM_RAW(vm->stack[0]) == 42 && SLOT_NUM_RAW(vm->stack[1]) == 1); + SLOT_F64_RAW(vm->stack[0]) == 42 && SLOT_F64_RAW(vm->stack[1]) == 1); rows++; if (!good) { fprintf(stderr, "FAIL: index-bail %s status=%d r13=%" PRIx64 @@ -535,12 +535,12 @@ static int run_reader_bail_cases(void) { int expected_sp = kind ? (status ? 2 : 4) : (status ? 1 : 2); int expected_line = status ? 71 : 72; int stack_ok = kind - ? SLOT_NUM_RAW(vm->stack[0]) == 0 && - (status ? SLOT_NUM_RAW(vm->stack[1]) == 0 : - SLOT_NUM_RAW(vm->stack[1]) == 42 && SLOT_NUM_RAW(vm->stack[2]) == 1 && - SLOT_NUM_RAW(vm->stack[3]) == 0) - : (status ? SLOT_NUM_RAW(vm->stack[0]) == 0 : - SLOT_NUM_RAW(vm->stack[0]) == 42 && SLOT_NUM_RAW(vm->stack[1]) == 1); + ? SLOT_F64_RAW(vm->stack[0]) == 0 && + (status ? SLOT_F64_RAW(vm->stack[1]) == 0 : + SLOT_F64_RAW(vm->stack[1]) == 42 && SLOT_F64_RAW(vm->stack[2]) == 1 && + SLOT_F64_RAW(vm->stack[3]) == 0) + : (status ? SLOT_F64_RAW(vm->stack[0]) == 0 : + SLOT_F64_RAW(vm->stack[0]) == 42 && SLOT_F64_RAW(vm->stack[1]) == 1); int good = saved_r13 == UINT64_C(0x13579bdf2468ace0) && advance == expected && reader_receipt.calls == 1 && reader_receipt.bad == 0 && vm->sp == expected_sp && stack_ok && @@ -600,8 +600,8 @@ static int run_exit_cases(void) { vm->owner = thread; eigs_current = thread; state->jit_entry_threshold = state->jit_iter_threshold = 1; - Value limit = {.type = VAL_NUM}; - VAL_NUM_RAW(&limit) = 4; + Value limit = {.type = VAL_FLOAT}; + VAL_F64_RAW(&limit) = 4; Value *constants[] = {&limit}; uint8_t code[] = {OP_NULL, OP_POP, OP_NULL, OP_POP, OP_NUM_ONE, OP_ADD, OP_DUP, OP_CONST, 0, 0, @@ -627,7 +627,7 @@ static int run_exit_cases(void) { thread->exit_scope = stop ? &stopped : &open; state->exit_scope = row == 2 ? &stopped : &open; vm->sp = vm->frame_count = 1; - vm->stack[0] = slot_from_num(0); + vm->stack[0] = slot_from_f64(0); vm->frames[0].chunk = &chunk; vm->frames[0].ip = chunk.code + entry; chunk.jit_advance = chunk.jit_osr[0].advance = -99; @@ -636,12 +636,12 @@ static int run_exit_cases(void) { int advance = osr ? chunk.jit_osr[0].advance : chunk.jit_advance; int other = osr ? chunk.jit_advance : chunk.jit_osr[0].advance; int want_advance = (stop ? 10 : 13) + (osr ? 2 : 0); - int ok = vm->sp == 1 && SLOT_NUM_RAW(vm->stack[0]) == (stop ? 1 : 4) && + int ok = vm->sp == 1 && SLOT_F64_RAW(vm->stack[0]) == (stop ? 1 : 4) && advance == want_advance && other == -99 && vm->frames[0].ip == chunk.code + entry; if (!ok) { fprintf(stderr, "FAIL native_exit %s row=%d sp=%d value=%g advance=%d other=%d\n", - osr ? "osr" : "entry", row, vm->sp, SLOT_NUM_RAW(vm->stack[0]), advance, other); + osr ? "osr" : "entry", row, vm->sp, SLOT_F64_RAW(vm->stack[0]), advance, other); rc = 1; } if (ok && stop) bailed++; diff --git a/src/lexer.c b/src/lexer.c index 2672d8436..2b20b7109 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -32,14 +32,14 @@ static void lexer_error_at(const char *caret_source, int line, int col, eigs_print_caret_src(stderr, caret_source, line, col); } -static void tok_add(TokenList *tl, TokType type, double num, const char *str, int line, int col) { +static void tok_add(TokenList *tl, TokType type, EigsNum number, const char *str, int line, int col) { if (tl->count >= tl->capacity) { tl->capacity *= 2; tl->tokens = xrealloc_array(tl->tokens, tl->capacity, sizeof(Token)); } Token *t = &tl->tokens[tl->count++]; t->type = type; - t->num_val = num; + t->number = number; t->str_val = str ? xstrdup(str) : NULL; t->line = line; t->col = col; @@ -170,7 +170,7 @@ static const char *fstr_interp_end(const char *p, int nesting, int *too_deep) { * reports the post-`}` column, since a zero length would read as * "column unknown" to the error recorder. */ static void tok_add_synth(TokenList *tl, TokType type, const char *str, int line, int col) { - tok_add(tl, type, 0, str, line, col); + tok_add(tl, type, (EigsNum){0}, str, line, col); tl->tokens[tl->count - 1].synth = 1; } @@ -260,7 +260,7 @@ static TokType keyword_type(const char *word) { } int tok_base_string_id_count(void) { - return (int)TOK_EOF + 1; + return (int)TOK_SLASH_SLASH_EQ + 1; } /* Recognised multi-char operators. Each operator is spelled once, as @@ -268,6 +268,7 @@ int tok_base_string_id_count(void) { #define LEX_MULTI_OP_MAP(X2, X3) \ X3(TOK_SHL_EQ, '<', '<', '=') \ X3(TOK_SHR_EQ, '>', '>', '=') \ + X3(TOK_SLASH_SLASH_EQ, '/', '/', '=') \ X2(TOK_EQ, '=', '=') \ X2(TOK_NE, '!', '=') \ X2(TOK_LE, '<', '=') \ @@ -278,6 +279,7 @@ int tok_base_string_id_count(void) { X2(TOK_MINUS_EQ, '-', '=') \ X2(TOK_STAR_EQ, '*', '=') \ X2(TOK_SLASH_EQ, '/', '=') \ + X2(TOK_SLASH_SLASH, '/', '/') \ X2(TOK_PERCENT_EQ, '%', '=') \ X2(TOK_AMP_EQ, '&', '=') \ X2(TOK_BITOR_EQ, '|', '=') \ @@ -394,6 +396,8 @@ const char* tok_base_string(TokType t) { case TOK_MINUS: return "- "; case TOK_STAR: return "* "; case TOK_SLASH: return "/ "; + case TOK_SLASH_SLASH: return "// "; + case TOK_SLASH_SLASH_EQ: return "//= "; case TOK_PERCENT: return "% "; case TOK_LT: return "< "; case TOK_GT: return "> "; @@ -478,7 +482,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init snprintf(msg, sizeof(msg), "f-string nesting too deep (max %d levels)", MAX_TOKENIZE_DEPTH); lexer_error_at(caret_source, initial_line, initial_col, msg); - tok_add(&tl, TOK_EOF, 0, NULL, initial_line, initial_col); + tok_add(&tl, TOK_EOF, (EigsNum){0}, NULL, initial_line, initial_col); return tl; } g_tokenize_depth++; @@ -531,12 +535,12 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init } else { indent_top++; indent_stack[indent_top] = spaces; - tok_add(&tl, TOK_INDENT, 0, NULL, line, col); + tok_add(&tl, TOK_INDENT, (EigsNum){0}, NULL, line, col); } } else { while (indent_top > 0 && spaces < indent_stack[indent_top]) { indent_top--; - tok_add(&tl, TOK_DEDENT, 0, NULL, line, col); + tok_add(&tl, TOK_DEDENT, (EigsNum){0}, NULL, line, col); } if (spaces != indent_stack[indent_top]) { fprintf(stderr, "Syntax error line %d: indentation does not match any outer level\n", line); @@ -571,7 +575,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init if (tl.count > 0 && tl.tokens[tl.count-1].type != TOK_NEWLINE && tl.tokens[tl.count-1].type != TOK_INDENT && tl.tokens[tl.count-1].type != TOK_DEDENT) { - tok_add(&tl, TOK_NEWLINE, 0, NULL, line, col); + tok_add(&tl, TOK_NEWLINE, (EigsNum){0}, NULL, line, col); } at_line_start = 1; } @@ -694,7 +698,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init * the post-`}` column sent LSP rename edits onto * the closing quote). */ Token *it = &inner.tokens[ti]; - tok_add(&tl, it->type, it->num_val, it->str_val, it->line, it->col); + tok_add(&tl, it->type, it->number, it->str_val, it->line, it->col); tl.tokens[tl.count - 1].len = it->len; tl.tokens[tl.count - 1].synth = it->synth; } @@ -760,7 +764,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init fprintf(stderr, "Syntax error line %d: unterminated string\n", tok_line); lexer_error_at(caret_source, tok_line, tok_col, "unterminated string"); } - tok_add(&tl, TOK_STR, 0, buf.data, tok_line, tok_col); + tok_add(&tl, TOK_STR, (EigsNum){0}, buf.data, tok_line, tok_col); tl.tokens[tl.count - 1].len = (int)(p - str_start); /* true source span */ if (layout) literal_span_add(&tl, (size_t)(str_start - source), (size_t)(p - source)); @@ -770,41 +774,58 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init if (isdigit(*p) || (*p == '.' && isdigit(*(p+1)))) { const char *num_start = p; - /* Hex integer literals are lexed HERE, not via strtod: the - * freestanding mini_strtod has no hex path (documented - * divergence — hex used to break on EigenOS), and C99 strtod - * also accepts hex-FLOAT forms (0x1p4, 0xA.8) the language - * does not mean. Digits accumulate in a double, so values - * past 2^53 round exactly like long decimal literals do. */ + /* Hex is parsed here rather than by strtod, whose hex-float + * grammar is wider than ours and absent from mini_strtod. N1: + * accumulate modulo 2^64, then interpret the signed int64 value + * without an implementation-defined uint64-to-int64 cast. */ if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { if (isxdigit((unsigned char)p[2])) { - double num = 0; + uint64_t bits = 0; p += 2; col += 2; while (isxdigit((unsigned char)*p)) { int d = *p <= '9' ? *p - '0' : (*p | 32) - 'a' + 10; - num = num * 16 + d; + bits = bits * UINT64_C(16) + (uint64_t)d; p++; col++; } - tok_add(&tl, TOK_NUM, num, NULL, line, tok_col); + int64_t value = bits <= INT64_MAX ? (int64_t)bits + : -1 - (int64_t)(UINT64_MAX - bits); + EigsNum number = { .is_int = 1, .value.i64 = value }; + tok_add(&tl, TOK_NUM, number, NULL, line, tok_col); tl.tokens[tl.count - 1].len = (int)(p - num_start); continue; } - /* A 0x prefix with no hex digit (bare `0x`, or the - * hex-FLOAT fraction form `0x.8`): lex just the `0` so - * the rest re-lexes as an identifier/dot and the parse - * fails loudly. strtod must NEVER see a hex prefix — - * glibc would parse `0x.8` as 0.5 while mini_strtod - * (no hex) reads 0, a silent profile divergence. */ - tok_add(&tl, TOK_NUM, 0, NULL, line, tok_col); + /* A hex prefix without a digit lexes as `0` followed by + * invalid suffix tokens, on hosted and freestanding builds. */ + EigsNum zero = { .is_int = 1, .value.i64 = 0 }; + tok_add(&tl, TOK_NUM, zero, NULL, line, tok_col); tl.tokens[tl.count - 1].len = 1; p++; col++; continue; } char *end; - double num = strtod(p, &end); + double f = strtod(p, &end); + EigsNum number = { .is_int = 0, .value.f64 = f }; + /* strtod determines the existing decimal grammar/span. Only a + * digits-only token can be an int: `3.0` and `1e3` stay floats. + * Parse the integer independently, so no double rounding occurs + * above 2^53, and fall back to the float on decimal overflow. */ + int64_t integer = 0; + int fits = 1; + for (const char *q = p; q < end; q++) { + if (*q < '0' || *q > '9' || + integer > (INT64_MAX - (*q - '0')) / 10) { + fits = 0; + break; + } + integer = integer * 10 + (*q - '0'); + } + if (fits) { + number.is_int = 1; + number.value.i64 = integer; + } col += (int)(end - p); p = end; - tok_add(&tl, TOK_NUM, num, NULL, line, tok_col); + tok_add(&tl, TOK_NUM, number, NULL, line, tok_col); tl.tokens[tl.count - 1].len = (int)(end - num_start); continue; } @@ -822,7 +843,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init col++; } TokType tt = keyword_type(buf.data); - tok_add(&tl, tt, 0, buf.data, line, tok_col); + tok_add(&tl, tt, (EigsNum){0}, buf.data, line, tok_col); strbuf_free(&buf); continue; } @@ -832,7 +853,7 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init TokType op_ty = 0; int op_n = lexer_operator_len(p, &op_ty); if (op_n > 1) { - tok_add(&tl, op_ty, 0, NULL, line, tok_col); + tok_add(&tl, op_ty, (EigsNum){0}, NULL, line, tok_col); p += op_n; col += op_n; continue; @@ -840,32 +861,32 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init } switch (*p) { - case '+': tok_add(&tl, TOK_PLUS, 0, NULL, line, tok_col); p++; col++; break; - case '-': tok_add(&tl, TOK_MINUS, 0, NULL, line, tok_col); p++; col++; break; - case '*': tok_add(&tl, TOK_STAR, 0, NULL, line, tok_col); p++; col++; break; - case '/': tok_add(&tl, TOK_SLASH, 0, NULL, line, tok_col); p++; col++; break; - case '%': tok_add(&tl, TOK_PERCENT, 0, NULL, line, tok_col); p++; col++; break; - case '(': tok_add(&tl, TOK_LPAREN, 0, NULL, line, tok_col); p++; col++; bracket_depth++; break; - case ')': tok_add(&tl, TOK_RPAREN, 0, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; - case '[': tok_add(&tl, TOK_LBRACKET, 0, NULL, line, tok_col); p++; col++; bracket_depth++; break; - case ']': tok_add(&tl, TOK_RBRACKET, 0, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; - case '{': tok_add(&tl, TOK_LBRACE, 0, NULL, line, tok_col); p++; col++; bracket_depth++; break; - case '}': tok_add(&tl, TOK_RBRACE, 0, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; - case ',': tok_add(&tl, TOK_COMMA, 0, NULL, line, tok_col); p++; col++; break; - case ':': tok_add(&tl, TOK_COLON, 0, NULL, line, tok_col); p++; col++; break; - case '.': tok_add(&tl, TOK_DOT, 0, NULL, line, tok_col); p++; col++; break; - case '<': tok_add(&tl, TOK_LT, 0, NULL, line, tok_col); p++; col++; break; - case '>': tok_add(&tl, TOK_GT, 0, NULL, line, tok_col); p++; col++; break; + case '+': tok_add(&tl, TOK_PLUS, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '-': tok_add(&tl, TOK_MINUS, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '*': tok_add(&tl, TOK_STAR, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '/': tok_add(&tl, TOK_SLASH, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '%': tok_add(&tl, TOK_PERCENT, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '(': tok_add(&tl, TOK_LPAREN, (EigsNum){0}, NULL, line, tok_col); p++; col++; bracket_depth++; break; + case ')': tok_add(&tl, TOK_RPAREN, (EigsNum){0}, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; + case '[': tok_add(&tl, TOK_LBRACKET, (EigsNum){0}, NULL, line, tok_col); p++; col++; bracket_depth++; break; + case ']': tok_add(&tl, TOK_RBRACKET, (EigsNum){0}, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; + case '{': tok_add(&tl, TOK_LBRACE, (EigsNum){0}, NULL, line, tok_col); p++; col++; bracket_depth++; break; + case '}': tok_add(&tl, TOK_RBRACE, (EigsNum){0}, NULL, line, tok_col); p++; col++; if (bracket_depth > 0) bracket_depth--; break; + case ',': tok_add(&tl, TOK_COMMA, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case ':': tok_add(&tl, TOK_COLON, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '.': tok_add(&tl, TOK_DOT, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '<': tok_add(&tl, TOK_LT, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '>': tok_add(&tl, TOK_GT, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; case '!': fprintf(stderr, "Syntax error line %d: expected '!=' after '!'\n", line); lexer_error_at(caret_source, line, tok_col, "expected '!=' after '!'"); p++; col++; break; - case '=': tok_add(&tl, TOK_ASSIGN, 0, NULL, line, tok_col); p++; col++; break; - case '|': tok_add(&tl, TOK_BITOR, 0, NULL, line, tok_col); p++; col++; break; - case '&': tok_add(&tl, TOK_AMP, 0, NULL, line, tok_col); p++; col++; break; - case '^': tok_add(&tl, TOK_CARET, 0, NULL, line, tok_col); p++; col++; break; - case '~': tok_add(&tl, TOK_TILDE, 0, NULL, line, tok_col); p++; col++; break; + case '=': tok_add(&tl, TOK_ASSIGN, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '|': tok_add(&tl, TOK_BITOR, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '&': tok_add(&tl, TOK_AMP, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '^': tok_add(&tl, TOK_CARET, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; + case '~': tok_add(&tl, TOK_TILDE, (EigsNum){0}, NULL, line, tok_col); p++; col++; break; default: { /* Spell the offending byte, never echo it (#1048). A byte @@ -892,14 +913,14 @@ static TokenList tokenize_at_line(const char *source, int initial_line, int init } while (indent_top > 0) { - tok_add(&tl, TOK_DEDENT, 0, NULL, line, col); + tok_add(&tl, TOK_DEDENT, (EigsNum){0}, NULL, line, col); indent_top--; } if (tl.count > 0 && tl.tokens[tl.count-1].type != TOK_NEWLINE) { - tok_add(&tl, TOK_NEWLINE, 0, NULL, line, col); + tok_add(&tl, TOK_NEWLINE, (EigsNum){0}, NULL, line, col); } - tok_add(&tl, TOK_EOF, 0, NULL, line, col); + tok_add(&tl, TOK_EOF, (EigsNum){0}, NULL, line, col); g_tokenize_depth--; if (layout) resolve_comment_depths(&tl); diff --git a/src/lsp_builtin_index.h b/src/lsp_builtin_index.h index 11d933bd2..f45b26e41 100644 --- a/src/lsp_builtin_index.h +++ b/src/lsp_builtin_index.h @@ -105,6 +105,7 @@ static const char *builtin_docs[][2] = { {"fill", "fill of [count, value] — create a list of `count` elements all set to `value`."}, {"floor", "floor — builtin; see docs/BUILTINS.md"}, {"flush", "flush of null — flush stdout"}, + {"fround", "fround of x — binary64 to binary32 to binary64, returning float kind"}, {"gather", "gather of [tensor, indices, dim] -> select one element per row by index."}, {"get_at", "get_at of [list, index] or get_at of [list, row, col]"}, {"get_observer_thresholds", "get_observer_thresholds — builtin; see docs/BUILTINS.md"}, @@ -141,6 +142,42 @@ static const char *builtin_docs[][2] = { {"http_serve", "http_serve — http extension builtin; see docs/BUILTINS.md"}, {"http_session_id", "http_session_id — http_request extension builtin; see docs/BUILTINS.md"}, {"http_static", "http_static — http extension builtin; see docs/BUILTINS.md"}, + {"i16_checked_add", "i16_checked_add of [a, b] — i16 checked add; N5 operands, int result"}, + {"i16_checked_mul", "i16_checked_mul of [a, b] — i16 checked mul; N5 operands, int result"}, + {"i16_checked_sub", "i16_checked_sub of [a, b] — i16 checked sub; N5 operands, int result"}, + {"i16_saturating_add", "i16_saturating_add of [a, b] — i16 saturating add; N5 operands, int result"}, + {"i16_saturating_mul", "i16_saturating_mul of [a, b] — i16 saturating mul; N5 operands, int result"}, + {"i16_saturating_sub", "i16_saturating_sub of [a, b] — i16 saturating sub; N5 operands, int result"}, + {"i16_wrapping_add", "i16_wrapping_add of [a, b] — i16 wrapping add; N5 operands, int result"}, + {"i16_wrapping_mul", "i16_wrapping_mul of [a, b] — i16 wrapping mul; N5 operands, int result"}, + {"i16_wrapping_sub", "i16_wrapping_sub of [a, b] — i16 wrapping sub; N5 operands, int result"}, + {"i32_checked_add", "i32_checked_add of [a, b] — i32 checked add; N5 operands, int result"}, + {"i32_checked_mul", "i32_checked_mul of [a, b] — i32 checked mul; N5 operands, int result"}, + {"i32_checked_sub", "i32_checked_sub of [a, b] — i32 checked sub; N5 operands, int result"}, + {"i32_saturating_add", "i32_saturating_add of [a, b] — i32 saturating add; N5 operands, int result"}, + {"i32_saturating_mul", "i32_saturating_mul of [a, b] — i32 saturating mul; N5 operands, int result"}, + {"i32_saturating_sub", "i32_saturating_sub of [a, b] — i32 saturating sub; N5 operands, int result"}, + {"i32_wrapping_add", "i32_wrapping_add of [a, b] — i32 wrapping add; N5 operands, int result"}, + {"i32_wrapping_mul", "i32_wrapping_mul of [a, b] — i32 wrapping mul; N5 operands, int result"}, + {"i32_wrapping_sub", "i32_wrapping_sub of [a, b] — i32 wrapping sub; N5 operands, int result"}, + {"i64_checked_add", "i64_checked_add of [a, b] — i64 checked add; N5 operands, int result"}, + {"i64_checked_mul", "i64_checked_mul of [a, b] — i64 checked mul; N5 operands, int result"}, + {"i64_checked_sub", "i64_checked_sub of [a, b] — i64 checked sub; N5 operands, int result"}, + {"i64_saturating_add", "i64_saturating_add of [a, b] — i64 saturating add; N5 operands, int result"}, + {"i64_saturating_mul", "i64_saturating_mul of [a, b] — i64 saturating mul; N5 operands, int result"}, + {"i64_saturating_sub", "i64_saturating_sub of [a, b] — i64 saturating sub; N5 operands, int result"}, + {"i64_wrapping_add", "i64_wrapping_add of [a, b] — i64 wrapping add; N5 operands, int result"}, + {"i64_wrapping_mul", "i64_wrapping_mul of [a, b] — i64 wrapping mul; N5 operands, int result"}, + {"i64_wrapping_sub", "i64_wrapping_sub of [a, b] — i64 wrapping sub; N5 operands, int result"}, + {"i8_checked_add", "i8_checked_add of [a, b] — i8 checked add; N5 operands, int result"}, + {"i8_checked_mul", "i8_checked_mul of [a, b] — i8 checked mul; N5 operands, int result"}, + {"i8_checked_sub", "i8_checked_sub of [a, b] — i8 checked sub; N5 operands, int result"}, + {"i8_saturating_add", "i8_saturating_add of [a, b] — i8 saturating add; N5 operands, int result"}, + {"i8_saturating_mul", "i8_saturating_mul of [a, b] — i8 saturating mul; N5 operands, int result"}, + {"i8_saturating_sub", "i8_saturating_sub of [a, b] — i8 saturating sub; N5 operands, int result"}, + {"i8_wrapping_add", "i8_wrapping_add of [a, b] — i8 wrapping add; N5 operands, int result"}, + {"i8_wrapping_mul", "i8_wrapping_mul of [a, b] — i8 wrapping mul; N5 operands, int result"}, + {"i8_wrapping_sub", "i8_wrapping_sub of [a, b] — i8 wrapping sub; N5 operands, int result"}, {"index_of", "index_of of [haystack, needle] → first index, or -1. Non-string operands"}, {"inflate", "inflate — builtin; see docs/BUILTINS.md"}, {"is_dir", "is_dir of path — true if path names a directory, false for a plain file, a"}, @@ -194,6 +231,7 @@ static const char *builtin_docs[][2] = { {"norm", "norm of a → L2 (Euclidean) norm = sqrt(sum_i a[i]^2). Buffers and tensors."}, {"num", "num — builtin; see docs/BUILTINS.md"}, {"num_copy", "num_copy of val → fresh heap-allocated copy of a numeric Value."}, + {"num_type", "num_type — builtin; see docs/BUILTINS.md"}, {"numerical_grad", "numerical_grad of [loss_fn, param, eps]"}, {"numerical_grad_cols", "numerical_grad_cols of [loss_fn, matrix, col_indices, eps]"}, {"numerical_grad_rows", "numerical_grad_rows of [loss_fn, matrix, row_indices, eps]"}, @@ -218,7 +256,7 @@ static const char *builtin_docs[][2] = { {"read_text", "read_text of \"path\" → file contents as string; I/O failure raises EK_IO."}, {"record_history", "record_history of flag — enable (nonzero) or disable (0) per-assignment"}, {"recv", "recv — builtin; see docs/BUILTINS.md"}, - {"recv_timeout", "recv_timeout of [channel, ms] — bounded wait. Returns the value if one"}, + {"recv_timeout", "recv_timeout of [channel, ms] — milliseconds rounded half away from zero"}, {"regex_find", "regex_find — builtin; see docs/BUILTINS.md"}, {"regex_match", "regex_match — builtin; see docs/BUILTINS.md"}, {"regex_replace", "regex_replace of [string, pattern, replacement] -> string"}, @@ -305,6 +343,46 @@ static const char *builtin_docs[][2] = { {"try_parse", "try_parse of string → true if valid EigenScript syntax, false if not."}, {"try_recv", "try_recv of channel — non-blocking receive, returns null if empty"}, {"type", "type — builtin; see docs/BUILTINS.md"}, + {"u16_checked_add", "u16_checked_add of [a, b] — u16 checked add; N5 operands, int result"}, + {"u16_checked_mul", "u16_checked_mul of [a, b] — u16 checked mul; N5 operands, int result"}, + {"u16_checked_sub", "u16_checked_sub of [a, b] — u16 checked sub; N5 operands, int result"}, + {"u16_saturating_add", "u16_saturating_add of [a, b] — u16 saturating add; N5 operands, int result"}, + {"u16_saturating_mul", "u16_saturating_mul of [a, b] — u16 saturating mul; N5 operands, int result"}, + {"u16_saturating_sub", "u16_saturating_sub of [a, b] — u16 saturating sub; N5 operands, int result"}, + {"u16_wrapping_add", "u16_wrapping_add of [a, b] — u16 wrapping add; N5 operands, int result"}, + {"u16_wrapping_mul", "u16_wrapping_mul of [a, b] — u16 wrapping mul; N5 operands, int result"}, + {"u16_wrapping_sub", "u16_wrapping_sub of [a, b] — u16 wrapping sub; N5 operands, int result"}, + {"u32_checked_add", "u32_checked_add of [a, b] — u32 checked add; N5 operands, int result"}, + {"u32_checked_mul", "u32_checked_mul of [a, b] — u32 checked mul; N5 operands, int result"}, + {"u32_checked_sub", "u32_checked_sub of [a, b] — u32 checked sub; N5 operands, int result"}, + {"u32_saturating_add", "u32_saturating_add of [a, b] — u32 saturating add; N5 operands, int result"}, + {"u32_saturating_mul", "u32_saturating_mul of [a, b] — u32 saturating mul; N5 operands, int result"}, + {"u32_saturating_sub", "u32_saturating_sub of [a, b] — u32 saturating sub; N5 operands, int result"}, + {"u32_wrapping_add", "u32_wrapping_add of [a, b] — u32 wrapping add; N5 operands, int result"}, + {"u32_wrapping_mul", "u32_wrapping_mul of [a, b] — u32 wrapping mul; N5 operands, int result"}, + {"u32_wrapping_sub", "u32_wrapping_sub of [a, b] — u32 wrapping sub; N5 operands, int result"}, + {"u64_checked_add", "u64_checked_add of [a, b] — u64 checked add; N5 operands, int result"}, + {"u64_checked_mul", "u64_checked_mul of [a, b] — u64 checked mul; N5 operands, int result"}, + {"u64_checked_sub", "u64_checked_sub of [a, b] — u64 checked sub; N5 operands, int result"}, + {"u64_compare", "u64_compare of [a, b] — unsigned ordering of int64 bit patterns"}, + {"u64_div", "u64_div of [a, b] — unsigned division; zero raises value"}, + {"u64_rem", "u64_rem of [a, b] — unsigned remainder; zero raises value"}, + {"u64_saturating_add", "u64_saturating_add of [a, b] — u64 saturating add; N5 operands, int result"}, + {"u64_saturating_mul", "u64_saturating_mul of [a, b] — u64 saturating mul; N5 operands, int result"}, + {"u64_saturating_sub", "u64_saturating_sub of [a, b] — u64 saturating sub; N5 operands, int result"}, + {"u64_to_string", "u64_to_string of x — unsigned decimal text from an int64 bit pattern"}, + {"u64_wrapping_add", "u64_wrapping_add of [a, b] — u64 wrapping add; N5 operands, int result"}, + {"u64_wrapping_mul", "u64_wrapping_mul of [a, b] — u64 wrapping mul; N5 operands, int result"}, + {"u64_wrapping_sub", "u64_wrapping_sub of [a, b] — u64 wrapping sub; N5 operands, int result"}, + {"u8_checked_add", "u8_checked_add of [a, b] — u8 checked add; N5 operands, int result"}, + {"u8_checked_mul", "u8_checked_mul of [a, b] — u8 checked mul; N5 operands, int result"}, + {"u8_checked_sub", "u8_checked_sub of [a, b] — u8 checked sub; N5 operands, int result"}, + {"u8_saturating_add", "u8_saturating_add of [a, b] — u8 saturating add; N5 operands, int result"}, + {"u8_saturating_mul", "u8_saturating_mul of [a, b] — u8 saturating mul; N5 operands, int result"}, + {"u8_saturating_sub", "u8_saturating_sub of [a, b] — u8 saturating sub; N5 operands, int result"}, + {"u8_wrapping_add", "u8_wrapping_add of [a, b] — u8 wrapping add; N5 operands, int result"}, + {"u8_wrapping_mul", "u8_wrapping_mul of [a, b] — u8 wrapping mul; N5 operands, int result"}, + {"u8_wrapping_sub", "u8_wrapping_sub of [a, b] — u8 wrapping sub; N5 operands, int result"}, {"usleep", "usleep of microseconds — pause execution"}, {"values", "values — builtin; see docs/BUILTINS.md"}, {"vm_run_bytecode", "vm_run_bytecode of — assemble a chunk (and its nested"}, diff --git a/src/main.c b/src/main.c index 7259a8c08..1cf438a17 100644 --- a/src/main.c +++ b/src/main.c @@ -112,6 +112,24 @@ int main(int argc, char **argv) { if (getenv("EIGS_TSAN_MISORDERED_PUBLISH_CONTROL")) return tsan_misordered_publish_control(); #endif + /* EIGS_SEED_RANDOM= (#2120): seed the shared random stream before + * any program code runs, through seed_random's own path, so every run with + * one value draws one sequence (differential testing, reproduction). A + * malformed value is a startup error here: never a silent fallback to the + * clock, and never an exit from inside the runtime, which embedders share. */ + { + const char *seed_env = getenv("EIGS_SEED_RANDOM"); + if (seed_env && *seed_env) { /* empty means unset, as for EIGS_* flags */ + char *end = NULL; + errno = 0; + long seed = strtol(seed_env, &end, 10); + if (errno == ERANGE || end == seed_env || *end != '\0') { + fprintf(stderr, "EIGS_SEED_RANDOM: invalid integer: %s\n", seed_env); + return 2; + } + eigs_seed_random(seed); + } + } /* #413: a bundled binary (appended archive, trailer magic) extracts * itself and rewrites argv to run the extracted script — before ANY * flag handling, because in bundle mode the arguments belong to the @@ -239,7 +257,8 @@ int main(int argc, char **argv) { } /* Machine-readable lexer fingerprint used by the formatter safety gate. - * Values are hex bytes, so quotes, newlines and CR are unambiguous. */ + * String values are hex bytes; numbers carry an i/f kind prefix followed + * by exact decimal int digits or an exact hexadecimal float. */ if (argc >= 2 && strcmp(argv[1], "--dump-tokens") == 0) { if (argc != 3) { fprintf(stderr, "Usage: eigenscript --dump-tokens file.eigs\n"); @@ -259,8 +278,12 @@ int main(int argc, char **argv) { if (!rc) for (int i = 0; i < tl.count; i++) { Token *t = &tl.tokens[i]; printf("%d\t", (int)t->type); - if (t->type == TOK_NUM) printf("%a", t->num_val); - else if (t->str_val) { + if (t->type == TOK_NUM) { + if (t->number.is_int) + printf("i%lld", (long long)t->number.value.i64); + else + printf("f%a", t->number.value.f64); + } else if (t->str_val) { const unsigned char *p = (const unsigned char *)t->str_val; while (*p) printf("%02x", *p++); } diff --git a/src/model_infer.c b/src/model_infer.c index b60810d46..136d4942d 100644 --- a/src/model_infer.c +++ b/src/model_infer.c @@ -536,6 +536,23 @@ Value* builtin_eigen_generate(Value *arg) { * It takes either outcome before consulting the model, which may be absent * or replaced. Refusal messages carry recorded lengths, not live config. * Deterministic argument validation precedes the tape bracket. */ + /* N5 argument refusals consume no tape record. Validate without consulting + * model state: replay still serves recorded outcomes on an unloaded model. */ + double temperature = 0.1; + int max_tokens = 80; + /* Optional 4th arg: top_p. Absent (or outside (0,1)) keeps the historical + * fixed top-k=40 path, so every existing caller and recorded number is + * unchanged -- decoding policy is opt-in, not a silent default flip. */ + double top_p = 0.0; + if (arg && arg->type == VAL_LIST && arg->data.list.count >= 3) { + /* #1637: optional numbers -- null keeps the default, a bool raises. */ + int64_t mt = max_tokens; + eigs_slot_opt_float(list_slot(arg, 1), &temperature, "eigen_generate"); + eigs_slot_opt_int_range(list_slot(arg, 2), INT_MIN, INT_MAX, &mt, "eigen_generate"); + if (arg->data.list.count >= 4) eigs_slot_opt_float(list_slot(arg, 3), &top_p, "eigen_generate"); + if (g_has_error) return make_null(); + max_tokens = (int)mt; + } if (!arg || arg->type != VAL_LIST || arg->data.list.count < 3) { fprintf(stderr, "eigen_generate: requires [prompt_ids, temperature, max_tokens]\n"); TRACE_NONDET_RECORD("eigen_generate", make_list(0)); @@ -547,19 +564,6 @@ Value* builtin_eigen_generate(Value *arg) { TRACE_NONDET_RECORD("eigen_generate", make_list(0)); } - double temperature = 0.1; - int max_tokens = 80; - /* Optional 4th arg: top_p. Absent (or outside (0,1)) keeps the historical - * fixed top-k=40 path, so every existing caller and recorded number is - * unchanged -- decoding policy is opt-in, not a silent default flip. */ - double top_p = 0.0; - /* #1637: optional numbers -- null keeps the default, a bool raises. */ - double mt = max_tokens; - eigs_slot_opt_num(list_slot(arg, 1), &temperature, __func__); - eigs_slot_opt_num(list_slot(arg, 2), &mt, __func__); - if (arg->data.list.count >= 4) eigs_slot_opt_num(list_slot(arg, 3), &top_p, __func__); - if (g_has_error) return make_null(); - max_tokens = (int)mt; /* Validate prompt and token count */ int prompt_len = prompt_list->data.list.count; @@ -578,7 +582,10 @@ Value* builtin_eigen_generate(Value *arg) { int *prompt_ids = xcalloc(prompt_len, sizeof(int)); for (int i = 0; i < prompt_len; i++) { - prompt_ids[i] = (int)eigs_slot_num(list_slot(prompt_list, i), "eigen_generate"); /* #1637: a bool raises */ + int64_t token; + if (!eigs_slot_int_range(list_slot(prompt_list, i), INT_MIN, INT_MAX, + &token, "eigen_generate")) { free(prompt_ids); return make_null(); } + prompt_ids[i] = (int)token; /* #1637: a bool raises */ } if (g_has_error) { free(prompt_ids); return make_null(); } @@ -650,7 +657,7 @@ Value* builtin_eigen_generate(Value *arg) { Value *result = make_list(out_len); for (int i = 0; i < out_len; i++) { - list_append_owned(result, make_num((double)output_ids[i])); + list_append_owned(result, make_int(output_ids[i])); } free(output_ids); TRACE_NONDET_RECORD("eigen_generate", result); @@ -681,24 +688,27 @@ Value* builtin_eigen_eval_loss(Value *arg) { * provably unchanged. This is a grader: a laundered -1 loss is the * exact shape #975 exists to stop, since a caller cannot tell it from * a real score. */ - ARG_GUARD(1, "eigen_eval_loss", "[prompt_ids, target_id]", make_num(-1.0)); + ARG_GUARD(1, "eigen_eval_loss", "[prompt_ids, target_id]", make_float(-1.0)); } EIGS_VIEW(prompt_list_view); Value *prompt_list = list_get_view(arg, 0, &prompt_list_view); if (prompt_list->type != VAL_LIST) { fprintf(stderr, "eigen_eval_loss: prompt_ids must be a list\n"); - ARG_GUARD(1, "eigen_eval_loss", "a list of prompt ids", make_num(-1.0)); + ARG_GUARD(1, "eigen_eval_loss", "a list of prompt ids", make_float(-1.0)); } - if (list_elem_type(arg, 1) != VAL_NUM) { + if ((list_elem_type(arg, 1) != VAL_FLOAT && list_elem_type(arg, 1) != VAL_INT)) { fprintf(stderr, "eigen_eval_loss: target_id must be a number\n"); - ARG_GUARD(1, "eigen_eval_loss", "a numeric target_id", make_num(-1.0)); + ARG_GUARD(1, "eigen_eval_loss", "a numeric target_id", make_float(-1.0)); } - int target_id = (int)eigs_list_num(arg, 1, __func__); + int64_t target; + if (!eigs_list_int_range(arg, 1, INT_MIN, INT_MAX, &target, "eigen_eval_loss")) + return make_null(); + int target_id = (int)target; /* fs:ANSWER no model is loaded, so there is no loss to compute. Model * state, not an argument mistake — the identical call scores fine once a * checkpoint is loaded. */ - if (!g_model.loaded) return make_num(-1.0); + if (!g_model.loaded) return make_float(-1.0); int vocab_size = g_model.config.vocab_size; if (target_id < 0 || target_id >= vocab_size) { rt_error(EK_INDEX, 0, "eigen_eval_loss: target_id %d out of range [0,%d)", @@ -711,7 +721,7 @@ Value* builtin_eigen_eval_loss(Value *arg) { fprintf(stderr, "eigen_eval_loss: prompt must be non-empty\n"); /* fs:EMPTY an empty prompt has no logits to score, so there is no * loss; the list's TYPE was already accepted above. */ - return make_num(-1.0); + return make_float(-1.0); } if (prompt_len > g_model.config.max_seq_len) { rt_error(EK_VALUE, 0, @@ -722,7 +732,10 @@ Value* builtin_eigen_eval_loss(Value *arg) { int *prompt_ids = xcalloc(prompt_len, sizeof(int)); for (int i = 0; i < prompt_len; i++) { - int t = (int)eigs_slot_num(list_slot(prompt_list, i), "eigen_eval_loss"); /* #1637: a bool raises */ + int64_t token; + if (!eigs_slot_int_range(list_slot(prompt_list, i), INT_MIN, INT_MAX, + &token, "eigen_eval_loss")) { free(prompt_ids); return make_null(); } + int t = (int)token; /* #1637: a bool raises */ if (!g_has_error && (t < 0 || t >= vocab_size)) { rt_error(EK_INDEX, 0, "eigen_eval_loss: token id %d at prompt index %d out of range [0,%d)", @@ -749,8 +762,8 @@ Value* builtin_eigen_eval_loss(Value *arg) { /* fs:ANSWER the loss computed to NaN/Inf from a degenerate logit row — * a numeric outcome of this model on this input, not a bad argument. */ - if (isnan(loss) || isinf(loss)) return make_num(-1.0); - return make_num(loss); + if (isnan(loss) || isinf(loss)) return make_float(-1.0); + return make_float(loss); } Value* builtin_eigen_model_info(Value *arg) { diff --git a/src/model_io.c b/src/model_io.c index 9be74175f..16dd37c6a 100644 --- a/src/model_io.c +++ b/src/model_io.c @@ -64,21 +64,32 @@ static void replay_model_load_banner(Value *status) { Value *layers = dict_get_view(root, "n_layers", &layers_view); Value *d_model = dict_get_view(root, "d_model", &d_model_view); Value *d_ff = dict_get_view(root, "d_ff", &d_ff_view); - if (vocab && vocab->type == VAL_NUM && layers && layers->type == VAL_NUM && - d_model && d_model->type == VAL_NUM && d_ff && d_ff->type == VAL_NUM) { - g_model.config.vocab_size = (int)eigs_num_arg(vocab, __func__); - g_model.config.n_layers = (int)eigs_num_arg(layers, __func__); - g_model.config.d_model = (int)eigs_num_arg(d_model, __func__); - g_model.config.d_ff = (int)eigs_num_arg(d_ff, __func__); + if (vocab && val_is_number(vocab) && layers && val_is_number(layers) && + d_model && val_is_number(d_model) && d_ff && val_is_number(d_ff)) { + int64_t vs, nl, dm, df, ms = INT_MAX; + /* The status carries dimensions, not continuous values. Apply N5 + * before the C conversion, with the loader's supported bounds. Do + * not install a partially decoded configuration after a refusal. */ + int valid = eigs_int_arg_range(vocab, 1, VOCAB_SIZE, &vs, + "eigen_model_load replay vocab_size") && + eigs_int_arg_range(layers, 1, MAX_LAYERS, &nl, + "eigen_model_load replay n_layers") && + eigs_int_arg_range(d_model, 1, MAX_D_MODEL, &dm, + "eigen_model_load replay d_model") && + eigs_int_arg_range(d_ff, 1, MAX_D_FF, &df, + "eigen_model_load replay d_ff"); /* max_seq_len is added to statuses below. Older statuses still carry * the vocabulary; defer their context decision to the taped outcome. */ EIGS_VIEW(max_seq_view); Value *max_seq = dict_get_view(root, "max_seq_len", &max_seq_view); - if (max_seq && max_seq->type == VAL_NUM) - g_model.config.max_seq_len = (int)eigs_num_arg(max_seq, __func__); - else - g_model.config.max_seq_len = INT_MAX; - g_model.loaded = 1; + if (valid && max_seq && val_is_number(max_seq)) + valid = eigs_int_arg_range(max_seq, 1, MAX_SEQ_LEN, &ms, + "eigen_model_load replay max_seq_len"); + if (valid) { + ModelConfig cfg = { (int)vs, (int)dm, (int)nl, (int)df, (int)ms }; + g_model.config = cfg; + g_model.loaded = 1; + } } val_decref(root); } diff --git a/src/model_train.c b/src/model_train.c index e361e71f5..2a9676296 100644 --- a/src/model_train.c +++ b/src/model_train.c @@ -1460,9 +1460,10 @@ Value* builtin_native_train_step(Value *arg) { } float lr = 0.001f; - if (list_elem_type(arg, 2) == VAL_BOOL) { eigs_slot_num(list_slot(arg, 2), __func__); return make_null(); } /* #1637 */ - if (list_elem_type(arg, 2) == VAL_NUM) lr = eigs_list_num(arg, 2, __func__); + if (list_elem_type(arg, 2) == VAL_BOOL) { eigs_slot_float(list_slot(arg, 2), "native_train_step"); return make_null(); } /* #1637 */ + if ((list_elem_type(arg, 2) == VAL_FLOAT || list_elem_type(arg, 2) == VAL_INT)) lr = eigs_list_float(arg, 2, "native_train_step"); else if (list_elem_type(arg, 2) == VAL_STR) lr = strtod(list_get_ref(arg, 2)->data.str, NULL); + if (g_has_error) return make_null(); if (lr <= 0 || lr > 1) lr = 0.001f; int input_len = in_list->data.list.count; @@ -1477,7 +1478,12 @@ Value* builtin_native_train_step(Value *arg) { int *input_ids = xcalloc(input_len > 0 ? input_len : 1, sizeof(int)); int *output_ids = xcalloc(output_len > 0 ? output_len : 1, sizeof(int)); for (int i = 0; i < input_len; i++) { - input_ids[i] = (int)eigs_slot_num(list_slot(in_list, i), "native_train_step"); /* #1637: a bool raises */ + int64_t token; + if (!eigs_slot_int_range(list_slot(in_list, i), INT_MIN, INT_MAX, + &token, "native_train_step")) { + free(input_ids); free(output_ids); return make_null(); + } + input_ids[i] = (int)token; /* #1637: a bool raises */ if (!g_has_error && (input_ids[i] < 0 || input_ids[i] >= g_model.config.vocab_size)) { rt_error(EK_INDEX, 0, "native_train_step: token id %d at input index %d out of range [0,%d)", @@ -1486,7 +1492,12 @@ Value* builtin_native_train_step(Value *arg) { } } for (int i = 0; i < output_len && !g_has_error; i++) { - output_ids[i] = (int)eigs_slot_num(list_slot(out_list, i), "native_train_step"); + int64_t token; + if (!eigs_slot_int_range(list_slot(out_list, i), INT_MIN, INT_MAX, + &token, "native_train_step")) { + free(input_ids); free(output_ids); return make_null(); + } + output_ids[i] = (int)token; if (!g_has_error && (output_ids[i] < 0 || output_ids[i] >= g_model.config.vocab_size)) { rt_error(EK_INDEX, 0, "native_train_step: token id %d at output index %d out of range [0,%d)", diff --git a/src/opcode_operands.h b/src/opcode_operands.h index a60373f06..c8678f7b5 100644 --- a/src/opcode_operands.h +++ b/src/opcode_operands.h @@ -18,7 +18,7 @@ typedef enum { * switch is the single operand-role table used by every bytecode walker. */ static inline int op_verify_operands(uint8_t op8, VerifyRole roles[3]) { switch ((OpCode)op8) { - case OP_CONST: + case OP_CONST: case OP_INT_CONST: roles[0] = VR_CONST; return 1; case OP_GET_NAME: case OP_SET_NAME: case OP_SET_NAME_LOCAL: case OP_SET_FN_NAME_LOCAL: case OP_DOT_GET: case OP_DOT_SET: @@ -63,7 +63,8 @@ static inline int op_verify_operands(uint8_t op8, VerifyRole roles[3]) { case OP_LOCAL_IDX_DOT_GET: case OP_LOCAL_IDX_DOT_SET: roles[0] = VR_RAW; roles[1] = VR_RAW; roles[2] = VR_NAME; return 3; case OP_NULL: case OP_NUM_ZERO: case OP_NUM_ONE: case OP_TRUE: case OP_FALSE: - case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: + case OP_INT_ZERO: case OP_INT_ONE: + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: case OP_IDIV: case OP_BAND: case OP_BOR: case OP_BXOR: case OP_SHL: case OP_SHR: case OP_NEG: case OP_NOT: case OP_BNOT: case OP_EQ: case OP_NE: case OP_LT: case OP_GT: case OP_LE: case OP_GE: diff --git a/src/parser.c b/src/parser.c index f51651507..5436ffc3d 100644 --- a/src/parser.c +++ b/src/parser.c @@ -558,7 +558,7 @@ void free_ast(ASTNode *node) { } static int is_compound_assign(TokType t) { - return t >= TOK_PLUS_EQ && t <= TOK_SHR_EQ; + return (t >= TOK_PLUS_EQ && t <= TOK_SHR_EQ) || t == TOK_SLASH_SLASH_EQ; } static void compound_to_op(TokType t, char op[4]) { @@ -568,6 +568,7 @@ static void compound_to_op(TokType t, char op[4]) { case TOK_MINUS_EQ: op[0] = '-'; break; case TOK_STAR_EQ: op[0] = '*'; break; case TOK_SLASH_EQ: op[0] = '/'; break; + case TOK_SLASH_SLASH_EQ: op[0] = '/'; op[1] = '/'; break; case TOK_PERCENT_EQ: op[0] = '%'; break; case TOK_AMP_EQ: op[0] = '&'; break; case TOK_BITOR_EQ: op[0] = '|'; break; @@ -829,7 +830,7 @@ static ASTNode* parse_primary(Parser *p) { if (t->type == TOK_NUM) { p_advance(p); ASTNode *n = make_node(AST_NUM, t->line); - n->data.num = t->num_val; + n->data.number = t->number; while (p_cur(p)->type == TOK_LBRACKET) { if (chain_too_deep(p)) break; n = parse_subscript_suffix(p, n); @@ -856,7 +857,7 @@ static ASTNode* parse_primary(Parser *p) { if (t->type == TOK_TRUE || t->type == TOK_FALSE) { /* #1637 */ p_advance(p); ASTNode *b = make_node(AST_BOOL, t->line); - b->data.num = (t->type == TOK_TRUE) ? 1.0 : 0.0; + b->data.boolean = (t->type == TOK_TRUE); return b; } @@ -1202,11 +1203,13 @@ static ASTNode* parse_unary_body(Parser *p) { static ASTNode* parse_multiply(Parser *p) { ASTNode *left = parse_unary(p); - while (p_cur(p)->type == TOK_STAR || p_cur(p)->type == TOK_SLASH || p_cur(p)->type == TOK_PERCENT) { + while (p_cur(p)->type == TOK_STAR || p_cur(p)->type == TOK_SLASH || + p_cur(p)->type == TOK_SLASH_SLASH || p_cur(p)->type == TOK_PERCENT) { if (chain_too_deep(p)) break; char op[4] = {0}; if (p_cur(p)->type == TOK_STAR) op[0] = '*'; else if (p_cur(p)->type == TOK_SLASH) op[0] = '/'; + else if (p_cur(p)->type == TOK_SLASH_SLASH) { op[0] = '/'; op[1] = '/'; } else op[0] = '%'; Token *op_tok = p_advance(p); ASTNode *right = parse_unary(p); diff --git a/src/task.c b/src/task.c index 5a1134bc1..ebfece58c 100644 --- a/src/task.c +++ b/src/task.c @@ -254,9 +254,9 @@ static uint64_t sched_rng_next(TaskScheduler *s) { * the scheduler exists so the seed sticks even when set before the first * task_spawn. The interleaving stays deterministic — a DST varies the seed to * explore different interleavings, each fully reproducible. */ -void task_sched_set_seed(double seed) { +void task_sched_set_seed(int64_t seed) { TaskScheduler *s = sched_ensure(); - s->rng_state = (uint64_t)(int64_t)seed; /* integer seeds; fractions truncate */ + s->rng_state = (uint64_t)seed; /* N5 validated by task_sched_seed */ s->seeded = 1; } @@ -311,9 +311,9 @@ Value *task_sched_trace_read(void) { for (int i = 0; i < s->trace_count; i++) { const SchedTraceEntry *e = &s->trace[i]; Value *d = make_dict(4); - dict_set_owned(d, "seq", make_num((double)i)); - dict_set_owned(d, "tick", make_num(e->tick)); - dict_set_owned(d, "task", make_num(e->task)); + dict_set_owned(d, "seq", make_int(i)); + dict_set_owned(d, "tick", make_float(e->tick)); + dict_set_owned(d, "task", make_int((int64_t)e->task)); dict_set_owned(d, "cause", make_str(e->cause < SCAUSE__COUNT ? sched_cause_name[e->cause] : "?")); list_append_owned(out, d); @@ -581,7 +581,7 @@ int task_do_kill(int tid) { Value *ev = make_dict(3); dict_set_owned(ev, "kind", make_str(err_kind_name(EK_INTERRUPT))); dict_set_owned(ev, "message", make_str("task was killed")); - dict_set_owned(ev, "line", make_num(0)); + dict_set_owned(ev, "line", make_int(0)); t->error_value = ev; } for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { @@ -686,7 +686,7 @@ static void task_error_summary(Value *ev, char *out, size_t cap) { if (msg && msg->type == VAL_STR) { Value *line = dict_get_view(ev, "line", &lv); /* The checking accessor (#1637): the type is proven, so it cannot raise. */ - double ln = (line && line->type == VAL_NUM) ? eigs_num_arg(line, __func__) : 0; + double ln = (line && val_is_number(line)) ? eigs_float_arg(line, __func__) : 0; if (ln > 0) snprintf(out, cap, "line %.0f: %s", ln, msg->data.str); else diff --git a/src/trace.c b/src/trace.c index c34c3111d..d46b5adce 100644 --- a/src/trace.c +++ b/src/trace.c @@ -13,7 +13,7 @@ * O win per-binding observer window override (v3) * * Value encoding: - * numeric (immediate, tracked, or heap VAL_NUM) + * numeric (immediate, tracked, or heap VAL_FLOAT) * null VAL_NULL / immediate null slot * true|false * "" heap string, content truncated at TRACE_STR_MAX @@ -737,7 +737,7 @@ int trace_query_at(int kind, const char *interned_name, int line, EigsSlot *out) long long count = 0; for (int i = 0; i < e->lc_count && e->lc[i].line <= line; i++) count += e->lc[i].count; - *out = slot_from_num((double)count); + *out = slot_from_int(count); return 1; } @@ -752,7 +752,7 @@ int trace_query_at(int kind, const char *interned_name, int line, EigsSlot *out) double r = (kind == 3) ? h->entropy : (kind == 4) ? h->dH : observer_settledness(h->dH); /* #412: how = f(dH) */ - *out = slot_from_num(r); + *out = slot_from_f64(r); return 1; } @@ -801,7 +801,7 @@ int trace_query_when(int kind, const char *interned_name, long long ordinal, * answerable without the entry, so it too only needs the range check. */ if (kind == 2) { if (ordinal < 1 || ordinal > e->occ_total) return TRACE_WHEN_MISS; - *out = slot_from_num((double)ordinal); + *out = slot_from_int(ordinal); return TRACE_WHEN_HIT; } @@ -822,7 +822,7 @@ int trace_query_when(int kind, const char *interned_name, long long ordinal, double r = (kind == 3) ? o->entropy : (kind == 4) ? o->dH : observer_settledness(o->dH); /* #412: how = f(dH) */ - *out = slot_from_num(r); + *out = slot_from_f64(r); return TRACE_WHEN_HIT; } @@ -1584,11 +1584,16 @@ static void obs_cfg_sync(void) { * %.17g fields in one call could silently truncate the record. */ emit_tag("O"); tp_puts("cfg "); - tp_printf("%.17g ", binding->obs_dh_zero); - tp_printf("%.17g ", binding->obs_dh_small); - tp_printf("%.17g ", binding->obs_h_low); + char number[32]; + eigs_num_text(number, sizeof number, binding->obs_dh_zero); + tp_printf("%s ", number); + eigs_num_text(number, sizeof number, binding->obs_dh_small); + tp_printf("%s ", number); + eigs_num_text(number, sizeof number, binding->obs_h_low); + tp_printf("%s ", number); tp_printf("%d ", binding->obs_window); - tp_printf("%.17g\n", binding->obs_scale); + eigs_num_text(number, sizeof number, binding->obs_scale); + tp_printf("%s\n", number); } /* `set_observer_window of ["x", n]` — the per-binding override, recorded at @@ -1931,7 +1936,7 @@ static void replay_malformed_value(const char *value) { * API declare their kinds with eigs_trace_declare_kind; a name in neither * table is refused at replay. Bits are ValType (TK below). */ #define TK(t) (1u << (t)) -#define TK_NUM_ TK(VAL_NUM) +#define TK_NUM_ (TK(VAL_FLOAT) | TK(VAL_INT)) #define TK_STR_ TK(VAL_STR) #define TK_BOOL_ TK(VAL_BOOL) #define TK_NULL_ TK(VAL_NULL) @@ -2043,7 +2048,9 @@ static unsigned replay_expected_kinds(const char *fn) { /* "a bool", "a list or null" */ static void kinds_text(unsigned kinds, char *buf, size_t n) { - static const ValType order[] = {VAL_NUM, VAL_STR, VAL_BOOL, VAL_LIST, VAL_DICT, + /* Both numeric kinds have the one surface name, num. */ + if (kinds & TK(VAL_INT)) kinds |= TK(VAL_FLOAT); + static const ValType order[] = {VAL_FLOAT, VAL_STR, VAL_BOOL, VAL_LIST, VAL_DICT, VAL_BUFFER, VAL_NULL}; size_t used = 0; int first = 1; buf[0] = '\0'; @@ -2294,7 +2301,8 @@ static int unescape_string(const char **p, char *out, int out_cap) { * * Recognized shapes: * null | true | false immediates - * strtod-parseable + * exact signed int64, no decimal point + * decimal point or exponent marks its kind * "" escapes per unescape_string * [v, v, …] list * b[num, num, …] buffer (the leading 'b' disambiguates @@ -2436,6 +2444,29 @@ static Value *parse_value_p(const char **p) { * not replayable. Caller falls back to live source. */ if (c == '<' || (unsigned char)c == 0xE2) return NULL; + /* v8: a decimal digit token without a point/exponent is an int. Parse + * its magnitude exactly, including INT64_MIN, without routing through a + * double (9007199254740993 must retain its low bit on replay). */ + const char *digits = *p; + int negative = *digits == '-'; + if (negative) digits++; + const char *end_digits = digits; + while (*end_digits >= '0' && *end_digits <= '9') end_digits++; + if (end_digits != digits && at_token_boundary(*end_digits)) { + uint64_t limit = negative ? UINT64_C(9223372036854775808) + : UINT64_C(9223372036854775807); + uint64_t magnitude = 0; + for (const char *q = digits; q != end_digits; q++) { + unsigned digit = (unsigned)(*q - '0'); + if (magnitude > (limit - digit) / 10) return NULL; + magnitude = magnitude * 10 + digit; + } + int64_t i = negative + ? -(int64_t)(magnitude - (magnitude != 0)) - (magnitude != 0) + : (int64_t)magnitude; + *p = end_digits; + return make_int(i); + } char *end = NULL; double d = strtod(*p, &end); /* A genuine recording cannot contain NaN or infinity: num_guard rejects @@ -2443,10 +2474,11 @@ static Value *parse_value_p(const char **p) { * corrupt token at the common tape parser boundary, rather than exposing * it to consumers (some of which legitimately convert numbers to C * integer types). Returning NULL selects replay_malformed_value(), whose - * diagnostic names the offending serialized value. */ - if (end == *p || !isfinite(d)) return NULL; + * diagnostic names the offending serialized value. Phase 2's non-finite + * tape tokens are scheduled in docs/design/phase2-numbers.md §8, R2.5. */ + if (end == *p || !at_token_boundary(*end) || !isfinite(d)) return NULL; *p = end; - return make_num(d); + return make_float(d); } static Value *parse_value(const char *s) { @@ -2792,7 +2824,7 @@ int trace_source_record(const char *kind, const char *source, size_t size, Value *meta = make_list(3); list_append_owned(meta, make_bool(0)); list_append_owned(meta, make_str(message)); - list_append_owned(meta, make_num(0)); + list_append_owned(meta, make_int(0)); trace_nondet_value(kind, meta); val_decref(meta); rt_error(EK_IO, 0, "%s", message); @@ -2801,7 +2833,7 @@ int trace_source_record(const char *kind, const char *source, size_t size, Value *meta = make_list(3); list_append_owned(meta, make_bool(error == NULL)); list_append_owned(meta, make_str(error ? error : (identity ? identity : ""))); - list_append_owned(meta, make_num(error ? 0.0 : (double)size)); + list_append_owned(meta, make_int(error ? 0 : (int64_t)size)); trace_nondet_value(kind, meta); val_decref(meta); if (error) return 1; @@ -2825,24 +2857,34 @@ int trace_source_replay(const char *kind, char **source, long *size, if (!trace_replay_take(kind, &meta)) return -1; if (!meta || meta->type != VAL_LIST || meta->data.list.count != 3 || list_elem_type(meta, 0) != VAL_BOOL || list_elem_type(meta, 1) != VAL_STR || - list_elem_type(meta, 2) != VAL_NUM) { + (list_elem_type(meta, 2) != VAL_INT && list_elem_type(meta, 2) != VAL_FLOAT)) { val_decref(meta); rt_error(EK_IO, 0, "%s: malformed source record", kind); return -1; } int ok = list_get_ref(meta, 0)->data.boolean; const char *detail = list_get_ref(meta, 1)->data.str; - double taped_size = eigs_slot_elem_num(list_slot(meta, 2), __func__); + /* v8 metadata counts are exact ints. A float is refused with the + * existing numeric-size diagnostic, including an integral float. */ + if (list_elem_type(meta, 2) != VAL_INT) { + double bad_size = 0.0; + slot_f64_value(list_slot(meta, 2), &bad_size); + val_decref(meta); + rt_error(EK_IO, 0, "%s: invalid source size %.17g (maximum %u)", + kind, bad_size, TRACE_SOURCE_MAX); + return -1; + } + int64_t taped_size = 0; + slot_int_value(list_slot(meta, 2), &taped_size); if (!ok) { *error = xstrdup(detail); val_decref(meta); return 1; } - if (!isfinite(taped_size) || taped_size < 0 || - taped_size > (double)TRACE_SOURCE_MAX || taped_size != floor(taped_size)) { + if (taped_size < 0 || taped_size > TRACE_SOURCE_MAX) { val_decref(meta); rt_error(EK_IO, 0, "%s: invalid source size %.17g (maximum %u)", - kind, taped_size, TRACE_SOURCE_MAX); + kind, (double)taped_size, TRACE_SOURCE_MAX); return -1; } long n = (long)taped_size; @@ -2999,12 +3041,31 @@ static void write_string(const char *s) { tp_putc('"'); } +/* N6/T1: the point/exponent is the v8 float-kind marker. The shared + * shortest writer retains it for every integral float, including -0.0. */ +static void tape_float_text(char *buf, size_t nbuf, double n) { + eigs_num_text(buf, nbuf, n); +} + +static void write_float(double n) { + char buf[32]; + tape_float_text(buf, sizeof buf, n); + tp_puts(buf); +} + +static void write_int(int64_t n) { + char buf[32]; + eigs_int_text(buf, sizeof buf, n); + tp_puts(buf); +} + /* Format a heap or tracked Value*. Numeric heap values unwrap to their * number; collections show their size for at-a-glance scanning. */ static void write_value_ptr(Value *v) { if (!v) { tp_puts("null"); return; } switch (v->type) { - case VAL_NUM: tp_printf("%.17g", VAL_NUM_RAW(v)); break; + case VAL_FLOAT: write_float(VAL_F64_RAW(v)); break; + case VAL_INT: write_int(VAL_I64_RAW(v)); break; case VAL_NULL: tp_puts("null"); break; case VAL_STR: write_string(v->data.str); break; case VAL_LIST: tp_printf("", v->data.list.count); break; @@ -3020,11 +3081,12 @@ static void write_value_ptr(Value *v) { } static void write_slot(EigsSlot s) { - if (slot_is_num(s)) { tp_printf("%.17g", SLOT_NUM_RAW(s)); return; } + if (slot_is_f64(s)) { write_float(SLOT_F64_RAW(s)); return; } if (slot_is_null(s)) { tp_puts("null"); return; } if (slot_is_bool(s)) { tp_puts(slot_as_bool(s) ? "true" : "false"); return; } if (slot_is_heap(s)) { write_value_ptr(slot_as_ptr(s)); return; } - tp_puts(""); + if (slot_is_smi(s)) { write_int(slot_smi_val(s)); return; } + eigs_slot_refuse("write_slot", s); } static void trace_assign_ex(const char *name, EigsSlot value, int filtered, @@ -3146,7 +3208,18 @@ static int write_value_ptr_full(strbuf *buf, Value *v, int *budget, } WfAncestor here = {v, ancestors}; switch (v->type) { - case VAL_NUM: wf_printf(buf, budget, "%.17g", VAL_NUM_RAW(v)); break; + case VAL_FLOAT: { + char number[32]; + tape_float_text(number, sizeof number, VAL_F64_RAW(v)); + wf_puts(buf, number, budget); + break; + } + case VAL_INT: { + char number[32]; + eigs_int_text(number, sizeof number, VAL_I64_RAW(v)); + wf_puts(buf, number, budget); + break; + } case VAL_NULL: wf_puts(buf, "null", budget); break; case VAL_BOOL: wf_puts(buf, v->data.boolean ? "true" : "false", budget); break; case VAL_STR: write_string_full_len(buf, v->data.str, val_str_len(v), budget); break; @@ -3184,7 +3257,9 @@ static int write_value_ptr_full(strbuf *buf, Value *v, int *budget, int n = v->data.buffer.count; for (int i = 0; i < n; i++) { if (i) wf_puts(buf, ", ", budget); - wf_printf(buf, budget, "%.17g", v->data.buffer.data[i]); + char number[32]; + eigs_num_text(number, sizeof number, v->data.buffer.data[i]); + wf_puts(buf, number, budget); } wf_putc(buf, ']', budget); break; diff --git a/src/trace.h b/src/trace.h index 1e63d6a7f..eb060b26d 100644 --- a/src/trace.h +++ b/src/trace.h @@ -20,7 +20,7 @@ * with the same sentinel value_slot.h uses to avoid a re-typedef. */ #ifndef EIGENSCRIPT_EIGSSLOT_UNION_DEFINED #define EIGENSCRIPT_EIGSSLOT_UNION_DEFINED -typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_NUM_RAW (#1637) */ +typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_F64_RAW (#1637) */ #endif /* Tape format version (#411). Every tape's first line is a header record: @@ -29,7 +29,7 @@ typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_NUM_RA * value serialization, escaping, truncation markers, the header itself. * Replay refuses a tape whose format or runtime version differs from the * running binary: version-and-reject, never migrate (docs/TRACE.md). */ -#define TRACE_FORMAT_VERSION 8 /* v8: length-framed, continued N records (#1816) */ +#define TRACE_FORMAT_VERSION 9 /* v9: combine phase2 v8 int/float kinds with main v8 continued N records; reject either older schema */ #define TRACE_STREAM_KEY_MAX 1024 /* B payload parser shared with the non-executing tape reader. key_hex points @@ -351,7 +351,7 @@ int trace_query_prev(const char *interned_name, EigsSlot *out); * * `kind` mirrors the interrogative encoding: * 0 (what), 6 (prev) → return historical slot - * 2 (when) → return assignment count up to that line (as immediate num) + * 2 (when) → return exact int assignment count (SMI or owned box) * 1 (who) → return binding name (string) * 3/4/5 (where/why/how)→ observer snapshot captured at that assign * (requires g_trace_obs_hist; miss returns 0) diff --git a/src/value_slot.h b/src/value_slot.h index b1ba7bbb0..f701ae840 100644 --- a/src/value_slot.h +++ b/src/value_slot.h @@ -1,15 +1,17 @@ /* * EigenScript NaN-boxed slot encoding. * - * An EigsSlot is a 64-bit union aliasing a double. Numbers live as + * An EigsSlot is a 64-bit union aliasing a double. Floats live as * immediate doubles. Pointers and other immediates encode into the * unused QNaN-with-sign-set bit pattern, which num_guard() makes * unreachable from user numbers (NaN -> 0, +/-Inf -> +/-1e308). * * Tag layout (high 16 bits): + * < 0xFFF8 : a double (slot_is_f64) * 0xFFF8 tag 0 : NULL (payload ignored) * 0xFFF9 tag 1 : BOOL (low bit = value) - * 0xFFFA tag 2 : reserved (SMI) (reserved for future small-int) + * 0xFFFA tag 2 : SMI (an int in [-2^47, 2^47): 48-bit two's + * complement payload; #1758) * 0xFFFB tag 3 : HEAP pointer (str/list/dict/fn/builtin/buffer/tb) * 0xFFFC tag 4 : reserved (#262 Step E: was TRACKED — removed) * 0xFFFD tag 5 : reserved @@ -30,7 +32,7 @@ #ifndef EIGENSCRIPT_EIGSSLOT_UNION_DEFINED #define EIGENSCRIPT_EIGSSLOT_UNION_DEFINED -typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_NUM_RAW (#1637) */ +typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_F64_RAW (#1637) */ #endif #define SLOT_QNAN_MASK 0xFFF8000000000000ULL @@ -42,36 +44,89 @@ typedef union { double d_; uint64_t u; } EigsSlot; /* d_: read via SLOT_NUM_RA #define SLOT_TRUE_BITS 0xFFF9000000000001ULL #define TAG_BOOL 0xFFF9000000000000ULL +#define TAG_SMI 0xFFFA000000000000ULL #define TAG_HEAP 0xFFFB000000000000ULL #define TAG_SENTINEL 0xFFFF000000000000ULL +/* #1758: the SMI range, [-2^47, 2^47), and the payload's sign bit. */ +#define SLOT_SMI_MIN (-(INT64_C(1) << 47)) +#define SLOT_SMI_MAX ((INT64_C(1) << 47) - 1) +#define SLOT_SMI_SIGN 0x0000800000000000ULL + +/* A slot reached a reader that does not handle its tag: a broken caller + * contract, never a program's fault. Prints the reader and the bits, then + * aborts (eigenscript.c). */ +#if defined(__GNUC__) || defined(__clang__) +#define EIGS_SLOT_NORETURN __attribute__((noreturn, cold)) +#else +#define EIGS_SLOT_NORETURN +#endif +EIGS_SLOT_NORETURN void eigs_slot_refuse(const char *who, EigsSlot s); +/* slot_as_ptr refuses a slot that is not a heap pointer in sanitizer builds + * and under -DEIGS_SLOT_CHECKED (#1758 R1.1: an SMI must never be read as a + * pointer). Release keeps the bare mask: it is on every hot path. */ +#if defined(__SANITIZE_ADDRESS__) || defined(EIGS_SLOT_CHECKED) +# define EIGS_SLOT_CHECKS 1 +#elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# define EIGS_SLOT_CHECKS 1 +# endif +#endif + +/* #1758 R1.3: the pre-flip source trap is retired now that SMI values are + * valid. Keep its build spelling for through-round gate invocations. */ +#define SLOT_SMI_TRAP(s) ((void)0) + /* Predicates */ -static inline int slot_is_num(EigsSlot s) { +/* #1758: a double. Was slot_is_num; renamed so that every site that meant + * "a number" had to decide what an int (SMI) does there. */ +static inline int slot_is_f64(EigsSlot s) { + SLOT_SMI_TRAP(s); return (s.u & SLOT_QNAN_MASK) != SLOT_QNAN_MASK; } -static inline int slot_is_null(EigsSlot s) { return s.u == SLOT_NULL_BITS; } -static inline int slot_is_bool(EigsSlot s) { return (s.u & SLOT_TAG_MASK) == TAG_BOOL; } -static inline int slot_is_heap(EigsSlot s) { return (s.u & SLOT_TAG_MASK) == TAG_HEAP; } +static inline int slot_is_null(EigsSlot s) { SLOT_SMI_TRAP(s); return s.u == SLOT_NULL_BITS; } +static inline int slot_is_bool(EigsSlot s) { SLOT_SMI_TRAP(s); return (s.u & SLOT_TAG_MASK) == TAG_BOOL; } +static inline int slot_is_smi(EigsSlot s) { SLOT_SMI_TRAP(s); return (s.u & SLOT_TAG_MASK) == TAG_SMI; } +static inline int slot_is_heap(EigsSlot s) { SLOT_SMI_TRAP(s); return (s.u & SLOT_TAG_MASK) == TAG_HEAP; } /* #262 Step E: TAG_TRACKED is gone — a number is always an immediate, never a * heap-boxed observer-tracked Value. Heap is the only pointer tag. */ -static inline int slot_is_ptr(EigsSlot s) { return (s.u & SLOT_TAG_MASK) == TAG_HEAP; } -__attribute__((unused)) -static inline int slot_is_numeric(EigsSlot s) { return slot_is_num(s); } +static inline int slot_is_ptr(EigsSlot s) { SLOT_SMI_TRAP(s); return (s.u & SLOT_TAG_MASK) == TAG_HEAP; } /* Accessors (caller must check the predicate first) */ /* #1637 round 4: the ONE raw way into a slot's double (an lvalue), for a slot - * proven a number (slot_is_num). Every use sits in a function listed in + * proven a double (slot_is_f64). Every use sits in a function listed in * tools/num_read_allowlist.txt with its count (tools/num_read_check.sh). */ -#define SLOT_NUM_RAW(s) ((s).d_) -static inline int slot_as_bool(EigsSlot s) { return (int)(s.u & 1ULL); } -static inline Value *slot_as_ptr(EigsSlot s) { return (Value*)(uintptr_t)(s.u & SLOT_PAYLOAD_MASK); } +#define SLOT_F64_RAW(s) ((s).d_) +static inline int slot_as_bool(EigsSlot s) { SLOT_SMI_TRAP(s); return (int)(s.u & 1ULL); } +static inline Value *slot_as_ptr(EigsSlot s) { + SLOT_SMI_TRAP(s); +#ifdef EIGS_SLOT_CHECKS + if (__builtin_expect(!slot_is_heap(s), 0)) eigs_slot_refuse("slot_as_ptr", s); +#endif + return (Value*)(uintptr_t)(s.u & SLOT_PAYLOAD_MASK); +} #define slot_as_heap slot_as_ptr +/* The int an SMI holds, sign-extending payload bit 47 without an + * implementation-defined right shift (design 2.1). */ +static inline int64_t slot_smi_val(EigsSlot s) { + SLOT_SMI_TRAP(s); + return (int64_t)((s.u & SLOT_PAYLOAD_MASK) ^ SLOT_SMI_SIGN) - (int64_t)SLOT_SMI_SIGN; +} /* Constructors */ -static inline EigsSlot slot_from_num(double d) { EigsSlot s; SLOT_NUM_RAW(s) = d; return s; } +/* #1758: the ONE double writer. Round 2 canonicalizes a NaN here. */ +static inline EigsSlot slot_from_f64(double d) { EigsSlot s; SLOT_F64_RAW(s) = d; return s; } +static inline EigsSlot slot_from_heap(Value *v); +/* #1758: the ONE int writer: an SMI inside [-2^47, 2^47), otherwise an + * owned VAL_INT heap box. */ +static inline EigsSlot slot_from_int(int64_t i) { + if (__builtin_expect(i < SLOT_SMI_MIN || i > SLOT_SMI_MAX, 0)) + return slot_from_heap(make_int(i)); + EigsSlot s; s.u = TAG_SMI | ((uint64_t)i & SLOT_PAYLOAD_MASK); return s; +} static inline EigsSlot slot_null(void) { EigsSlot s; s.u = SLOT_NULL_BITS; return s; } static inline EigsSlot slot_true(void) { EigsSlot s; s.u = SLOT_TRUE_BITS; return s; } static inline EigsSlot slot_false(void) { EigsSlot s; s.u = SLOT_FALSE_BITS; return s; } @@ -102,6 +157,7 @@ static inline EigsSlot slot_from_heap(Value *v) { * The outer `& SLOT_QNAN_MASK` branch peels off numbers in one * predicted-not-taken test with zero memory access. */ static inline void slot_incref(EigsSlot s) { + SLOT_SMI_TRAP(s); if ((s.u & SLOT_QNAN_MASK) == SLOT_QNAN_MASK) { uint64_t tag = s.u & SLOT_TAG_MASK; if (tag == TAG_HEAP) { @@ -115,6 +171,7 @@ static inline void slot_incref(EigsSlot s) { } static inline void slot_decref(EigsSlot s) { + SLOT_SMI_TRAP(s); if ((s.u & SLOT_QNAN_MASK) == SLOT_QNAN_MASK) { uint64_t tag = s.u & SLOT_TAG_MASK; if (tag == TAG_HEAP) { @@ -137,22 +194,51 @@ static inline void slot_decref(EigsSlot s) { } /* Truthiness — used by JUMP_IF_FALSE / JUMP_IF_TRUE fast paths. - * Immediate-number truthiness is a non-zero double; null and false - * are falsy; everything else (heap, tracked) defers to caller-side - * is_truthy() for now (Phase A keeps semantics identical). */ + * Immediate-number truthiness is a non-zero double, or a non-zero int + * (#1758: an SMI is 0 exactly when its payload is 0); null and false + * are falsy; everything else (heap) defers to caller-side is_truthy(). */ __attribute__((unused)) static inline int slot_truthy_immediate(EigsSlot s, int *out_decided) { - if (slot_is_num(s)) { *out_decided = 1; return SLOT_NUM_RAW(s) != 0.0; } + if (slot_is_f64(s)) { *out_decided = 1; return SLOT_F64_RAW(s) != 0.0; } if (slot_is_null(s) || s.u == SLOT_FALSE_BITS) { *out_decided = 1; return 0; } if (s.u == SLOT_TRUE_BITS) { *out_decided = 1; return 1; } + if (slot_is_smi(s)) { *out_decided = 1; return slot_smi_val(s) != 0; } *out_decided = 0; return 0; } +/* #1758: a float's double, from an immediate or a VAL_FLOAT box (the VM boxes + * numbers on purpose: the lifts, a for-in variable). 0 for anything else, + * an int (an SMI, later a VAL_INT box) included: the caller sends it to its + * slow path; *out is written only when the answer is 1. Replaces vm.c's + * slot_as_double. + * Leave it so: its callers sit in vm_run_ex's hot arms, and making it write + * *out on every path (0.0, or the slot's bits, on the failure path) changed + * vm_run_ex's code and cost +3.8% Ir on a JIT-off float loop, as much as + * initialising the callers' locals did (#1775, measured with callgrind). */ +static inline int slot_f64_value(EigsSlot s, double *out) { + if (slot_is_f64(s)) { *out = SLOT_F64_RAW(s); return 1; } + if (slot_is_heap(s)) { + Value *v = slot_as_ptr(s); + if (v->type == VAL_FLOAT) { *out = VAL_F64_RAW(v); return 1; } /* a VAL_INT box: 0 */ + } + return 0; +} +/* #1758: an int's value. R1.1 knows the SMI; R1.2a adds the VAL_INT box + * arm with the type it names. */ +static inline int slot_int_value(EigsSlot s, int64_t *out) { + if (slot_is_smi(s)) { *out = slot_smi_val(s); return 1; } + if (slot_is_heap(s)) { + Value *v = slot_as_ptr(s); + if (v->type == VAL_INT) { *out = VAL_I64_RAW(v); return 1; } + } + return 0; +} + /* Boundary shims declared here, defined in eigenscript.c. * * slot_from_value (takes ownership of the input ref): - * - VAL_NUM -> immediate double, input decref'd (#262 Step E: numbers never + * - VAL_FLOAT -> immediate double, input decref'd (#262 Step E: numbers never * carry observer state, so there is no tracked-pointer case). * - VAL_NULL -> SLOT_NULL (incref skipped; null is borrowed). * - any heap type -> TAG_HEAP pointer (incref). diff --git a/src/vm.c b/src/vm.c index 5e0aed9d4..bf91bf663 100644 --- a/src/vm.c +++ b/src/vm.c @@ -64,6 +64,16 @@ int vm_sandbox_work_charge(uint64_t amount) { return 1; } +/* #1758 N4: floor modulo. Avoid a - floor(a/b)*b: a/b can overflow or + * round across an integer boundary. As in Lua's luai_nummod, correct fmod's + * truncating remainder to have the divisor's sign. The executor uses only + * this floor result, never the truncating remainder. */ +static double vm_floor_mod(double a, double b) { + double rem = fmod(a, b); + if (rem != 0.0 && ((rem < 0.0) != (b < 0.0))) rem += b; + return rem; +} + /* #262 Phase-3 D: read the last-observed trajectory (dH, entropy) for the * observer loop-stall check. Prefers the slot model (the default); falls back * to the global last-observer Value when the slot isn't populated (e.g. a @@ -115,13 +125,15 @@ static void obs_dump_num(double n, char *buf, size_t nbuf) { * out-of-scope slot-value race class; a summary reads only the header * words). No allocation. */ static void obs_dump_value(EigsSlot s, char *buf, size_t nbuf) { - if (slot_is_num(s)) { obs_dump_num(SLOT_NUM_RAW(s), buf, nbuf); return; } + if (slot_is_f64(s)) { obs_dump_num(SLOT_F64_RAW(s), buf, nbuf); return; } + if (slot_is_smi(s)) { snprintf(buf, nbuf, "%lld", (long long)slot_smi_val(s)); return; } /* #1758: exact digits */ if (slot_is_null(s)) { snprintf(buf, nbuf, "null"); return; } if (slot_is_bool(s)) { snprintf(buf, nbuf, "%s", slot_as_bool(s) ? "true" : "false"); return; } if (slot_is_ptr(s)) { Value *v = slot_as_ptr(s); switch (v->type) { - case VAL_NUM: obs_dump_num(VAL_NUM_RAW(v), buf, nbuf); break; + case VAL_FLOAT: obs_dump_num(VAL_F64_RAW(v), buf, nbuf); break; + case VAL_INT: eigs_int_text(buf, nbuf, VAL_I64_RAW(v)); break; /* #1758 R1.2a */ case VAL_STR: { size_t o = 0, i; buf[o++] = '"'; @@ -209,8 +221,10 @@ static void obs_dump_scope(const char *scope, Env *e, int shared, * so refresh inline — observer_entropy_now would self-deadlock). * The band classifies the same refreshed view. */ ObserverSlot qs = *os; - if (slot_is_num(s)) { - qs.entropy = observer_entropy_of_num(SLOT_NUM_RAW(s)); + if (slot_is_f64(s)) { + qs.entropy = observer_entropy_of_num(SLOT_F64_RAW(s)); + } else if (slot_is_smi(s)) { /* #1758: an int, as its double */ + qs.entropy = observer_entropy_of_num((double)slot_smi_val(s)); } else if (slot_is_bool(s)) { qs.entropy = compute_entropy(make_bool(slot_as_bool(s))); /* #1637 */ } else if (slot_is_ptr(s)) { @@ -399,12 +413,23 @@ static int vm_slot_value_opaque(Env *e, int idx) { * source line, and a binding assigned inside the block has no `used` slot at * all, so a classifier-level check is never reached on the path that hangs. */ int observer_predicate_at(Env *e, int idx, int kind, int require_used) { - if (vm_slot_value_opaque(e, idx)) return 0; + if (!e || idx < 0 || vm_slot_value_opaque(e, idx)) return 0; + /* The entropy walk can synchronize a live namespace and take #607; + * prepare it before the fold hold. The classification below may inspect + * eq_last's detached snapshot (S10-D1), whose ownership and window arrays + * must remain protected against a concurrent shared-binding update. */ + double entropy; + int have_entropy = observer_entropy_now(e, idx, &entropy); + env_dump_lock(e); const ObserverSlot *s = env_obs_slot(e, idx); - if (!s) return 0; - if (require_used && !s->used) return 0; - ObserverSlot q = vm_slot_query_view(e, idx, s); - return vm_slot_predicate(&q, (uint16_t)kind); + int result = 0; + if (s && (!require_used || s->used)) { + ObserverSlot q = *s; + if (have_entropy) q.entropy = entropy; + result = vm_slot_predicate(&q, (uint16_t)kind); + } + env_dump_unlock(e); + return result; } /* Phase 5: VM execution state (g_vm), loop-stall accounting @@ -688,7 +713,7 @@ static inline void dict_set_cached(Value *dict, const char *key, uint32_t h, Val } /* Slot-aware dict set: for the DMG register-write hot path. If the dict - * cache hits and the existing slot is an exclusive untracked VAL_NUM, + * cache hits and the existing slot is an exclusive untracked VAL_FLOAT, * mutate data.num in place — no allocation, no refcount churn. Returns * 1 if the in-place fast path fired; 0 means the caller must materialize * and call dict_set_cached. */ @@ -703,7 +728,7 @@ dict_set_cached_immediate(Value *dict, const char *key, uint32_t h, double num) /* Slot storage (#1665 arm D B10): a cache hit is a plain * slot store of the already-guarded immediate, whatever the * field held before (the old occupant is released). */ - return dict_value_rewrite_num(dict, ce->index, num); + return dict_value_rewrite_f64(dict, ce->index, num); } } return 0; @@ -783,9 +808,9 @@ static inline EigsSlot slot_bridge_wrap(Value *v) { } static inline Value *slot_bridge_unwrap(EigsSlot s) { - if (slot_is_num(s)) { + if (slot_is_f64(s)) { /* Materialize immediate -> fresh Value. Caller owns one ref. */ - return make_num(SLOT_NUM_RAW(s)); + return make_float(SLOT_F64_RAW(s)); } if (slot_is_null(s)) { return make_null(); @@ -793,6 +818,8 @@ static inline Value *slot_bridge_unwrap(EigsSlot s) { if (slot_is_bool(s)) { return make_bool(slot_as_bool(s)); } + /* #1758: an int materializes through slot_to_value, the one SMI boxer. */ + if (slot_is_smi(s)) return slot_to_value(s); /* Heap or tracked: just unwrap the pointer (ref already in slot). */ return slot_as_ptr(s); } @@ -828,10 +855,13 @@ static inline Value *vm_slot_lift(int idx) { EigsSlot s = g_vm.stack[idx]; if (slot_is_ptr(s)) return slot_as_ptr(s); Value *v; - if (slot_is_num(s)) v = make_num(SLOT_NUM_RAW(s)); + if (slot_is_f64(s)) v = make_float(SLOT_F64_RAW(s)); else if (slot_is_null(s)) v = make_null(); else if (slot_is_bool(s)) v = make_bool(slot_as_bool(s)); - else v = make_null(); + /* #1758: an int is boxed on purpose, like a double (design 2.1); a + * reserved tag used to become null here, silently. */ + else if (slot_is_smi(s)) v = slot_to_value(s); + else eigs_slot_refuse("vm_slot_lift", s); g_vm.stack[idx] = slot_from_heap(v); return v; } @@ -859,26 +889,82 @@ static inline void vm_push_slot(EigsSlot s) { } /* Slot-aware truthiness — covers immediate num, null, bool, and falls - * back to is_truthy() for heap/tracked. */ + * back to is_truthy() for heap/tracked. #1758: an int is truthy when it is + * not 0, like a double; a reserved tag aborts instead of being dereferenced. */ static inline int slot_truthy(EigsSlot s) { - if (slot_is_num(s)) return SLOT_NUM_RAW(s) != 0.0; + if (slot_is_f64(s)) return SLOT_F64_RAW(s) != 0.0; if (slot_is_null(s)) return 0; if (slot_is_bool(s)) return slot_as_bool(s); - return is_truthy(slot_as_ptr(s)); + if (slot_is_heap(s)) return is_truthy(slot_as_ptr(s)); + if (slot_is_smi(s)) return slot_smi_val(s) != 0; + eigs_slot_refuse("slot_truthy", s); } -/* Pull a double out of a slot if it represents a number. Covers both - * immediate doubles and heap/tracked VAL_NUMs (which still appear from - * env loads and from the constant pool until B-3 flips those). */ -static inline int slot_as_double(EigsSlot s, double *out) { - if (slot_is_num(s)) { *out = SLOT_NUM_RAW(s); return 1; } - if (slot_is_ptr(s)) { - Value *v = slot_as_ptr(s); - if (v->type == VAL_NUM) { *out = VAL_NUM_RAW(v); return 1; } +/* #1758 R1.3: floats keep the existing fast paths. Integer and mixed + * arithmetic is computed here, outside the JIT/leaf float executors. */ +static inline __attribute__((always_inline)) EigsSlot vm_int_arith(int64_t a, int64_t b, char op, int line) { + int64_t r; + int overflow; + if (op == '+') overflow = __builtin_add_overflow(a, b, &r); + else if (op == '-') overflow = __builtin_sub_overflow(a, b, &r); + else overflow = __builtin_mul_overflow(a, b, &r); + if (overflow) { + rt_error(EK_VALUE, line, "integer overflow in '%c'", op); + return slot_null(); + } + return slot_from_int(r); +} + +/* A number as an int64 for the bitwise operators (#1740, #1758 N8). + * An int never round-trips through double; float operands retain the exact + * integer-representation rule. Return -1 for a type error, 0 for value. */ +static int vm_bit_operand(EigsSlot s, int64_t *out) { + double f; + if (slot_int_value(s, out)) return 1; + if (slot_f64_value(s, &f)) return num_to_i64_exact(f, out); + return -1; +} + +/* Exact N2 ordering; 2 means unordered, and 0 return means not numbers. */ +static int vm_number_compare(EigsSlot a, EigsSlot b, int *out) { + int64_t ai, bi; + double af, bf; + int ia = slot_int_value(a, &ai), ib = slot_int_value(b, &bi); + if (ia && ib) { *out = (ai > bi) - (ai < bi); return 1; } + if (ia && slot_f64_value(b, &bf)) { *out = eigs_cmp_int_float(ai, bf); return 1; } + if (ib && slot_f64_value(a, &af)) { + int c = eigs_cmp_int_float(bi, af); + *out = c == 2 ? 2 : -c; + return 1; } return 0; } +/* Iterator state is exact int metadata made by make_iter_state (S9-K1). + * Raw bytecode may supply floats too. Check the C range BEFORE conversion. */ +static int vm_iter_index(EigsSlot s, int *out) { + int64_t i; + if (slot_int_value(s, &i)) { + if (i < INT_MIN || i > INT_MAX) return 0; + *out = (int)i; + return 1; + } + double f; + if (slot_f64_value(s, &f)) { + if (!(f >= INT_MIN && f <= INT_MAX)) return 0; + *out = (int)f; + return 1; + } + return 0; +} +static int vm_iter_limit(EigsSlot s, int len) { + int i; + if (vm_iter_index(s, &i)) return i < len ? (i < 0 ? 0 : i) : len; + int64_t big; + if (slot_int_value(s, &big) && big < 0) return 0; + return len; +} + static inline uint16_t read_u16(uint8_t *ip) { return ip[0] | (ip[1] << 8); } @@ -923,7 +1009,7 @@ static int import_collision_first_report(const char *name) { static Value *make_iter_state(Value *iterable) { Value *state = make_list(3); list_append(state, iterable); - Value *idx = make_num(0); + Value *idx = make_int(0); list_append(state, idx); val_decref(idx); /* #491: snapshot the length at loop entry (items[2]). ITER_NEXT bounds @@ -936,7 +1022,7 @@ static Value *make_iter_state(Value *iterable) { int snap = 0; if (iterable && iterable->type == VAL_LIST) snap = iterable->data.list.count; else if (iterable && iterable->type == VAL_BUFFER) snap = iterable->data.buffer.count; - Value *slen = make_num((double)snap); + Value *slen = make_int(snap); list_append(state, slen); val_decref(slen); return state; @@ -1155,19 +1241,19 @@ static inline Env *env_resolve_store(Env *start, const char *name, uint32_t h, /* #1063/#2016: the ONE implementation of "store TOS into fn-local slot", shared by * CASE(SET_LOCAL) and the JIT's out-of-line path (jit_helper_set_local), so * the two can never disagree. The in-place branch is load-bearing beyond - * speed: g_last_observer may alias the exclusive VAL_NUM being overwritten, + * speed: g_last_observer may alias the exclusive VAL_FLOAT being overwritten, * and a swap+decref would free it under the observer. A parameter arrives * with assign_counts=1; every user SET_LOCAL advances that same counter, just * as a name store does. Runtime bookkeeping stores pass count_assignment=0. */ -static inline void vm_store_local_slot(Env *e, int slot, EigsSlot tos, +static inline __attribute__((always_inline)) void vm_store_local_slot(Env *e, int slot, EigsSlot tos, int count_assignment) { - if (slot_is_num(tos)) { + if (slot_is_f64(tos)) { EigsSlot ex_s = e->values[slot]; if (slot_is_heap(ex_s)) { Value *existing = slot_as_ptr(ex_s); - if (existing && existing->type == VAL_NUM && + if (existing && existing->type == VAL_FLOAT && existing->refcount == 1) { - VAL_NUM_RAW(existing) = SLOT_NUM_RAW(tos); + VAL_F64_RAW(existing) = SLOT_F64_RAW(tos); if (count_assignment && e->assign_counts) e->assign_counts[slot]++; return; @@ -1250,8 +1336,9 @@ void jit_helper_set_name(EigsChunk *chunk, int idx) { return; } env_set_local_pre_interned_slot(env_binding_home(start), name, chunk->intern_tbl, h, s); + /* Resolver outputs are initialized only on a successful lookup. */ Env *t2 = env_resolve_chain(start, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && t2 == start) { /* #1171 */ + if (!g_vm_multithreaded && t2 && t2 == start) { /* #1171 */ ic->starting_env = start; ic->starting_ver = start->binding_version; ic->target_ver = start->binding_version; @@ -1281,7 +1368,7 @@ void jit_helper_set_name_local(EigsChunk *chunk, int idx) { env_set_local_pre_interned_slot(start, name, chunk->intern_tbl, h, s); int slot_idx, depth; Env *target = env_resolve_chain(start, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && target == start) { /* #1171 */ + if (!g_vm_multithreaded && target && target == start) { /* #1171 */ ic->starting_env = start; ic->starting_ver = start->binding_version; ic->target_ver = start->binding_version; @@ -1330,7 +1417,7 @@ void jit_helper_set_fn_name_local(EigsChunk *chunk, int idx) { env_set_local_pre_interned_slot(target, name, chunk->intern_tbl, h, s); int slot_idx, depth; Env *resolved = env_resolve_chain(target, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && resolved == target) { /* #1171 */ + if (!g_vm_multithreaded && resolved && resolved == target) { /* #1171 */ ic->starting_env = target; ic->starting_ver = target->binding_version; ic->target_ver = target->binding_version; @@ -1353,7 +1440,7 @@ int jit_helper_local_idx_get(int slot, int idx) { int i = idx; if (target->type == VAL_BUFFER) { if (i < target->data.buffer.count) { - vm_push_slot(slot_from_num(buffer_read_num(target, i))); + vm_push_slot(slot_from_f64(buffer_read_f64(target, i))); } else { rt_error(EK_INDEX, g_vm.current_line, "buffer index %d out of range (length %d)", @@ -1510,14 +1597,14 @@ void jit_helper_dot_set(EigsChunk *chunk, int name_idx) { } /* Slot fast path (mirrors CASE(LOCAL_DOT_SET)): an immediate-num * write into an exclusive untracked num field mutates in place — - * no make_num, no refcount churn. Was 2 make_num + 1.5 free_value + * no make_float, no refcount churn. Was 2 make_float + 1.5 free_value * per DMG step via the vm_pop materialization below. */ EigsSlot val_s = g_vm.stack[g_vm.sp - 1]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 2]; - if (slot_is_num(val_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(val_s) && slot_is_ptr(tgt_s)) { Value *target = slot_as_ptr(tgt_s); if (target->type == VAL_DICT && - dict_set_cached_immediate(target, key, h, SLOT_NUM_RAW(val_s))) { + dict_set_cached_immediate(target, key, h, SLOT_F64_RAW(val_s))) { /* Same net stack effect as the slow path: pop val + target, * push val (val is immediate — no refcount sides). */ g_vm.sp -= 1; @@ -1584,8 +1671,8 @@ void jit_helper_local_dot_set(EigsChunk *chunk, int slot, int name_idx) { if (chunk->const_hashes) chunk->const_hashes[name_idx] = h; } EigsSlot tos = g_vm.stack[g_vm.sp - 1]; - if (slot_is_num(tos) && - dict_set_cached_immediate(target, key, h, SLOT_NUM_RAW(tos))) { + if (slot_is_f64(tos) && + dict_set_cached_immediate(target, key, h, SLOT_F64_RAW(tos))) { return; } Value *val = vm_slot_lift(g_vm.sp - 1); @@ -1629,8 +1716,26 @@ void jit_helper_observe_assign(EigsChunk *chunk, int name_idx) { * (entropy: false 0.0, true 1.0 — compute_entropy's VAL_BOOL band). Callers * test obs_slot_observable first: a heap pointer or a bool. Null is the only * value still not observed. */ +/* #1758 R1.1: an SMI (or a reserved tag) is refused: every caller first + * routes a number to the numeric channel (obs_slot_num), so an int that got + * here would be silently unobserved. A BOXED int is a pointer and goes to + * observer_slot_update's Value arm (R1.2). */ static inline int obs_slot_observable(EigsSlot s) { - return slot_is_ptr(s) || slot_is_bool(s); + if (slot_is_ptr(s) || slot_is_bool(s)) return 1; + if (slot_is_null(s) || slot_is_f64(s)) return 0; + eigs_slot_refuse("obs_slot_observable", s); +} +/* #1758: the observer's numeric channel takes a double -- a float's own, or + * an int's (exact: |SMI| < 2^47) until round 3 (design 7). 0 for anything + * that is not an immediate number. */ +static inline int obs_slot_num(EigsSlot s, double *out) { + if (slot_is_f64(s)) { *out = SLOT_F64_RAW(s); return 1; } + if (slot_is_smi(s)) { *out = (double)slot_smi_val(s); return 1; } + if (slot_is_heap(s) && slot_as_ptr(s)->type == VAL_INT) { + *out = (double)VAL_I64_RAW(slot_as_ptr(s)); + return 1; + } + return 0; } static inline Value *obs_slot_value(EigsSlot s) { return slot_is_bool(s) ? make_bool(slot_as_bool(s)) : slot_as_ptr(s); @@ -1680,14 +1785,15 @@ void jit_helper_observe_assign_local(int slot) { CallFrame *frame = &g_vm.frames[g_vm.frame_count - 1]; EigsSlot s = g_vm.stack[g_vm.sp - 1]; Env *e = frame->fn_env; + double d; if (g_unobserved_depth != 0) { /* #1049: elided — value-window sample only (mirrors the CASE body). */ - if (slot_is_num(s)) jit_sample_num_gated(e, slot, SLOT_NUM_RAW(s)); + if (obs_slot_num(s, &d)) jit_sample_num_gated(e, slot, d); else if (obs_slot_observable(s)) observer_slot_sample_gated(e, slot, obs_slot_value(s)); return; } - if (slot_is_num(s)) { - observer_slot_update_num(e, slot, SLOT_NUM_RAW(s)); + if (obs_slot_num(s, &d)) { + observer_slot_update_num(e, slot, d); g_last_obs_slot_env = e; g_last_obs_slot_idx = slot; } else if (obs_slot_observable(s)) { observer_slot_update(e, slot, obs_slot_value(s)); @@ -1735,19 +1841,21 @@ void jit_helper_observe_name_post(EigsChunk *chunk, int name_idx) { /* #262 Phase-3 D: TOS may be an immediate num (the default path no longer * promotes observed names to tracked Values) or a heap value — observe the * slot from whichever (#1637: a bool too). Skip null. */ - if (!slot_is_num(s) && !obs_slot_observable(s)) return; + double d; + int isnum = obs_slot_num(s, &d); /* #1758: a float or an int */ + if (!isnum && !obs_slot_observable(s)) return; const char *name = chunk->const_interns[name_idx]; uint32_t h = chunk->const_hashes ? chunk->const_hashes[name_idx] : 0; if (h == 0) { h = env_hash_name(name); if (chunk->const_hashes) chunk->const_hashes[name_idx] = h; } int oidx = -1, odepth = 0; Env *oe = env_resolve_chain(frame->env, name, h, &oidx, &odepth); if (oe && oidx >= 0 && g_unobserved_depth != 0) { - if (slot_is_num(s)) observer_slot_sample_num_gated(oe, oidx, SLOT_NUM_RAW(s)); - else observer_slot_sample_gated(oe, oidx, obs_slot_value(s)); + if (isnum) observer_slot_sample_num_gated(oe, oidx, d); + else observer_slot_sample_gated(oe, oidx, obs_slot_value(s)); return; } if (oe && oidx >= 0) { - if (slot_is_num(s)) observer_slot_update_num(oe, oidx, SLOT_NUM_RAW(s)); + if (isnum) observer_slot_update_num(oe, oidx, d); else observer_slot_update(oe, oidx, obs_slot_value(s)); g_last_obs_slot_env = oe; g_last_obs_slot_idx = oidx; @@ -1776,11 +1884,11 @@ int jit_helper_iter_next(void) { Value *state = slot_as_ptr(state_slot); if (!state || state->type != VAL_LIST || state->data.list.count < 2) return 1; - EIGS_VIEW(iterable_view); - Value *iterable = list_get_view(state, 0, &iterable_view); + /* Iterables are reference values; a numeric facade cannot be iterable. */ + Value *iterable = list_get_ref(state, 0); + int idx; + if (!vm_iter_index(list_slot(state, 1), &idx)) return 1; if (!iterable) return 1; - if (list_elem_type(state, 1) != VAL_NUM) return 1; - int idx = (int)eigs_slot_num(list_slot(state, 1), "for"); int len = 0; if (iterable->type == VAL_LIST) len = iterable->data.list.count; else if (iterable->type == VAL_BUFFER) len = iterable->data.buffer.count; @@ -1788,23 +1896,23 @@ int jit_helper_iter_next(void) { /* #491: cap by the entry-time snapshot (items[2]) — see make_iter_state. * Mirrors CASE(ITER_NEXT). */ if (state->data.list.count >= 3) { - EIGS_VIEW(snap_v_view); - Value *snap_v = list_get_view(state, 2, &snap_v_view); - if (snap_v && snap_v->type == VAL_NUM) { - int snap = (int)VAL_NUM_RAW(snap_v); - if (snap < len) len = snap; - } + len = vm_iter_limit(list_slot(state, 2), len); } - if (idx >= len) return 1; + if ((unsigned)idx >= (unsigned)len) return 1; EigsSlot elem; if (iterable->type == VAL_BUFFER) { - elem = slot_from_num(num_guard(buffer_read_num(iterable, idx))); + elem = slot_from_f64(num_guard(buffer_read_f64(iterable, idx))); } else { elem = slot_read_owned(list_slot(iterable, idx)); } - /* In-place idx bump when the slot is an exclusive plain VAL_NUM - * (matches the interpreter's NUM_REUSE fast path). */ - list_set_num(state, 1, (double)(idx + 1)); + /* S9-K1-C: valid C-int positions and idx+1 fit in an SMI. Reuse the + * immediate slot in place. The raw-state fallback also stores a + * known canonical SMI and releases the replaced slot; it needs no + * generic storage normalization (which inhibits unrelated inlining). */ + if (slot_is_smi(list_slot(state, 1))) + list_bump_smi(state, 1); + else + list_set_int(state, 1, (int64_t)idx + 1); vm_push_slot(elem); return g_has_error ? 2 : 0; } @@ -1858,15 +1966,15 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { break; case OP_CONST: { uint16_t idx = read_u16(ip); ip += 2; - /* Scanner guarantees VAL_NUM. */ - mini[msp++] = slot_from_num(VAL_NUM_RAW(c->constants[idx])); + /* Scanner guarantees VAL_FLOAT. */ + mini[msp++] = slot_from_f64(VAL_F64_RAW(c->constants[idx])); break; } case OP_NUM_ZERO: - mini[msp++] = slot_from_num(0.0); + mini[msp++] = slot_from_f64(0.0); break; case OP_NUM_ONE: - mini[msp++] = slot_from_num(1.0); + mini[msp++] = slot_from_f64(1.0); break; case OP_GET_LOCAL: { uint16_t slot = read_u16(ip); ip += 2; @@ -1901,9 +2009,11 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { EigsSlot idx_s = mini[--msp]; EigsSlot tgt_s = mini[--msp]; double idx_d; - /* slot_as_double: immediate OR boxed VAL_NUM index — a for-in - * loop variable arrives as a heap num, same as ARITH_FAST. */ - if (!slot_as_double(idx_s, &idx_d) || !slot_is_ptr(tgt_s)) return 0; + /* slot_f64_value: immediate OR boxed VAL_FLOAT index — a for-in + * loop variable arrives as a heap num, same as ARITH_FAST. + * #1758: an int index is not a float: bail, and the generic call + * indexes with it (design 4.5; round 5 adds the SMI index). */ + if (!slot_f64_value(idx_s, &idx_d) || !slot_is_ptr(tgt_s)) return 0; Value *target = slot_as_ptr(tgt_s); int i; if (!vm_index_is_int(idx_d, &i)) return 0; @@ -1912,12 +2022,13 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { mini[msp++] = slot_read_value(list_slot(target, i)); /* borrow, no incref */ } else if (target->type == VAL_BUFFER) { if (!vm_index_resolve(&i, target->data.buffer.count)) return 0; - /* #1417: this speculative evaluator has no callee frame and - * skips OP_LINE. Let the generic call report a strict NaN at - * the indexed expression, before the guard can raise here. */ + /* R1.6d-D1: this speculative evaluator has no callee frame + * and skips OP_LINE. Every non-finite cell must fall back + * before a reader can raise, so the generic call diagnoses + * the indexed expression with the same kind/message/line. */ double raw = target->data.buffer.data[i]; - if (g_strict && raw != raw) return 0; - mini[msp++] = slot_from_num(buffer_read_num(target, i)); + if (!isfinite(raw)) return 0; + mini[msp++] = slot_from_f64(buffer_read_f64(target, i)); } else { return 0; } @@ -1925,8 +2036,11 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { } case OP_ADD: case OP_SUB: case OP_MUL: { double a, b; - if (!slot_as_double(mini[msp - 2], &a) || - !slot_as_double(mini[msp - 1], &b)) + /* #1758: both operands must be floats. An int or mixed operation + * bails, so the generic path computes it with the caller's line + * (design 4.5; round 5 adds int arms whose overflow bails). */ + if (!slot_f64_value(mini[msp - 2], &a) || + !slot_f64_value(mini[msp - 1], &b)) return 0; double r; switch (ip[-1]) { @@ -1944,7 +2058,7 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { break; } msp--; - mini[msp - 1] = slot_from_num(num_guard(r)); + mini[msp - 1] = slot_from_f64(num_guard(r)); break; } case OP_RETURN: { @@ -1975,16 +2089,14 @@ static int vm_leaf_accessor_exec(EigsChunk *c, int argc) { * mutates sp net -1). */ /* Stage 5c: cached writer for the per-iteration __loop_iterations__ * update. env_set_local resolves the 19-char name (hash + table walk) - * and round-trips through make_num/val_decref on EVERY iteration of - * EVERY `loop while` — measured as the dominant cost of observed loops - * once the loop body itself compiles to native code. Cache the resolved - * slot per (env, binding_version); on a hit with an immediate-num slot - * (always, after the first write) the store is a bare double overwrite - * plus the same assign_counts bump env_set_local_hashed performs. + * and round-trips through make_int/val_decref on every uncached iteration. + * Cache the resolved slot per (env, binding_version); an SMI count stays + * an immediate overwrite plus the same assign_counts bump + * env_set_local_hashed performs. S9-K1 (#1758) makes the published count + * an exact int; counts beyond the SMI range use the owned-box setter. * binding_version moves on new-binding adds (which can realloc values[]) * and env recycles — exactly the events that could invalidate the index. - * Semantics are unchanged: mid-loop reads of __loop_iterations__ see the - * same value as before. */ + * Mid-loop reads see the current cumulative count, now as an exact int. */ typedef struct { Env *env; uint64_t version; /* #1674 round 2: 64-bit, matches Env.binding_version */ @@ -2020,7 +2132,7 @@ void vm_thread_reset_caches(void) { memset(&g_loop_iter_cache, 0, sizeof(g_loop_iter_cache)); } -static void loop_iter_store(Env *env, double n) { +static void loop_iter_store(Env *env, int64_t n) { /* #1171: runtime-owned publication can target a shared root or module * env even when user code shares no writable binding. Keep the slot * replacement and assignment count under the existing env lock. The @@ -2028,27 +2140,28 @@ static void loop_iter_store(Env *env, double n) { * return before either path; a thread-local cache is not a slot lock. */ if (__builtin_expect(g_vm_multithreaded, 0) && __atomic_load_n(&env->mt_shared, __ATOMIC_RELAXED)) { - Value *iter_val = make_num(n); + Value *iter_val = make_int(n); env_set_local(env, "__loop_iterations__", iter_val); val_decref(iter_val); return; } LoopIterCache *c = &g_loop_iter_cache; if (c->env == env && c->version == env->binding_version && - slot_is_num(env->values[c->slot])) { - SLOT_NUM_RAW(env->values[c->slot]) = n; + slot_is_smi(env->values[c->slot]) && + n >= SLOT_SMI_MIN && n <= SLOT_SMI_MAX) { + env->values[c->slot] = slot_from_int(n); if (env->assign_counts) env->assign_counts[c->slot]++; return; } - Value *iter_val = make_num(n); + Value *iter_val = make_int(n); env_set_local(env, "__loop_iterations__", iter_val); val_decref(iter_val); int slot_idx, depth; Env *target = env_resolve_chain(env, "__loop_iterations__", env_hash_name("__loop_iterations__"), &slot_idx, &depth); - if (target == env && depth == 0) { + if (target && target == env && depth == 0) { c->env = env; c->version = env->binding_version; c->slot = slot_idx; @@ -2067,13 +2180,22 @@ static EigsSlot *loop_counter_slot(CallFrame *frame, uint16_t depth) { return NULL; } -static long long loop_counter_load(EigsSlot *slot) { - return slot && slot_is_num(*slot) ? (long long)eigs_slot_num(*slot, __func__) : 0; +static int64_t loop_counter_load(EigsSlot *slot) { + int64_t n; + if (slot && slot_int_value(*slot, &n)) return n; + rt_error(EK_INTERNAL, g_vm.current_line, "private loop counter is not an int"); + return 0; } -static void loop_counter_store(EigsSlot *slot, long long n) { +static void loop_counter_store(EigsSlot *slot, int64_t n) { + /* S9-K1/R1.8: counters remain exact ints; common SMI updates need no + * generic slot normalization or ownership work. */ + if (slot_is_smi(*slot) && n >= SLOT_SMI_MIN && n <= SLOT_SMI_MAX) { + *slot = slot_from_int(n); + return; + } slot_decref(*slot); - *slot = slot_from_num((double)n); + *slot = slot_from_int(n); } static void eigs_loop_enter_named(Env *env, CallFrame *frame, uint16_t counter_depth) { @@ -2083,14 +2205,14 @@ static void eigs_loop_enter_named(Env *env, CallFrame *frame, uint16_t counter_d (unsigned)counter_depth, g_vm.sp - frame->bp); return; } - vm_push_slot(slot_from_num(0.0)); + vm_push_slot(slot_from_int(0)); g_loop_iterations = 0; - loop_iter_store(env, 0.0); + loop_iter_store(env, 0); } void eigs_loop_enter(Env *env) { g_loop_iterations = 0; - loop_iter_store(env, 0.0); + loop_iter_store(env, 0); } /* Stage 4w: out-of-line helper for OP_LOOP_STALL_CHECK. Mirrors the @@ -2153,7 +2275,7 @@ static int eigs_loop_stall_step_named(Env *e, CallFrame *frame, uint16_t counter g_sandbox_cap_hit = 1; /* a capped run must not report ok (#960-adjacent) */ should_exit = 1; } - loop_iter_store(e, (double)g_loop_iterations); + loop_iter_store(e, g_loop_iterations); if (should_exit) { Value *exit_val = make_str(g_loop_exit_reason); env_set_local(e, "__loop_exit__", exit_val); @@ -2181,7 +2303,7 @@ static int eigs_loop_cap_step_named(Env *e, CallFrame *frame, uint16_t counter_d g_sandbox_cap_hit = 1; should_exit = 1; } - loop_iter_store(e, (double)g_loop_iterations); + loop_iter_store(e, g_loop_iterations); if (should_exit) { Value *exit_val = make_str(g_loop_exit_reason); env_set_local(e, "__loop_exit__", exit_val); @@ -2220,11 +2342,38 @@ void jit_helper_loop_exit(uint16_t counter_depth) { CallFrame *f = &g_vm.frames[g_vm.frame_count - 1]; EigsSlot *counter = loop_counter_slot(f, counter_depth); if (!counter) return; - loop_iter_store(f->env, (double)loop_counter_load(counter)); + loop_iter_store(f->env, loop_counter_load(counter)); while (g_vm.sp > f->bp + (int)counter_depth) slot_decref(g_vm.stack[--g_vm.sp]); } +/* #1758 R1.2: an int subscript is its exact value in the VM's C-index + * domain. Floats keep the existing integrality test. No int exists yet. */ +static inline int vm_value_index_is_int(const Value *v, int *out) { + if (v->type == VAL_INT) { + int64_t i = VAL_I64_RAW(v); + if (i < INT_MIN || i > INT_MAX) return 0; + *out = (int)i; + return 1; + } + return vm_index_is_int(VAL_F64_RAW(v), out); +} + +/* #1849 / Phase2: an exact int subscript outside the C index domain is a + * range error, just like an exact float. Clamp only the diagnostic-route + * endpoint; every container's bounds check refuses it before a read/write. + * Keep vm_value_index_is_int's separate AT/slice C-domain refusal unchanged. */ +static inline int vm_subscript_index_is_int(const Value *v, int *out) { + if (v->type == VAL_INT) { + int64_t i = VAL_I64_RAW(v); + if (i < INT_MIN) *out = INT_MIN; + else if (i > INT_MAX) *out = INT_MAX; + else *out = (int)i; + return 1; + } + return vm_index_is_int(VAL_F64_RAW(v), out); +} + /* Stage 4v: out-of-line helper for OP_INDEX_SET. Pops 3 (val, idx, * target), pushes val back (net sp -2). Full opcode semantics including * the slot fast paths, so the emitter never bails; errors go through @@ -2235,14 +2384,17 @@ void jit_helper_index_set(void) { EigsSlot val_s = g_vm.stack[g_vm.sp - 1]; EigsSlot idx_s = g_vm.stack[g_vm.sp - 2]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 3]; - if (slot_is_num(idx_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(idx_s) && slot_is_ptr(tgt_s)) { Value *target = slot_as_ptr(tgt_s); - int i = 0, _ok = vm_index_is_int(SLOT_NUM_RAW(idx_s), &i); - if (target->type == VAL_BUFFER && slot_is_num(val_s)) { + /* #1775: `i` is read only when _ok, but GCC at -O1 cannot see that + * through vm_index_resolve; this is one of the three sites it flags, + * and the only initialisations kept (callgrind: none costs Ir). */ + int i = 0, _ok = vm_index_is_int(SLOT_F64_RAW(idx_s), &i); + if (target->type == VAL_BUFFER && slot_is_f64(val_s)) { if (_ok && vm_index_resolve(&i, target->data.buffer.count)) { - target->data.buffer.data[i] = SLOT_NUM_RAW(val_s); + target->data.buffer.data[i] = SLOT_F64_RAW(val_s); } else { - if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, g_vm.current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); } @@ -2252,12 +2404,12 @@ void jit_helper_index_set(void) { slot_decref(tgt_s); return; } - if (target->type == VAL_LIST && slot_is_num(val_s)) { + if (target->type == VAL_LIST && slot_is_f64(val_s)) { if (_ok && vm_index_resolve(&i, target->data.list.count)) { - double n = SLOT_NUM_RAW(val_s); /* already guarded in a slot */ - list_rewrite_num(target, i, n); + double n = SLOT_F64_RAW(val_s); /* already guarded in a slot */ + list_rewrite_f64(target, i, n); } else { - if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, g_vm.current_line, "index %d out of range (list length %d)", i, target->data.list.count); } @@ -2269,24 +2421,24 @@ void jit_helper_index_set(void) { } } Value *val = vm_pop(); Value *idx = vm_pop(); Value *target = vm_pop(); - if (target->type == VAL_LIST && idx->type == VAL_NUM) { + if (target->type == VAL_LIST && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (vm_index_resolve(&i, target->data.list.count)) { val_incref(val); list_set_slot_owned(target, i, slot_from_heap(val)); } else { rt_error(EK_INDEX, g_vm.current_line, "index %d out of range (list length %d)", i, target->data.list.count); } - } else if (target->type == VAL_BUFFER && idx->type == VAL_NUM) { + } else if (target->type == VAL_BUFFER && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (!vm_index_resolve(&i, target->data.buffer.count)) { rt_error(EK_INDEX, g_vm.current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); - } else if (val->type == VAL_NUM) { - target->data.buffer.data[i] = VAL_NUM_RAW(val); + } else if (val_is_number(val)) { + target->data.buffer.data[i] = eigs_float_arg(val, "buffer store"); } else { /* #1061 -- mirror of CASE(INDEX_SET)'s buffer arm. */ rt_error(EK_TYPE, g_vm.current_line, "cannot store %s in a buffer (buffers hold numbers)", @@ -2306,16 +2458,16 @@ int jit_helper_index_get(void) { EigsSlot idx_s = g_vm.stack[g_vm.sp - 1]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 2]; g_vm.sp -= 2; - if (slot_is_num(idx_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(idx_s) && slot_is_ptr(tgt_s)) { Value *target = slot_as_ptr(tgt_s); - int i, _ok = vm_index_is_int(SLOT_NUM_RAW(idx_s), &i); + int i, _ok = vm_index_is_int(SLOT_F64_RAW(idx_s), &i); if (target->type == VAL_LIST) { if (_ok && vm_index_resolve(&i, target->data.list.count)) { EigsSlot r = slot_read_owned(list_slot(target, i)); slot_decref(tgt_s); vm_push_slot(r); } else { - if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, g_vm.current_line, "index %d out of range (list length %d)", i, target->data.list.count); @@ -2326,11 +2478,11 @@ int jit_helper_index_get(void) { } if (target->type == VAL_BUFFER) { if (_ok && vm_index_resolve(&i, target->data.buffer.count)) { - double v = buffer_read_num(target, i); + double v = buffer_read_f64(target, i); slot_decref(tgt_s); - vm_push_slot(slot_from_num(v)); + vm_push_slot(slot_from_f64(v)); } else { - if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, g_vm.current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); @@ -2346,10 +2498,10 @@ int jit_helper_index_get(void) { Value *target = slot_to_value(tgt_s); slot_decref(tgt_s); Value *result = make_null(); - if (target->type == VAL_LIST && idx->type == VAL_NUM) { + if (target->type == VAL_LIST && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (vm_index_resolve(&i, target->data.list.count)) { result = list_get_owned(target, i); } else { @@ -2361,10 +2513,10 @@ int jit_helper_index_get(void) { EigsSlot v = vm_dict_slot_cached(target, idx->data.str, env_hash_name(idx->data.str)); if (!slot_is_null(v)) result = eigs_slot_box(v); - } else if (target->type == VAL_STR && idx->type == VAL_NUM) { + } else if (target->type == VAL_STR && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (vm_index_resolve(&i, (int)val_str_len(target))) { char buf[2] = { target->data.str[i], 0 }; result = make_str_len(buf, 1); @@ -2373,12 +2525,12 @@ int jit_helper_index_get(void) { "string index %d out of range (length %d)", i, (int)val_str_len(target)); } - } else if (target->type == VAL_BUFFER && idx->type == VAL_NUM) { + } else if (target->type == VAL_BUFFER && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) - rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) + rt_error(EK_VALUE, g_vm.current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); else if (vm_index_resolve(&i, target->data.buffer.count)) - result = make_num(buffer_read_num(target, i)); + result = make_float(buffer_read_f64(target, i)); else rt_error(EK_INDEX, g_vm.current_line, "buffer index %d out of range (length %d)", @@ -2485,7 +2637,7 @@ static inline void vm_borrow_scan(Value *arg, Value *result, /* #720: the one place the builtin-result borrow protocol is written down. * * A builtin's raw return may BORROW from its argument vector with no - * compensating incref: `num` returns its argument unchanged for VAL_NUM, + * compensating incref: `num` returns its argument unchanged for VAL_FLOAT, * `sort` returns its arg, and `append` / `dict_set` / `set_at` / `coalesce` * return their target — a direct child of the arg vector. Whoever receives * that result must compensate, or it over-releases into a use-after-free. @@ -2944,7 +3096,7 @@ static Value *vm_take_error_value(void) { Value *d = make_dict(4); dict_set_owned(d, "kind", make_str(err_kind_name((ErrKind)g_error_kind))); dict_set_owned(d, "message", make_str(g_error_raw)); - dict_set_owned(d, "line", make_num((double)g_error_line)); + dict_set_owned(d, "line", make_int(g_error_line)); dict_set_owned(d, "file", make_str(g_error_file)); return d; } @@ -3070,7 +3222,9 @@ static const char *slot_type_name(EigsSlot s) { if (slot_is_null(s)) return "none"; if (slot_is_ptr(s)) { Value *v = slot_as_ptr(s); return v ? val_type_name(v->type) : "none"; } if (slot_is_bool(s)) return "bool"; /* #1637 */ - return "num"; /* immediate double */ + if (slot_is_f64(s)) return "num"; /* immediate double */ + if (slot_is_smi(s)) return "num"; /* #1758 (B): an int's type is "num" too */ + eigs_slot_refuse("slot_type_name", s); } /* #408: forward decls — the copying-stack save/restore and the "who is @@ -3160,9 +3314,12 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, static void *dispatch_table[OP_COUNT] = { [OP_CONST] = &&lbl_CONST, [OP_NULL] = &&lbl_NULL, [OP_NUM_ZERO] = &&lbl_NUM_ZERO, [OP_NUM_ONE] = &&lbl_NUM_ONE, + [OP_INT_ZERO] = &&lbl_INT_ZERO, [OP_INT_ONE] = &&lbl_INT_ONE, + [OP_INT_CONST] = &&lbl_INT_CONST, [OP_TRUE] = &&lbl_TRUE, [OP_FALSE] = &&lbl_FALSE, [OP_ADD] = &&lbl_ADD, [OP_SUB] = &&lbl_SUB, [OP_MUL] = &&lbl_MUL, [OP_DIV] = &&lbl_DIV, [OP_MOD] = &&lbl_MOD, + [OP_IDIV] = &&lbl_IDIV, [OP_BAND] = &&lbl_BAND, [OP_BOR] = &&lbl_BOR, [OP_BXOR] = &&lbl_BXOR, [OP_SHL] = &&lbl_SHL, [OP_SHR] = &&lbl_SHR, [OP_NEG] = &&lbl_NEG, [OP_NOT] = &&lbl_NOT, [OP_BNOT] = &&lbl_BNOT, @@ -3328,8 +3485,8 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, Value *v = chunk->constants[idx]; /* Numeric constants without observer state ship as immediates — * skips an incref/decref pair on every push/pop. */ - if (v->type == VAL_NUM) { - vm_push_slot(slot_from_num(VAL_NUM_RAW(v))); + if (v->type == VAL_FLOAT) { + vm_push_slot(slot_from_f64(VAL_F64_RAW(v))); } else { val_incref(v); vm_push(v); @@ -3337,18 +3494,35 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, DISPATCH(); } + CASE(INT_ZERO): { + vm_push_slot(slot_from_int(0)); + DISPATCH(); + } + CASE(INT_ONE): { + vm_push_slot(slot_from_int(1)); + DISPATCH(); + } + CASE(INT_CONST): { + uint16_t idx = read_u16(ip); ip += 2; + Value *v = chunk->constants[idx]; + int64_t i = VAL_I64_RAW(v); /* verifier: OP_INT_CONST requires VAL_INT */ + if (i >= SLOT_SMI_MIN && i <= SLOT_SMI_MAX) vm_push_slot(slot_from_int(i)); + else { val_incref(v); vm_push(v); } + DISPATCH(); + } + CASE(NULL): { vm_push_slot(slot_null()); DISPATCH(); } CASE(NUM_ZERO): { - vm_push_slot(slot_from_num(0.0)); + vm_push_slot(slot_from_f64(0.0)); DISPATCH(); } CASE(NUM_ONE): { - vm_push_slot(slot_from_num(1.0)); + vm_push_slot(slot_from_f64(1.0)); DISPATCH(); } @@ -3365,28 +3539,28 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, /* ---- Arithmetic ---- */ /* NUM_REUSE — retained for slow-path fallbacks that allocate via - * make_num then realize they could have mutated in place. The hot + * make_float then realize they could have mutated in place. The hot * arith fast path no longer needs it: immediate doubles have no * refcount and heap-num results are emitted as immediates. */ -#define NUM_REUSE(v) ((v)->type == VAL_NUM && (v)->refcount == 1) +#define NUM_REUSE(v) ((v)->type == VAL_FLOAT && (v)->refcount == 1) /* Stack-top fast path: read two slots, extract doubles from either - * immediate or heap VAL_NUM form, compute. Result is wrapped as a - * heap Value (via make_num or in-place NUM_REUSE). Immediates aren't + * immediate or heap VAL_FLOAT form, compute. Result is wrapped as a + * heap Value (via make_float or in-place NUM_REUSE). Immediates aren't * emitted until B-3 audits every vm_peek caller for immediate-safe * refcount handling. */ #define ARITH_FAST(OP, UF) do { \ EigsSlot _bs = g_vm.stack[g_vm.sp - 1]; \ EigsSlot _as = g_vm.stack[g_vm.sp - 2]; \ double _ad, _bd; \ - if (__builtin_expect(slot_as_double(_as, &_ad) & slot_as_double(_bs, &_bd), 1)) { \ + if (__builtin_expect(slot_f64_value(_as, &_ad) & slot_f64_value(_bs, &_bd), 1)) { \ double _r = num_guard(_ad OP _bd); \ if ((UF) && _r == 0.0 && _ad != 0.0 && _bd != 0.0) \ g_math_flags |= EIGS_MATH_UNDERFLOW; \ if (slot_is_ptr(_as)) { \ Value *_a = slot_as_ptr(_as); \ if (NUM_REUSE(_a)) { \ - VAL_NUM_RAW(_a) = _r; \ + VAL_F64_RAW(_a) = _r; \ slot_decref(_bs); \ g_vm.sp--; \ DISPATCH(); \ @@ -3395,7 +3569,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, if (slot_is_ptr(_bs)) { \ Value *_b = slot_as_ptr(_bs); \ if (NUM_REUSE(_b)) { \ - VAL_NUM_RAW(_b) = _r; \ + VAL_F64_RAW(_b) = _r; \ g_vm.stack[g_vm.sp - 2] = _bs; \ slot_decref(_as); \ g_vm.sp--; \ @@ -3403,21 +3577,39 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } \ } \ slot_decref(_as); slot_decref(_bs); \ - g_vm.stack[g_vm.sp - 2] = slot_from_num(_r); \ + g_vm.stack[g_vm.sp - 2] = slot_from_f64(_r); \ g_vm.sp--; \ DISPATCH(); \ } \ } while(0) + /* Float first, then exact integer slots. A small integer result is an + * SMI; only a result outside the SMI range allocates. This is the basic + * interpreter producer (§2.1); native JIT/leaf int paths remain R5. */ +#define INT_ARITH(OP) do { \ + EigsSlot _as = g_vm.stack[g_vm.sp - 2], _bs = g_vm.stack[g_vm.sp - 1]; \ + int64_t _ai, _bi; \ + if (slot_int_value(_as, &_ai) && slot_int_value(_bs, &_bi)) { \ + EigsSlot _r = vm_int_arith(_ai, _bi, (OP)[0], current_line); \ + slot_decref(_as); slot_decref(_bs); \ + g_vm.stack[g_vm.sp - 2] = _r; \ + g_vm.sp--; \ + DISPATCH(); \ + } \ +} while (0) + CASE(ADD): { ARITH_FAST(+, 0); + INT_ARITH("+"); /* Slow path: handles string concat etc */ Value *b = vm_pop(); Value *a = vm_pop(); - if (a->type == VAL_NUM && b->type == VAL_NUM) { - double r = num_guard(VAL_NUM_RAW(a) + VAL_NUM_RAW(b)); - if (NUM_REUSE(a)) { VAL_NUM_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } - if (NUM_REUSE(b)) { VAL_NUM_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } - vm_push(make_num(r)); + if (a->type == VAL_FLOAT && b->type == VAL_FLOAT) { + double r = num_guard(VAL_F64_RAW(a) + VAL_F64_RAW(b)); + if (NUM_REUSE(a)) { VAL_F64_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } + if (NUM_REUSE(b)) { VAL_F64_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } + vm_push(make_float(r)); + } else if (val_is_number(a) && val_is_number(b)) { + vm_push(make_float(num_guard(eigs_float_arg(a, "addition") + eigs_float_arg(b, "addition")))); } else if (a->type == VAL_STR && b->type == VAL_STR) { int la = val_str_len(a), lb = val_str_len(b); /* #292's byte budget charged only the size-controlled builtins; @@ -3465,18 +3657,25 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, #define NUM_BINOP(NAME, OP, OPNAME, UF) \ CASE(NAME): { \ ARITH_FAST(OP, UF); \ + INT_ARITH(OPNAME); \ Value *b = vm_pop(); Value *a = vm_pop(); \ - if (a->type == VAL_NUM && b->type == VAL_NUM) { \ - double r = num_guard(VAL_NUM_RAW(a) OP VAL_NUM_RAW(b)); \ - if ((UF) && r == 0.0 && VAL_NUM_RAW(a) != 0.0 && VAL_NUM_RAW(b) != 0.0) \ + if (a->type == VAL_FLOAT && b->type == VAL_FLOAT) { \ + double r = num_guard(VAL_F64_RAW(a) OP VAL_F64_RAW(b)); \ + if ((UF) && r == 0.0 && VAL_F64_RAW(a) != 0.0 && VAL_F64_RAW(b) != 0.0) \ + g_math_flags |= EIGS_MATH_UNDERFLOW; \ + if (NUM_REUSE(a)) { VAL_F64_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } \ + if (NUM_REUSE(b)) { VAL_F64_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } \ + vm_push(make_float(r)); \ + } else if (val_is_number(a) && val_is_number(b)) { \ + double ad = eigs_float_arg(a, OPNAME), bd = eigs_float_arg(b, OPNAME); \ + double r = num_guard(ad OP bd); \ + if ((UF) && r == 0.0 && ad != 0.0 && bd != 0.0) \ g_math_flags |= EIGS_MATH_UNDERFLOW; \ - if (NUM_REUSE(a)) { VAL_NUM_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } \ - if (NUM_REUSE(b)) { VAL_NUM_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } \ - vm_push(make_num(r)); \ + vm_push(make_float(r)); \ } else { \ rt_error(EK_TYPE, current_line, "cannot apply '%s' to %s and %s", \ OPNAME, val_type_name(a->type), val_type_name(b->type)); \ - vm_push(make_num(0)); \ + vm_push(make_float(0)); \ } \ val_decref(a); val_decref(b); \ DISPATCH(); \ @@ -3494,134 +3693,174 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, CASE(DIV): { Value *b = vm_pop(); Value *a = vm_pop(); - if (a->type == VAL_NUM && b->type == VAL_NUM) { - if (VAL_NUM_RAW(b) == 0.0) { + if (val_is_number(a) && val_is_number(b)) { + if (eigs_float_arg(b, "division") == 0.0) { rt_error(EK_VALUE, current_line, "division by zero"); - vm_push(make_num(0)); + vm_push(make_float(0)); } else { - double r = num_guard(VAL_NUM_RAW(a) / VAL_NUM_RAW(b)); + double r = num_guard(eigs_float_arg(a, "division") / eigs_float_arg(b, "division")); /* divisor already known nonzero (the raise above), so a zero * quotient from a nonzero dividend is underflow. */ - if (r == 0.0 && VAL_NUM_RAW(a) != 0.0) + if (r == 0.0 && eigs_float_arg(a, "division") != 0.0) g_math_flags |= EIGS_MATH_UNDERFLOW; - if (NUM_REUSE(a)) { VAL_NUM_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } - if (NUM_REUSE(b)) { VAL_NUM_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } - vm_push(make_num(r)); + if (NUM_REUSE(a)) { VAL_F64_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } + if (NUM_REUSE(b)) { VAL_F64_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } + vm_push(make_float(r)); } } else { rt_error(EK_TYPE, current_line, "cannot apply '/' to %s and %s", val_type_name(a->type), val_type_name(b->type)); - vm_push(make_num(0)); + vm_push(make_float(0)); + } + val_decref(a); val_decref(b); + DISPATCH(); + } + + CASE(IDIV): { + Value *b = vm_pop(); Value *a = vm_pop(); + if (val_is_number(a) && val_is_number(b)) { + if (a->type == VAL_INT && b->type == VAL_INT) { + int64_t ai = VAL_I64_RAW(a), bi = VAL_I64_RAW(b); + if (bi == 0) { + rt_error(EK_VALUE, current_line, "division by zero"); + vm_push(make_int(0)); + } else if (ai == INT64_MIN && bi == -1) { + rt_error(EK_VALUE, current_line, "integer overflow in '//'"); + vm_push(make_int(0)); + } else { + int64_t q = ai / bi, rem = ai % bi; + /* C truncates. A nonzero remainder with the opposite sign + * to the divisor moves the quotient down by one (N4). */ + if (rem != 0 && ((rem < 0) != (bi < 0))) q--; + vm_push(make_int(q)); + } + } else { + double divisor = eigs_float_arg(b, "floor division"); + /* #1758 comment 6294926822: exactly DIV's error through R1.4. + * N11 changes this float arm with DIV/MOD to IEEE floor(x/0). */ + if (divisor == 0.0) { + rt_error(EK_VALUE, current_line, "division by zero"); + vm_push(make_float(0)); + } else { + /* #1882: Python float_divmod's quotient, not floor(a/b). + * Derive it from fmod so decimal boundaries and an + * underflowed opposite-sign ratio agree with floor %. */ + double dividend = eigs_float_arg(a, "floor division"); + double rem = fmod(dividend, divisor); + double div = (dividend - rem) / divisor; + if (rem != 0.0 && ((rem < 0.0) != (divisor < 0.0))) + div -= 1.0; + double floordiv; + if (div != 0.0) { + floordiv = floor(div); + if (div - floordiv > 0.5) floordiv += 1.0; + } else { + floordiv = signbit(dividend / divisor) ? -0.0 : 0.0; + } + vm_push(make_float(num_guard(floordiv))); + } + } + } else { + rt_error(EK_TYPE, current_line, "cannot apply '//' to %s and %s", + val_type_name(a->type), val_type_name(b->type)); + vm_push(make_int(0)); } + /* vm_pop owns both boxes, including INT64_MIN and a boxed divisor; + * every result and raise path releases both references. */ val_decref(a); val_decref(b); DISPATCH(); } CASE(MOD): { Value *b = vm_pop(); Value *a = vm_pop(); - if (a->type == VAL_NUM && b->type == VAL_NUM && VAL_NUM_RAW(b) != 0.0) { - double r = num_guard(fmod(VAL_NUM_RAW(a), VAL_NUM_RAW(b))); - if (NUM_REUSE(a)) { VAL_NUM_RAW(a) = r; vm_push(a); val_decref(b); DISPATCH(); } - if (NUM_REUSE(b)) { VAL_NUM_RAW(b) = r; vm_push(b); val_decref(a); DISPATCH(); } - vm_push(make_num(r)); + if (val_is_number(a) && val_is_number(b)) { + if (a->type == VAL_INT && b->type == VAL_INT) { + int64_t ai = VAL_I64_RAW(a), bi = VAL_I64_RAW(b); + if (bi == 0) { + rt_error(EK_VALUE, current_line, "modulo by zero"); + vm_push(make_int(0)); + } else { + /* N4b: x86 idiv faults for INT64_MIN / -1 even when only + * the remainder is needed. Handle it before C's %. */ + int64_t rem = bi == -1 ? 0 : ai % bi; + if (rem != 0 && ((rem < 0) != (bi < 0))) rem += bi; + vm_push(make_int(rem)); + } + } else { + double divisor = eigs_float_arg(b, "modulo"); + if (divisor == 0.0) { + rt_error(EK_VALUE, current_line, "modulo by zero"); + vm_push(make_float(0)); + } else { + vm_push(make_float(num_guard(vm_floor_mod(eigs_float_arg(a, "modulo"), divisor)))); + } + } } else { - if (!(a->type == VAL_NUM && b->type == VAL_NUM)) - rt_error(EK_TYPE, current_line, "cannot apply '%%' to %s and %s", - val_type_name(a->type), val_type_name(b->type)); - else - rt_error(EK_VALUE, current_line, "modulo by zero"); - vm_push(make_num(0)); + rt_error(EK_TYPE, current_line, "cannot apply '%%' to %s and %s", + val_type_name(a->type), val_type_name(b->type)); + vm_push(make_float(0)); } val_decref(a); val_decref(b); DISPATCH(); } -/* #1740: both operands must be exact integers in int64 (Lua 5.4's rule); - * a fractional or out-of-range one raises `value` (see num_to_i64_exact). - * RHS is the (possibly transformed) right operand _bi. For `& | ^` - * it is the plain int64 operand; for the shifts it is masked to [0,63] so a - * large or negative shift count is defined (mod-64), never C UB — the same - * `& 63` the bit_shl/bit_shr builtins apply, honoring the LANGUAGE_CONTRACT - * "shifts are defined, not UB" promise. Without the mask, `1 << 64` on the - * infix path is undefined behavior (UBSan trips; only untested counts hid it). */ -#define INT_BINOP_R(NAME, OP, OPNAME, RHS) \ +/* #1758 N8: exact int64 operands (#1740), integer results, Lua shifts. + * Both popped operands are owned, including boxed ints on raise paths. */ +#define INT_BINOP_R(NAME, OPNAME, EXPR) \ CASE(NAME): { \ EigsSlot _bs = g_vm.stack[g_vm.sp - 1]; \ EigsSlot _as = g_vm.stack[g_vm.sp - 2]; \ - double _ad = 0.0, _bd = 0.0; \ - if (__builtin_expect(slot_as_double(_as, &_ad) & slot_as_double(_bs, &_bd), 1)) { \ - int64_t _ai, _bi; \ - if (__builtin_expect(!num_to_i64_exact(_ad, &_ai) || \ - !num_to_i64_exact(_bd, &_bi), 0)) { \ - slot_decref(_as); slot_decref(_bs); \ - rt_error(EK_VALUE, current_line, EIGS_NO_INT_REP_FMT, OPNAME, \ - num_to_i64_exact(_ad, &_ai) ? _bd : _ad); \ - g_vm.stack[g_vm.sp - 2] = slot_from_num(0.0); \ - g_vm.sp--; \ - DISPATCH(); \ - } \ - double _r = (double)(INT_OP_##NAME(_ai, (RHS))); \ - if (slot_is_ptr(_as)) { \ - Value *_a = slot_as_ptr(_as); \ - if (NUM_REUSE(_a)) { VAL_NUM_RAW(_a) = _r; slot_decref(_bs); g_vm.sp--; DISPATCH(); } \ - } \ - if (slot_is_ptr(_bs)) { \ - Value *_b = slot_as_ptr(_bs); \ - if (NUM_REUSE(_b)) { VAL_NUM_RAW(_b) = _r; g_vm.stack[g_vm.sp - 2] = _bs; slot_decref(_as); g_vm.sp--; DISPATCH(); } \ - } \ - slot_decref(_as); slot_decref(_bs); \ - g_vm.stack[g_vm.sp - 2] = slot_from_num(_r); \ - g_vm.sp--; \ - DISPATCH(); \ - } \ - /* Mixed/non-numeric: error rather than silently returning 0, \ - * matching the arithmetic operators and the strict error model. \ - * Capture type names before decref (cf. NUM_CMP above). */ \ - { \ - const char *_tna = slot_type_name(_as), *_tnb = slot_type_name(_bs); \ - slot_decref(_as); slot_decref(_bs); \ + int64_t _ai, _bi; \ + int _ak = vm_bit_operand(_as, &_ai), _bk = vm_bit_operand(_bs, &_bi); \ + EigsSlot _r = slot_null(); \ + if (_ak == 1 && _bk == 1) { \ + _r = slot_from_int(EXPR); \ + } else if (_ak < 0 || _bk < 0) { \ rt_error(EK_TYPE, current_line, "cannot apply '%s' to %s and %s", \ - OPNAME, _tna, _tnb); \ + OPNAME, slot_type_name(_as), slot_type_name(_bs)); \ + } else { \ + double _bad = 0.0; \ + slot_f64_value(_ak == 0 ? _as : _bs, &_bad); \ + rt_error(EK_VALUE, current_line, EIGS_NO_INT_REP_FMT, OPNAME, _bad); \ } \ - g_vm.stack[g_vm.sp - 2] = slot_from_num(0.0); \ + slot_decref(_as); slot_decref(_bs); \ + g_vm.stack[g_vm.sp - 2] = _r; \ g_vm.sp--; \ DISPATCH(); \ } -/* SHL shifts unsigned: `<<` on a negative int64 is UB. Same bits as the - * JIT's `shl`. */ -#define INT_OP_BAND(a, b) ((a) & (b)) -#define INT_OP_BOR(a, b) ((a) | (b)) -#define INT_OP_BXOR(a, b) ((a) ^ (b)) -#define INT_OP_SHL(a, b) ((int64_t)((uint64_t)(a) << (b))) -#define INT_OP_SHR(a, b) ((a) >> (b)) - -/* Non-shift bitwise ops: RHS is the plain int64 operand. */ -#define INT_BINOP(NAME, OP, OPNAME) INT_BINOP_R(NAME, OP, OPNAME, _bi) - - INT_BINOP(BAND, &, "&") - INT_BINOP(BOR, |, "|") - INT_BINOP(BXOR, ^, "^") - INT_BINOP_R(SHL, <<, "<<", (_bi & 63)) - INT_BINOP_R(SHR, >>, ">>", (_bi & 63)) + INT_BINOP_R(BAND, "&", _ai & _bi) + INT_BINOP_R(BOR, "|", _ai | _bi) + INT_BINOP_R(BXOR, "^", _ai ^ _bi) + INT_BINOP_R(SHL, "<<", eigs_bit_shift(_ai, _bi, 0)) + INT_BINOP_R(SHR, ">>", eigs_bit_shift(_ai, _bi, 1)) /* ---- Unary ---- */ CASE(NEG): { EigsSlot s = g_vm.stack[g_vm.sp - 1]; double d; - if (__builtin_expect(slot_as_double(s, &d), 1)) { + if (__builtin_expect(slot_f64_value(s, &d), 1)) { if (slot_is_ptr(s)) { Value *v = slot_as_ptr(s); - if (NUM_REUSE(v)) { VAL_NUM_RAW(v) = -d; DISPATCH(); } + if (NUM_REUSE(v)) { VAL_F64_RAW(v) = -d; DISPATCH(); } } slot_decref(s); - g_vm.stack[g_vm.sp - 1] = slot_from_num(-d); + g_vm.stack[g_vm.sp - 1] = slot_from_f64(-d); + DISPATCH(); + } + int64_t i; + if (slot_int_value(s, &i)) { + slot_decref(s); + if (i == INT64_MIN) { + rt_error(EK_VALUE, current_line, "integer overflow in unary '-'"); + g_vm.stack[g_vm.sp - 1] = slot_null(); + } else g_vm.stack[g_vm.sp - 1] = slot_from_int(-i); DISPATCH(); } rt_error(EK_TYPE, current_line, "cannot negate %s", slot_type_name(s)); /* #1637: names bool */ slot_decref(s); - g_vm.stack[g_vm.sp - 1] = slot_from_num(0.0); + g_vm.stack[g_vm.sp - 1] = slot_from_f64(0.0); DISPATCH(); } @@ -3635,30 +3874,19 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, CASE(BNOT): { EigsSlot s = g_vm.stack[g_vm.sp - 1]; - double d; - if (__builtin_expect(slot_as_double(s, &d), 1)) { - int64_t i; - if (__builtin_expect(!num_to_i64_exact(d, &i), 0)) { /* #1740 */ - slot_decref(s); - rt_error(EK_VALUE, current_line, EIGS_NO_INT_REP_FMT, "~", d); - g_vm.stack[g_vm.sp - 1] = slot_from_num(0.0); - DISPATCH(); - } - double r = (double)(~i); - if (slot_is_ptr(s)) { - Value *v = slot_as_ptr(s); - if (NUM_REUSE(v)) { VAL_NUM_RAW(v) = r; DISPATCH(); } - } - slot_decref(s); - g_vm.stack[g_vm.sp - 1] = slot_from_num(r); - DISPATCH(); - } - { - const char *_tn = slot_type_name(s); - slot_decref(s); - rt_error(EK_TYPE, current_line, "cannot apply '~' to %s", _tn); + int64_t i; + int k = vm_bit_operand(s, &i); + EigsSlot r = slot_null(); + if (k == 1) r = slot_from_int(~i); + else if (k < 0) + rt_error(EK_TYPE, current_line, "cannot apply '~' to %s", slot_type_name(s)); + else { + double bad = 0.0; + slot_f64_value(s, &bad); + rt_error(EK_VALUE, current_line, EIGS_NO_INT_REP_FMT, "~", bad); } - g_vm.stack[g_vm.sp - 1] = slot_from_num(0.0); + slot_decref(s); + g_vm.stack[g_vm.sp - 1] = r; DISPATCH(); } @@ -3667,8 +3895,13 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, CASE(EQ): { EigsSlot _bs = g_vm.stack[g_vm.sp - 1]; EigsSlot _as = g_vm.stack[g_vm.sp - 2]; + if (slot_is_smi(_as) && slot_is_smi(_bs)) { + g_vm.stack[g_vm.sp - 2] = slot_from_bool(_as.u == _bs.u); + g_vm.sp--; + DISPATCH(); + } double _ad, _bd; - if (__builtin_expect(slot_as_double(_as, &_ad) & slot_as_double(_bs, &_bd), 1)) { + if (__builtin_expect(slot_f64_value(_as, &_ad) & slot_f64_value(_bs, &_bd), 1)) { /* #1637: the result is a bool immediate. The old NUM_REUSE * branches wrote a 0/1 result INTO a heap-number operand; a bool * cannot live there, so they are gone, not retagged. */ @@ -3688,8 +3921,13 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, CASE(NE): { EigsSlot _bs = g_vm.stack[g_vm.sp - 1]; EigsSlot _as = g_vm.stack[g_vm.sp - 2]; + if (slot_is_smi(_as) && slot_is_smi(_bs)) { + g_vm.stack[g_vm.sp - 2] = slot_from_bool(_as.u != _bs.u); + g_vm.sp--; + DISPATCH(); + } double _ad, _bd; - if (__builtin_expect(slot_as_double(_as, &_ad) & slot_as_double(_bs, &_bd), 1)) { + if (__builtin_expect(slot_f64_value(_as, &_ad) & slot_f64_value(_bs, &_bd), 1)) { /* #1637: the result is a bool immediate. The old NUM_REUSE * branches wrote a 0/1 result INTO a heap-number operand; a bool * cannot live there, so they are gone, not retagged. */ @@ -3711,7 +3949,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, EigsSlot _bs = g_vm.stack[g_vm.sp - 1]; \ EigsSlot _as = g_vm.stack[g_vm.sp - 2]; \ double _ad, _bd; \ - if (__builtin_expect(slot_as_double(_as, &_ad) & slot_as_double(_bs, &_bd), 1)) { \ + if (__builtin_expect(slot_f64_value(_as, &_ad) & slot_f64_value(_bs, &_bd), 1)) { \ /* #1637: a bool immediate; no NUM_REUSE (see CASE(EQ)). */ \ int _r = (_ad OP _bd); \ slot_decref(_as); slot_decref(_bs); \ @@ -3731,6 +3969,14 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, DISPATCH(); \ } \ } \ + int _cmp; \ + if (vm_number_compare(_as, _bs, &_cmp)) { \ + int _r = _cmp != 2 && (_cmp OP 0); \ + slot_decref(_as); slot_decref(_bs); \ + g_vm.stack[g_vm.sp - 2] = slot_from_bool(_r); \ + g_vm.sp--; \ + DISPATCH(); \ + } \ /* Mixed/uncomparable types: error rather than silently returning \ * false (e.g. "3" < 4 used to be 0). Capture type names before \ * decref. ==/!= keep cross-type "not equal"; only ordering errors. */ \ @@ -3901,7 +4147,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * scope (#959), then populate IC. */ env_set_local_pre_interned_slot(env_binding_home(start), name, chunk->intern_tbl, h, s); Env *t2 = env_resolve_chain(start, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && t2 == start) { /* #297: see above */ + if (!g_vm_multithreaded && t2 && t2 == start) { /* #297: see above */ ic->starting_env = start; ic->starting_ver = start->binding_version; ic->target_ver = start->binding_version; @@ -3932,7 +4178,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, env_set_local_pre_interned_slot(start, name, chunk->intern_tbl, h, s); int slot_idx, depth; Env *target = env_resolve_chain(start, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && target == start) { /* #297: see above */ + if (!g_vm_multithreaded && target && target == start) { /* #297: see above */ ic->starting_env = start; ic->starting_ver = start->binding_version; ic->target_ver = start->binding_version; @@ -3971,7 +4217,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, env_set_local_pre_interned_slot(target, name, chunk->intern_tbl, h, s); int slot_idx, depth; Env *resolved = env_resolve_chain(target, name, h, &slot_idx, &depth); - if (!g_vm_multithreaded && resolved == target) { /* #297: see above */ + if (!g_vm_multithreaded && resolved && resolved == target) { /* #297: see above */ ic->starting_env = target; ic->starting_ver = target->binding_version; ic->target_ver = target->binding_version; @@ -4113,10 +4359,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, chunk_decref(chunk); if (g_vm.frame_count <= base_frame) { EigsSlot result_s = g_vm.stack[--g_vm.sp]; - if (slot_is_num(result_s)) return make_num(SLOT_NUM_RAW(result_s)); - if (slot_is_null(result_s)) return make_null(); - if (slot_is_bool(result_s)) return make_bool(slot_as_bool(result_s)); - return slot_as_ptr(result_s); + /* Transfer the slot's owned ref into a Value*: the one conversion, + * with its #1758 int arm (it was spelled out at four sites). */ + return slot_bridge_unwrap(result_s); } frame = &g_vm.frames[g_vm.frame_count - 1]; ip = frame->ip; @@ -4414,7 +4659,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } /* Pop args + fn from stack (slot-direct; immediates never - * round-trip through make_num + free_value). #1171: the + * round-trip through make_float + free_value). #1171: the * callee's reference is released only AFTER callframe_init has * taken the frame's chunk reference. The stack can hold the * function's LAST reference: a rebind of its name (another @@ -4489,10 +4734,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, chunk_decref(fn_chunk); if (g_vm.frame_count <= base_frame) { EigsSlot result_s = g_vm.stack[--g_vm.sp]; - if (slot_is_num(result_s)) return make_num(SLOT_NUM_RAW(result_s)); - if (slot_is_null(result_s)) return make_null(); - if (slot_is_bool(result_s)) return make_bool(slot_as_bool(result_s)); - return slot_as_ptr(result_s); + /* Transfer the slot's owned ref into a Value*: the one conversion, + * with its #1758 int arm (it was spelled out at four sites). */ + return slot_bridge_unwrap(result_s); } frame = &g_vm.frames[g_vm.frame_count - 1]; ip = frame->ip; @@ -4536,7 +4780,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } CASE(RETURN): { - /* Slot-native: avoid make_num/free_value round-trip when a callee + /* Slot-native: avoid make_float/free_value round-trip when a callee * returns an immediate. Carry the result slot across the frame * switch directly. */ EigsSlot result_s; @@ -4571,11 +4815,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, g_vm.frame_count--; if (g_vm.frame_count <= base_frame) { /* Return to C: transfer slot's owned ref into a Value*. - * Immediate -> materialize; pointer -> reuse slot's ref. */ - if (slot_is_num(result_s)) return make_num(SLOT_NUM_RAW(result_s)); - if (slot_is_null(result_s)) return make_null(); - if (slot_is_bool(result_s)) return make_bool(slot_as_bool(result_s)); - return slot_as_ptr(result_s); + * Immediate -> materialize; pointer -> reuse slot's ref. One + * conversion, with its #1758 int arm (it was spelled out at + * four sites). */ + return slot_bridge_unwrap(result_s); } frame = &g_vm.frames[g_vm.frame_count - 1]; ip = frame->ip; @@ -4657,16 +4900,16 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, EigsSlot idx_s = g_vm.stack[g_vm.sp - 1]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 2]; g_vm.sp -= 2; - if (slot_is_num(idx_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(idx_s) && slot_is_ptr(tgt_s)) { Value *target = slot_as_ptr(tgt_s); - int i, _ok = vm_index_is_int(SLOT_NUM_RAW(idx_s), &i); + int i, _ok = vm_index_is_int(SLOT_F64_RAW(idx_s), &i); if (target->type == VAL_LIST) { if (_ok && vm_index_resolve(&i, target->data.list.count)) { EigsSlot r = slot_read_owned(list_slot(target, i)); slot_decref(tgt_s); vm_push_slot(r); } else { - if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, current_line, "index %d out of range (list length %d)", i, target->data.list.count); slot_decref(tgt_s); @@ -4676,11 +4919,11 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } if (target->type == VAL_BUFFER) { if (_ok && vm_index_resolve(&i, target->data.buffer.count)) { - double v = buffer_read_num(target, i); + double v = buffer_read_f64(target, i); slot_decref(tgt_s); - vm_push_slot(slot_from_num(v)); + vm_push_slot(slot_from_f64(v)); } else { - if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); slot_decref(tgt_s); @@ -4689,48 +4932,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, DISPATCH(); } } - /* Slow path: materialize both via slot_to_value for unified handling. */ - Value *idx = slot_to_value(idx_s); - slot_decref(idx_s); - Value *target = slot_to_value(tgt_s); - slot_decref(tgt_s); - Value *result = make_null(); - if (target->type == VAL_LIST && idx->type == VAL_NUM) { - int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); - } else if (vm_index_resolve(&i, target->data.list.count)) { - result = list_get_owned(target, i); - } else { - rt_error(EK_INDEX, current_line, "index %d out of range (list length %d)", i, target->data.list.count); - } - } else if (target->type == VAL_DICT && idx->type == VAL_STR) { - EigsSlot v = vm_dict_slot_cached(target, idx->data.str, - env_hash_name(idx->data.str)); - if (!slot_is_null(v)) result = eigs_slot_box(v); - } else if (target->type == VAL_STR && idx->type == VAL_NUM) { - int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); - } else if (vm_index_resolve(&i, (int)val_str_len(target))) { - char buf[2] = { target->data.str[i], 0 }; - result = make_str_len(buf, 1); - } else { - rt_error(EK_INDEX, current_line, "string index %d out of range (length %d)", i, (int)val_str_len(target)); - } - } else if (target->type == VAL_BUFFER && idx->type == VAL_NUM) { - int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) - rt_error(EK_VALUE, current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); - else if (vm_index_resolve(&i, target->data.buffer.count)) - result = make_num(buffer_read_num(target, i)); - else - rt_error(EK_INDEX, current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); - } else { - rt_error(EK_TYPE, current_line, "cannot index %s", val_type_name(target->type)); - } - val_decref(target); val_decref(idx); - vm_push(result); + /* The helper owns the same slow path and int decisions. Restore the + * two borrowed stack slots before handing their ownership to it. */ + g_vm.sp += 2; + (void)jit_helper_index_get(); DISPATCH(); } @@ -4740,14 +4945,14 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, EigsSlot val_s = g_vm.stack[g_vm.sp - 1]; EigsSlot idx_s = g_vm.stack[g_vm.sp - 2]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 3]; - if (slot_is_num(idx_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(idx_s) && slot_is_ptr(tgt_s)) { Value *target = slot_as_ptr(tgt_s); - int i = 0, _ok = vm_index_is_int(SLOT_NUM_RAW(idx_s), &i); - if (target->type == VAL_BUFFER && slot_is_num(val_s)) { + int i = 0, _ok = vm_index_is_int(SLOT_F64_RAW(idx_s), &i); /* #1775, as in jit_helper_index_set */ + if (target->type == VAL_BUFFER && slot_is_f64(val_s)) { if (_ok && vm_index_resolve(&i, target->data.buffer.count)) { - target->data.buffer.data[i] = SLOT_NUM_RAW(val_s); + target->data.buffer.data[i] = SLOT_F64_RAW(val_s); } else { - if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); } @@ -4757,11 +4962,11 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, slot_decref(tgt_s); DISPATCH(); } - if (target->type == VAL_LIST && slot_is_num(val_s)) { + if (target->type == VAL_LIST && slot_is_f64(val_s)) { if (_ok && vm_index_resolve(&i, target->data.list.count)) { - list_set_num(target, i, SLOT_NUM_RAW(val_s)); + list_set_f64(target, i, SLOT_F64_RAW(val_s)); } else { - if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_NUM_RAW(idx_s)); + if (!_ok) rt_error(EK_VALUE, current_line, "index must be an integer, got %g", SLOT_F64_RAW(idx_s)); else rt_error(EK_INDEX, current_line, "index %d out of range (list length %d)", i, target->data.list.count); } @@ -4773,24 +4978,24 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } } Value *val = vm_pop(); Value *idx = vm_pop(); Value *target = vm_pop(); - if (target->type == VAL_LIST && idx->type == VAL_NUM) { + if (target->type == VAL_LIST && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (vm_index_resolve(&i, target->data.list.count)) { val_incref(val); list_set_slot_owned(target, i, slot_from_heap(val)); } else { rt_error(EK_INDEX, current_line, "index %d out of range (list length %d)", i, target->data.list.count); } - } else if (target->type == VAL_BUFFER && idx->type == VAL_NUM) { + } else if (target->type == VAL_BUFFER && val_is_number(idx)) { int i; - if (!vm_index_is_int(VAL_NUM_RAW(idx), &i)) { - rt_error(EK_VALUE, current_line, "index must be an integer, got %g", VAL_NUM_RAW(idx)); + if (!vm_subscript_index_is_int(idx, &i)) { + rt_error(EK_VALUE, current_line, "index must be an integer, got %g", eigs_float_arg(idx, "index")); } else if (!vm_index_resolve(&i, target->data.buffer.count)) { rt_error(EK_INDEX, current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); - } else if (val->type == VAL_NUM) { - target->data.buffer.data[i] = VAL_NUM_RAW(val); + } else if (val_is_number(val)) { + target->data.buffer.data[i] = eigs_float_arg(val, "buffer store"); } else { /* #1061: the last fail-soft numeric context. A non-number * store was silently DROPPED (the old element stayed), so @@ -4847,10 +5052,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, { EigsSlot val_s = g_vm.stack[g_vm.sp - 1]; EigsSlot tgt_s = g_vm.stack[g_vm.sp - 2]; - if (slot_is_num(val_s) && slot_is_ptr(tgt_s)) { + if (slot_is_f64(val_s) && slot_is_ptr(tgt_s)) { Value *t = slot_as_ptr(tgt_s); if (t->type == VAL_DICT && - dict_set_cached_immediate(t, key, h, SLOT_NUM_RAW(val_s))) { + dict_set_cached_immediate(t, key, h, SLOT_F64_RAW(val_s))) { g_vm.sp -= 1; g_vm.stack[g_vm.sp - 1] = val_s; slot_decref(tgt_s); @@ -4911,8 +5116,8 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, uint32_t h = chunk->const_hashes ? chunk->const_hashes[name_idx] : 0; if (h == 0) { h = env_hash_name(key); if (chunk->const_hashes) chunk->const_hashes[name_idx] = h; } EigsSlot tos = g_vm.stack[g_vm.sp - 1]; - if (slot_is_num(tos) && - dict_set_cached_immediate(target, key, h, SLOT_NUM_RAW(tos))) { + if (slot_is_f64(tos) && + dict_set_cached_immediate(target, key, h, SLOT_F64_RAW(tos))) { DISPATCH(); } Value *val = vm_slot_lift(g_vm.sp - 1); @@ -4936,9 +5141,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, if (target) { int i = (int)idx; if (target->type == VAL_BUFFER) { - /* DMG hot path: mem[addr] — emit immediate, skip make_num. */ + /* DMG hot path: mem[addr] — emit immediate, skip make_float. */ if (i < target->data.buffer.count) { - vm_push_slot(slot_from_num(buffer_read_num(target, i))); + vm_push_slot(slot_from_f64(buffer_read_f64(target, i))); } else { rt_error(EK_INDEX, current_line, "buffer index %d out of range (length %d)", i, target->data.buffer.count); @@ -5044,8 +5249,8 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * mutate the existing dict slot's data.num in place * and leave the stack slot as an immediate (no lift). */ EigsSlot tos = g_vm.stack[g_vm.sp - 1]; - if (slot_is_num(tos) && - dict_set_cached_immediate(dict, key, h, SLOT_NUM_RAW(tos))) { + if (slot_is_f64(tos) && + dict_set_cached_immediate(dict, key, h, SLOT_F64_RAW(tos))) { eigs_view_check(&dict_view); DISPATCH(); } @@ -5087,26 +5292,26 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, Value *state = vm_peek(0); /* The index slot must be a number: make_iter_state always builds one, * but an untrusted chunk (vm_run_bytecode / sandbox_run) can reach a - * bare ITER_NEXT with any list on TOS, and reading VAL_NUM_RAW() off a + * bare ITER_NEXT with any list on TOS, and reading VAL_F64_RAW() off a * VAL_STR is a type confusion whose result then indexes items[] (#721). */ - if (!state || state->type != VAL_LIST || state->data.list.count < 2 || - list_elem_type(state, 1) != VAL_NUM) { + if (!state || state->type != VAL_LIST || state->data.list.count < 2) { ip += exit_offset; DISPATCH(); } - EIGS_VIEW_NOCHECK(iterable_view); - Value *iterable = list_get_view(state, 0, &iterable_view); - int idx = (int)eigs_slot_num(list_slot(state, 1), "for"); + int idx; + if (!vm_iter_index(list_slot(state, 1), &idx)) { + ip += exit_offset; + DISPATCH(); + } + Value *iterable = list_get_ref(state, 0); int len = 0; - if (iterable->type == VAL_LIST) len = iterable->data.list.count; - else if (iterable->type == VAL_BUFFER) len = iterable->data.buffer.count; + if (iterable && iterable->type == VAL_LIST) len = iterable->data.list.count; + else if (iterable && iterable->type == VAL_BUFFER) len = iterable->data.buffer.count; /* #491: cap by the entry-time snapshot (items[2]) so a body that * appends can't extend the loop; min() with the live length keeps a * remove from reading past the end. See make_iter_state. */ - if (state->data.list.count >= 3 && - list_elem_type(state, 2) == VAL_NUM) { - int snap = (int)eigs_slot_num(list_slot(state, 2), "for"); - if (snap < len) len = snap; + if (state->data.list.count >= 3) { + len = vm_iter_limit(list_slot(state, 2), len); } /* Unsigned compare bounds BOTH ends in one branch: a negative index @@ -5116,19 +5321,20 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, if ((unsigned)idx >= (unsigned)len) { ip += exit_offset; } else { - /* Update index — mutate in place when the existing slot is - * an exclusive plain VAL_NUM (avoids per-iter make_num/free). */ - list_set_num(state, 1, (double)(idx + 1)); + /* S9-K1-C: mirror the helper's in-place SMI update. */ + if (slot_is_smi(list_slot(state, 1))) + list_bump_smi(state, 1); + else + list_set_int(state, 1, (int64_t)idx + 1); if (iterable->type == VAL_BUFFER) { - /* Push number immediate directly — skip make_num + immediate- + /* Push number immediate directly — skip make_float + immediate- * promote round-trip through vm_push. */ - vm_push_slot(slot_from_num(buffer_read_num(iterable, idx))); + vm_push_slot(slot_from_f64(buffer_read_f64(iterable, idx))); } else { EigsSlot elem = slot_read_owned(list_slot(iterable, idx)); vm_push_slot(elem); } } - eigs_view_check(&iterable_view); DISPATCH(); } @@ -5281,8 +5487,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * the slot persists across assignments, which is the #262 fix. */ EigsSlot s = g_vm.stack[g_vm.sp - 1]; Env *e = frame->fn_env; - if (slot_is_num(s)) { - observer_slot_update_num(e, (int)slot, SLOT_NUM_RAW(s)); + double d; + if (obs_slot_num(s, &d)) { /* #1758: a float or an int */ + observer_slot_update_num(e, (int)slot, d); g_last_obs_slot_env = e; g_last_obs_slot_idx = (int)slot; } else if (obs_slot_observable(s)) { observer_slot_update(e, (int)slot, obs_slot_value(s)); @@ -5295,8 +5502,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * observer_slot_sample_num (eigenscript.c). */ EigsSlot s = g_vm.stack[g_vm.sp - 1]; Env *e = frame->fn_env; + double d; eigs_obs_count_call(); - if (slot_is_num(s)) observer_slot_sample_num_gated(e, (int)slot, SLOT_NUM_RAW(s)); + if (obs_slot_num(s, &d)) observer_slot_sample_num_gated(e, (int)slot, d); else if (obs_slot_observable(s)) observer_slot_sample_gated(e, (int)slot, obs_slot_value(s)); } DISPATCH(); @@ -5461,9 +5669,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, list_append_owned(list, make_str( vm_slot_value_opaque(e, (int)slot) ? "opaque" : observer_slot_report(&q))); - list_append_owned(list, make_num(q.entropy)); - list_append_owned(list, make_num(s->dH)); - list_append_owned(list, make_num(s->prev_dH)); + list_append_owned(list, make_float(q.entropy)); + list_append_owned(list, make_float(s->dH)); + list_append_owned(list, make_float(s->prev_dH)); vm_push(list); } else { vm_push(builtin_observe(NULL)); @@ -5497,9 +5705,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, list_append_owned(list, make_str( vm_slot_value_opaque(oe, oidx) ? "opaque" /* #708 */ : observer_slot_report(&q))); - list_append_owned(list, make_num(q.entropy)); - list_append_owned(list, make_num(s->dH)); - list_append_owned(list, make_num(s->prev_dH)); + list_append_owned(list, make_float(q.entropy)); + list_append_owned(list, make_float(s->dH)); + list_append_owned(list, make_float(s->prev_dH)); vm_push(list); } else { vm_push(builtin_observe(NULL)); @@ -5526,7 +5734,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, /* #262 Phase-3 D: TOS is now an immediate num for an observed name * (default path no longer promotes), or a heap value. Observe the * slot from whichever (#1637: a bool too); skip null. */ - if (slot_is_num(s) || obs_slot_observable(s)) { + double d; + int isnum = obs_slot_num(s, &d); /* #1758: a float or an int */ + if (isnum || obs_slot_observable(s)) { const char *name = chunk->const_interns[name_idx]; uint32_t h = chunk->const_hashes ? chunk->const_hashes[name_idx] : 0; if (h == 0) { h = env_hash_name(name); if (chunk->const_hashes) chunk->const_hashes[name_idx] = h; } @@ -5534,10 +5744,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, Env *oe = env_resolve_chain(frame->env, name, h, &oidx, &odepth); if (oe && oidx >= 0 && g_unobserved_depth != 0) { eigs_obs_count_call(); - if (slot_is_num(s)) observer_slot_sample_num_gated(oe, oidx, SLOT_NUM_RAW(s)); - else observer_slot_sample_gated(oe, oidx, obs_slot_value(s)); + if (isnum) observer_slot_sample_num_gated(oe, oidx, d); + else observer_slot_sample_gated(oe, oidx, obs_slot_value(s)); } else if (oe && oidx >= 0) { - if (slot_is_num(s)) observer_slot_update_num(oe, oidx, SLOT_NUM_RAW(s)); + if (isnum) observer_slot_update_num(oe, oidx, d); else observer_slot_update(oe, oidx, obs_slot_value(s)); g_last_obs_slot_env = oe; g_last_obs_slot_idx = oidx; @@ -5561,18 +5771,19 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, Value *result = make_null(); switch (kind) { case 0: /* what */ - if (v && v->type == VAL_NUM) { result = make_num(VAL_NUM_RAW(v)); } + if (v && v->type == VAL_INT) { val_incref(v); result = v; } + else if (v && v->type == VAL_FLOAT) { result = make_float(VAL_F64_RAW(v)); } /* #1637: a bool is its own value -- it fell to the else arm and * answered 0, so `what is (5 > 3)` read as false. */ else if (v && v->type == VAL_BOOL) { result = make_bool(v->data.boolean); } - else if (v && v->type == VAL_STR) { result = make_num(val_str_len(v)); } - else if (v && v->type == VAL_LIST) { result = make_num(v->data.list.count); } - else if (v && v->type == VAL_BUFFER) { result = make_num(v->data.buffer.count); } - else { result = make_num(0); } + else if (v && v->type == VAL_STR) { result = make_int(val_str_len(v)); } + else if (v && v->type == VAL_LIST) { result = make_int(v->data.list.count); } + else if (v && v->type == VAL_BUFFER) { result = make_int(v->data.buffer.count); } + else { result = make_float(0); } break; case 1: /* who — returns descriptive type name */ if (v) result = make_str( - v->type == VAL_NUM ? "number" : + val_is_number(v) ? "number" : v->type == VAL_STR ? "string" : v->type == VAL_LIST ? "list" : v->type == VAL_DICT ? "dict" : @@ -5586,10 +5797,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * slot trajectory (observer state is binding-keyed). where/why/how on a * bound ident go through INTERROGATE_NAMED; the bare value form reports * the no-observation defaults. */ - case 2: /* when */ result = make_num(0); break; - case 3: /* where */ result = make_num(0); break; - case 4: /* why */ result = make_num(0); break; - case 5: /* how */ result = make_num(1.0); break; + case 2: /* when */ result = make_int(0); break; + case 3: /* where */ result = make_float(0); break; + case 4: /* why */ result = make_float(0); break; + case 5: /* how */ result = make_float(1.0); break; } val_decref(v); vm_push(result); @@ -5609,7 +5820,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, result = make_str(name); break; case 2: /* when — return assignment count */ - result = make_num(env_get_assign_count(frame->env, name, h)); + result = make_int(env_get_assign_count(frame->env, name, h)); break; case 6: { /* prev — value just before the most recent assignment */ EigsSlot prev_slot; @@ -5635,14 +5846,14 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, * in-place mutation is visible. dH (why/how) is the * assignment-recorded trajectory and stays as stored. */ double h_now; - result = make_num(observer_entropy_now(oe, oidx, &h_now) + result = make_float(observer_entropy_now(oe, oidx, &h_now) ? h_now : s->entropy); } - else if (kind == 4) result = make_num(s->dH); - else result = make_num(observer_settledness(s->dH)); + else if (kind == 4) result = make_float(s->dH); + else result = make_float(observer_settledness(s->dH)); } else { /* #262 Step E: unobserved binding — no trajectory. */ - result = make_num(kind == 5 ? 1.0 : 0.0); + result = make_float(kind == 5 ? 1.0 : 0.0); } break; } @@ -5658,14 +5869,23 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, Value *line_v = vm_pop(); /* #1637: the line is a number, as `when`'s ordinal is. A bool (or any * other type) used to read as line 0 and answer null silently. */ - if (!line_v || line_v->type != VAL_NUM) { + if (!line_v || !val_is_number(line_v)) { rt_error(EK_TYPE, current_line, "'at' line must be a number, got %s", line_v ? val_type_name(line_v->type) : "none"); val_decref(line_v); vm_push(make_null()); DISPATCH(); } - int line = (int)VAL_NUM_RAW(line_v); + int line; + if (line_v->type == VAL_INT) { + if (!vm_value_index_is_int(line_v, &line)) { + rt_error(EK_VALUE, current_line, "'at' line %lld is out of range", + (long long)VAL_I64_RAW(line_v)); + val_decref(line_v); + vm_push(make_null()); + DISPATCH(); + } + } else line = (int)VAL_F64_RAW(line_v); val_decref(line_v); Value *result = make_null(); const char *name = chunk->const_interns[name_idx]; @@ -5685,8 +5905,10 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, uint16_t kind = read_u16(ip); ip += 2; uint16_t name_idx = read_u16(ip); ip += 2; Value *ord_v = vm_pop(); - int ord_is_num = (ord_v && ord_v->type == VAL_NUM); - double ord_d = ord_is_num ? VAL_NUM_RAW(ord_v) : 0.0; + int ord_is_num = (ord_v && val_is_number(ord_v)); + int ord_is_int = ord_v && ord_v->type == VAL_INT; + int64_t ord_i = ord_is_int ? VAL_I64_RAW(ord_v) : 0; + double ord_d = ord_is_num && !ord_is_int ? VAL_F64_RAW(ord_v) : 0.0; val_decref(ord_v); const char *name = chunk->const_interns[name_idx]; @@ -5705,7 +5927,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, DISPATCH(); } - long long ordinal = (long long)ord_d; + long long ordinal = ord_is_int ? (long long)ord_i : (long long)ord_d; EigsSlot w_slot; long long total = 0, oldest = 0; int r = trace_query_when(kind, name, ordinal, &w_slot, &total, &oldest); @@ -5816,7 +6038,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, case VAL_BUFFER: len = target->data.buffer.count; break; /* Not sliceable. Enumerated rather than covered by a `default:` * so -Werror=switch forces a new ValType to choose here. */ - case VAL_NUM: case VAL_FN: case VAL_BUILTIN: case VAL_NULL: + case VAL_FLOAT: case VAL_INT: case VAL_FN: case VAL_BUILTIN: case VAL_NULL: case VAL_DICT: case VAL_TEXT_BUILDER: case VAL_BOOL: rt_error(EK_TYPE, g_vm.current_line, "cannot slice %s", val_type_name(target->type)); @@ -5826,28 +6048,35 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } /* Extract a slice bound from a slot: null → use default; immediate - * num or VAL_NUM → must be an exact integer. Returns 1 on success + * num or VAL_FLOAT → must be an exact integer. Returns 1 on success * (with *out set), 0 on type error (after raising). */ int start = 0, end = len; int bound_err = 0; #define READ_SLICE_BOUND(slot, defval, outvar) \ do { \ - if (slot_is_num(slot)) { \ - if (!vm_index_is_int(SLOT_NUM_RAW(slot), &(outvar))) { \ + if (slot_is_f64(slot)) { \ + if (!vm_index_is_int(SLOT_F64_RAW(slot), &(outvar))) { \ rt_error(EK_VALUE, g_vm.current_line, \ - "slice bound must be an integer, got %g", SLOT_NUM_RAW(slot));\ + "slice bound must be an integer, got %g", SLOT_F64_RAW(slot));\ bound_err = 1; \ } \ } else if (slot_is_ptr(slot)) { \ Value *bv = slot_as_ptr(slot); \ if (bv->type == VAL_NULL) { \ (outvar) = (defval); \ - } else if (bv->type == VAL_NUM) { \ - if (!vm_index_is_int(VAL_NUM_RAW(bv), &(outvar))) { \ + } else if (bv->type == VAL_INT) { \ + if (!vm_value_index_is_int(bv, &(outvar))) { \ + rt_error(EK_VALUE, g_vm.current_line, \ + "slice bound %lld is out of range", \ + (long long)VAL_I64_RAW(bv)); \ + bound_err = 1; \ + } \ + } else if (bv->type == VAL_FLOAT) { \ + if (!vm_index_is_int(VAL_F64_RAW(bv), &(outvar))) { \ rt_error(EK_VALUE, g_vm.current_line, \ "slice bound must be an integer, got %g", \ - VAL_NUM_RAW(bv)); \ + VAL_F64_RAW(bv)); \ bound_err = 1; \ } \ } else { \ @@ -5858,6 +6087,18 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, } \ } else if (slot_is_null(slot)) { \ (outvar) = (defval); /* an omitted bound */ \ + } else if (slot_is_smi(slot)) { \ + /* #1758: an int bound is its value, when an int \ + * (the C type) holds it. */ \ + int64_t _bv = slot_smi_val(slot); \ + if (_bv < INT_MIN || _bv > INT_MAX) { \ + rt_error(EK_VALUE, g_vm.current_line, \ + "slice bound %lld is out of range", \ + (long long)_bv); \ + bound_err = 1; \ + } else { \ + (outvar) = (int)_bv; \ + } \ } else { \ /* #1637: an immediate bool (or any other non-number \ * slot) is not a bound -- never the default one. */ \ @@ -6071,7 +6312,7 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, uint16_t counter_depth = read_u16(ip); ip += 2; EigsSlot *counter = loop_counter_slot(frame, counter_depth); if (counter) { - loop_iter_store(frame->env, (double)loop_counter_load(counter)); + loop_iter_store(frame->env, loop_counter_load(counter)); while (g_vm.sp > frame->bp + (int)counter_depth) slot_decref(g_vm.stack[--g_vm.sp]); } @@ -6537,56 +6778,50 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, CASE(DISPATCH): { /* Native dispatch: stack [table, key, arg] -> table[key](arg). - * Key may be an immediate-num slot (literal key) or a boxed - * VAL_NUM (key sourced from a fn return, arithmetic, variable - * read — DMG's per-instruction `op` path). Table and arg are - * heap pointers in practice (list + ctx). */ + * Exact number extraction matches builtin_dispatch: ints retain all + * bits, floats satisfy N5 before the C index-range check (#1849). + * Table and arg are heap pointers in practice (list + ctx). */ EigsSlot arg_s = g_vm.stack[g_vm.sp - 1]; EigsSlot key_s = g_vm.stack[g_vm.sp - 2]; EigsSlot table_s = g_vm.stack[g_vm.sp - 3]; g_vm.sp -= 3; - double key_d; - if (slot_is_num(key_s)) { - key_d = SLOT_NUM_RAW(key_s); - } else if (slot_is_ptr(key_s) && slot_as_ptr(key_s) - && slot_as_ptr(key_s)->type == VAL_NUM) { - key_d = VAL_NUM_RAW(slot_as_ptr(key_s)); - } else { + ValType key_type = slot_type(key_s); + if (key_type != VAL_FLOAT && key_type != VAL_INT) { slot_decref(arg_s); slot_decref(key_s); slot_decref(table_s); rt_error(EK_TYPE, current_line, "dispatch: key must be a number"); vm_push_slot(slot_null()); DISPATCH(); } + EigsNum key_n = eigs_slot_number(key_s, "dispatch"); slot_decref(key_s); - - if (!slot_is_ptr(table_s)) { + int64_t key_i; + if (key_n.is_int) { + key_i = key_n.value.i64; + } else if (!num_to_i64_exact(key_n.value.f64, &key_i)) { slot_decref(arg_s); slot_decref(table_s); - rt_error(EK_TYPE, current_line, "dispatch: table must be a list"); + rt_error(EK_VALUE, current_line, + "dispatch key must be an integer, got %g", key_n.value.f64); vm_push_slot(slot_null()); DISPATCH(); } - - Value *table = slot_as_ptr(table_s); - int key; - /* Integrality first, then range: a fractional key is a value error - * wherever it lies (2147483647.5 included), as ordinary indexing - * reports it; only an integral key outside int range is index_range. */ - if (!vm_index_is_int(key_d, &key)) { + if (key_i < INT_MIN || key_i > INT_MAX) { slot_decref(arg_s); slot_decref(table_s); - rt_error(EK_VALUE, current_line, - "dispatch key must be an integer, got %g", key_d); + rt_error(EK_INDEX, current_line, + "dispatch key %lld out of range", (long long)key_i); vm_push_slot(slot_null()); DISPATCH(); } - if (key_d < (double)INT_MIN || key_d > (double)INT_MAX) { + int key = (int)key_i; + + if (!slot_is_ptr(table_s)) { slot_decref(arg_s); slot_decref(table_s); - rt_error(EK_INDEX, current_line, - "dispatch key %g out of range", key_d); + rt_error(EK_TYPE, current_line, "dispatch: table must be a list"); vm_push_slot(slot_null()); DISPATCH(); } + Value *table = slot_as_ptr(table_s); if (table->type != VAL_LIST) { slot_decref(arg_s); slot_decref(table_s); rt_error(EK_TYPE, current_line, "dispatch: table must be a list"); @@ -6708,10 +6943,9 @@ static Value *vm_run_ex(EigsChunk *chunk, Env *env, Task *resume, if (g_vm.frame_count <= base_frame) { EigsSlot result_s = g_vm.stack[--g_vm.sp]; eigs_view_check(&fn_view); - if (slot_is_num(result_s)) return make_num(SLOT_NUM_RAW(result_s)); - if (slot_is_null(result_s)) return make_null(); - if (slot_is_bool(result_s)) return make_bool(slot_as_bool(result_s)); - return slot_as_ptr(result_s); + /* Transfer the slot's owned ref into a Value*: the one conversion, + * with its #1758 int arm (it was spelled out at four sites). */ + return slot_bridge_unwrap(result_s); } frame = &g_vm.frames[g_vm.frame_count - 1]; ip = frame->ip; diff --git a/src/vm.h b/src/vm.h index bc59c4938..3593379a1 100644 --- a/src/vm.h +++ b/src/vm.h @@ -222,6 +222,12 @@ typedef enum { * Appended to preserve the public bytecode ABI. */ OP_TRUE, /*obs:NONE*/ /* #1637: push the bool true immediate. Appended. */ OP_FALSE, /*obs:NONE*/ /* #1637: push the bool false immediate. Appended. */ + /* #1758 R1.3: int literal opcodes append to preserve the bytecode ABI. + * The float literal opcodes above retain their original meanings. */ + OP_INT_ZERO, /*obs:NONE*/ /* push int 0 */ + OP_INT_ONE, /*obs:NONE*/ /* push int 1 */ + OP_INT_CONST, /*obs:NONE*/ /* [idx:16] push int constant pool entry */ + OP_IDIV, /*obs:NONE*/ /* #1758 N4: floor division, appended */ OP_LOOP_ENTER, /*obs:DIAG*/ /* legacy shared counter reset */ OP_LOOP_STALL_NAMED,/*obs:READS*/ /* [exit:16][stack_depth:16] per-loop stall/cap guard */ OP_LOOP_CAP_NAMED, /*obs:DIAG*/ /* [exit:16][stack_depth:16] per-loop cap guard */ @@ -695,7 +701,7 @@ int task_handle_unpack(double packed, int *id, uint32_t *gen); Value *task_sched_trace_read(void); /* list of {seq, tick, task, cause} dicts; [] when off / no scheduler */ void task_sched_trace_clear(void); /* discard the recorded history (no-op without a scheduler) */ /* Inc 4 seeded scheduling strategy (builtins.c task_sched_seed). */ -void task_sched_set_seed(double seed); /* install a seed → seeded pick; ensures the scheduler */ +void task_sched_set_seed(int64_t seed); /* install a seed → seeded pick; ensures the scheduler */ /* ---- Public API ---- */ @@ -825,21 +831,17 @@ void vm_thread_reset_caches(void); * integral double outside the C int range is clamped to the nearest endpoint. * That preserves the predicate's compact * interface while sending it through the caller's ordinary out-of-range path; - * critically, the range test happens before the cast. Values outside the int - * range but below 2^52 are tested with a (long long) round-trip (exact there, and - * no libm: the freestanding profile has no trunc()); at and above 2^52 every finite - * binary64 value is already integral. Negatives pass here and fail the usual - * range check downstream, unchanged. */ + * critically, Phase2's exact signed-int64 check happens before either cast. + * A fractional, non-finite or out-of-int64 float is a value refusal (N5); + * an exact int64 outside the C index domain takes main's range-error path. + * num_to_i64_exact is pure inline and needs no freestanding libm helper. + * Negatives pass here and fail the usual range check downstream, unchanged. */ static inline int vm_index_is_int(double d, int *out) { - if (d < (double)INT_MIN || d > (double)INT_MAX) { - if (!isfinite(d) || (fabs(d) < 4503599627370496.0 && (double)(long long)d != d)) - return 0; - *out = d < 0.0 ? INT_MIN : INT_MAX; - return 1; - } - int i = (int)d; - if ((double)i != d) return 0; - *out = i; + int64_t i; + if (!num_to_i64_exact(d, &i)) return 0; + if (i < INT_MIN) *out = INT_MIN; + else if (i > INT_MAX) *out = INT_MAX; + else *out = (int)i; return 1; } diff --git a/tests/arming_mt_fixtures/arming_mt_st.expected b/tests/arming_mt_fixtures/arming_mt_st.expected index ba570e5f4..6300fa49e 100644 --- a/tests/arming_mt_fixtures/arming_mt_st.expected +++ b/tests/arming_mt_fixtures/arming_mt_st.expected @@ -1,4 +1,4 @@ -PREV 2 +PREV 2.0 WHEN1 1 WHEN2 2 NOW 3 diff --git a/tests/bool_fuzz_anyvalue.txt b/tests/bool_fuzz_anyvalue.txt index 84f5fc951..5f9df8467 100644 --- a/tests/bool_fuzz_anyvalue.txt +++ b/tests/bool_fuzz_anyvalue.txt @@ -20,6 +20,9 @@ str|e1|converts any value to text type|u|names any value's type type|e0|names any value's type type|e1|names any value's type +num_type|u|names a numeric kind or returns null for other values +num_type|e0|a list is a non-number so num_type returns null +num_type|e1|a list is a non-number so num_type returns null json_encode|u|JSON has true and false json_encode|e0|JSON has true and false json_encode|e1|JSON has true and false diff --git a/tests/bool_fuzz_gen.eigs b/tests/bool_fuzz_gen.eigs index a19ec4346..9cfa2ac71 100644 --- a/tests/bool_fuzz_gen.eigs +++ b/tests/bool_fuzz_gen.eigs @@ -267,6 +267,8 @@ for v in ["true", "false"]: append of [vm_calls, ["1 - " + v, "arith-sub", v]] append of [vm_calls, [v + " * 2", "arith-mul", v]] append of [vm_calls, ["2 / " + v, "arith-div", v]] + append of [vm_calls, [v + " // 2", "arith-idiv-left", v]] + append of [vm_calls, ["2 // " + v, "arith-idiv-right", v]] append of [vm_calls, [v + " % 2", "arith-mod", v]] append of [vm_calls, ["0 - " + v, "arith-neg", v]] append of [vm_calls, [v + " < 1", "order", v]] diff --git a/tests/enrolment_exemptions.txt b/tests/enrolment_exemptions.txt index 70a599136..8accffc3c 100644 --- a/tests/enrolment_exemptions.txt +++ b/tests/enrolment_exemptions.txt @@ -1,8 +1,9 @@ -# Test scripts nothing in CI runs, on purpose (tools/enrolment_check.sh, #1264). -# `path | reason`. A row for a missing or invoked file fails; prefer a section. +# Test scripts and tools/ entry points that no workflow invokes, as tools/enrolment_check.sh +# reads "invoked" (#1264; tools/ since #1758 R1.3). `path | reason`: the reason says how the +# script does run. A row for a missing or invoked file fails; prefer a section or a workflow step. tests/test_gc_runner_controls.py | manual deferred control for the GC traversal runner (tests/GC_TRAVERSAL.md); forks child sessions and must run with the heavy slot released, so it is run by hand -tools/builtin_usage.sh | maintainer-only source audit that reports builtin use sites; it is not an executable CI check -tools/doc_coupling_hook.sh | optional local Git pre-commit hook installed by a contributor, not a CI entry point -tools/mutants.sh | manual mutation-train driver documented in concurrency and trace guides; individual product tests run in CI +tools/builtin_usage.sh | a report over local consumer checkouts (counts builtin references), not a gate; run by hand +tools/doc_coupling_hook.sh | a Claude Code hook (.claude/settings.json) for agent sessions, not a CI gate +tools/mutants.sh | the manual mutation trains (its header); its --selftest is a tools/selftests.txt row tests/test_gfx_utf8_title.eigs | alternate oracle: test_gfx_utf8_title.sh checks WM_NAME and _NET_WM_NAME on the live X11 window, covering the fixture's sole "X11 window opens" assertion tests/test_variant_stdlib.eigs | alternate oracle: 18f-variant-stdlib-1679.sh runs a byte-for-byte temporary copy from outside the checkout and requires rc 0 plus its exact Tests: 1 | Pass: 1 | Fail: 0 summary, covering the fixture's stdlib-load assertion diff --git a/tests/fixtures/jit_diff_agreement.eigs b/tests/fixtures/jit_diff_agreement.eigs new file mode 100644 index 000000000..0da1a3f2d --- /dev/null +++ b/tests/fixtures/jit_diff_agreement.eigs @@ -0,0 +1,2 @@ +# Clean control for ledger-comparison self-tests: REF and OBS agree. +print of 2122 diff --git a/tests/fixtures/jit_diff_gate_nondeterministic.eigs b/tests/fixtures/jit_diff_gate_nondeterministic.eigs new file mode 100644 index 000000000..907fb13b0 --- /dev/null +++ b/tests/fixtures/jit_diff_gate_nondeterministic.eigs @@ -0,0 +1,3 @@ +# Clean control: the harness's common seed makes this stable. The self-test +# plant varies that process seed between runs; trace/replay then stabilizes it. +print of (random_int of [1, 1000000]) diff --git a/tests/fixtures/jit_diff_taped_divergence.eigs b/tests/fixtures/jit_diff_taped_divergence.eigs new file mode 100644 index 000000000..979544111 --- /dev/null +++ b/tests/fixtures/jit_diff_taped_divergence.eigs @@ -0,0 +1 @@ +print of (random_int of [1, 2]) diff --git a/tests/gfx_headless.sh b/tests/gfx_headless.sh index 475346fd4..94d98d7b9 100644 --- a/tests/gfx_headless.sh +++ b/tests/gfx_headless.sh @@ -44,9 +44,10 @@ gfx_headless_run() { platform=$(uname -s) || return 125 ( - unset DISPLAY WAYLAND_DISPLAY export SDL_VIDEODRIVER=dummy gfx_address_space_apply "$binary" "$instrumentation" "$platform" "$cap_kib" || exit 125 - exec "$timeout_cmd" "$seconds" "$@" + # Unsetting a function's temporary DISPLAY assignment can expose the + # caller's exported value. Remove it at the child process boundary. + exec env -u DISPLAY -u WAYLAND_DISPLAY "$timeout_cmd" "$seconds" "$@" ) } diff --git a/tests/jit_diff_expected.txt b/tests/jit_diff_expected.txt index 3e9f13a59..a76b2f0e1 100644 --- a/tests/jit_diff_expected.txt +++ b/tests/jit_diff_expected.txt @@ -1,4 +1,21 @@ # OBS rows: these programs assert gate-CLOSED behaviour by design, so forcing the gate open changes their # output. They are not defects to work down. +bench_dmg_shape.eigs OBS COULD_NOT_ADJUDICATE +bench_idxset.eigs OBS COULD_NOT_ADJUDICATE +bench_perf.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_3dof_core.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_adaptive.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_arm.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_core.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_decomposition.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_edl.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_geodesic.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_lorentz.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_meta.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_multi_agent.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_noperthedron.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_qec.eigs OBS COULD_NOT_ADJUDICATE +examples/invariant_similarity.eigs OBS COULD_NOT_ADJUDICATE test_chunk_verify_stack.eigs OBS test_desc_unrecorded_read.eigs OBS +trace_mt_replay_workers.eigs OBS COULD_NOT_ADJUDICATE diff --git a/tests/jit_env_flags_float.eigs b/tests/jit_env_flags_float.eigs new file mode 100644 index 000000000..340e51635 --- /dev/null +++ b/tests/jit_env_flags_float.eigs @@ -0,0 +1,8 @@ +# R1.3: native env-flag witnesses use explicit float literals until the +# JIT int fast-path round. Keep cost benchmarks unchanged. +i is 0.0 +total is 0.0 +loop while i < 20000.0: + total is total + i + i is i + 1.0 +print of total diff --git a/tests/line_after_callback_halt.eigs b/tests/line_after_callback_halt.eigs index 077987bfa..5f57dcbfb 100644 --- a/tests/line_after_callback_halt.eigs +++ b/tests/line_after_callback_halt.eigs @@ -3,13 +3,13 @@ # error-halts (sandbox_run swallows the error), then sqrt raises in hot_sb's # own expression on line 9. Pre-fix: "Error line 77: sqrt: ..." and # "at hot_sb (line 77)". LINE-NUMBER SENSITIVE ([0g] greps these numbers). -ok77 is [1, [68,77,0,0,0, 3, 40], []] -halt77 is [1, [68,77,0,0,0, 3, 2, 7, 40], []] +ok77 is [1.0, [68.0,77.0,0.0,0.0,0.0, 3.0, 40.0], []] +halt77 is [1.0, [68.0,77.0,0.0,0.0,0.0, 3.0, 2.0, 7.0, 40.0], []] define hot_sb(d, k) as: - r is sqrt of (k - (len of (sandbox_run of [d, 100]))) + r is sqrt of (k - (len of (sandbox_run of [d, 100.0]))) return r -k is 60 -loop while k > 1: +k is 60.0 +loop while k > 1.0: w is hot_sb of [ok77, k] - k is k - 1 -w is hot_sb of [halt77, 1] + k is k - 1.0 +w is hot_sb of [halt77, 1.0] diff --git a/tests/line_after_callback_tape.eigs b/tests/line_after_callback_tape.eigs index a599856e4..e4cdc5cbf 100644 --- a/tests/line_after_callback_tape.eigs +++ b/tests/line_after_callback_tape.eigs @@ -5,13 +5,13 @@ # the statement's line 15. [0g] steps the tape: after the last stop at line 9 # the next stop must be 15. LINE-NUMBER SENSITIVE. define key(v) as: - w is 0 - v + w is 0.0 - v return w define mk(n) as: - q is [[n, 2], key] + q is [[n, 2.0], key] return q -k is 60 -loop while k > 0: +k is 60.0 +loop while k > 0.0: x is len of (sort_by of (mk of k)) - k is k - 1 -print of (what is x at 15) + k is k - 1.0 +print of (what is x at 15.0) diff --git a/tests/line_after_return_fn.eigs b/tests/line_after_return_fn.eigs index 8ee4903e5..4474d93ad 100644 --- a/tests/line_after_return_fn.eigs +++ b/tests/line_after_return_fn.eigs @@ -4,13 +4,13 @@ # Pre-fix: "Error line 8" and "at outer (line 8)". # LINE-NUMBER SENSITIVE (the [0g] runner section greps these numbers). define dec(x) as: - y is x - 1 + y is x - 1.0 return y define outer(x) as: - q is x + 1 - r is 1 / (dec of x) + q is x + 1.0 + r is 1.0 / (dec of x) return r -k is 6001 -loop while k > 0: +k is 6001.0 +loop while k > 0.0: w is outer of k - k is k - 1 + k is k - 1.0 diff --git a/tests/line_after_return_module.eigs b/tests/line_after_return_module.eigs index c2111239b..88e54a7c4 100644 --- a/tests/line_after_return_module.eigs +++ b/tests/line_after_return_module.eigs @@ -3,9 +3,9 @@ # last iteration. Pre-fix: "Error line 7" and "at (line 7)". # LINE-NUMBER SENSITIVE (the [0g] runner section greps these numbers). define dec(x) as: - y is x - 1 + y is x - 1.0 return y -k is 6001 -loop while k > 0: - r is 1 / (dec of k) - k is k - 1 +k is 6001.0 +loop while k > 0.0: + r is 1.0 / (dec of k) + k is k - 1.0 diff --git a/tests/line_after_return_null.eigs b/tests/line_after_return_null.eigs index 2e42a9186..891c7b5b6 100644 --- a/tests/line_after_return_null.eigs +++ b/tests/line_after_return_null.eigs @@ -7,14 +7,14 @@ define nothing(x) as: y is x define one(x) as: y is x - return 1 + return 1.0 define hot_null(g, x) as: - q is x + 1 + q is x + 1.0 r is x / (g of x) return r -k is 0 -loop while k < 6000: +k is 0.0 +loop while k < 6000.0: w is nothing of k w is hot_null of [one, k] - k is k + 1 -w is hot_null of [nothing, 4] + k is k + 1.0 +w is hot_null of [nothing, 4.0] diff --git a/tests/loader_mt_userrace.eigs b/tests/loader_mt_userrace.eigs index b48708765..ff2c64dc8 100644 --- a/tests/loader_mt_userrace.eigs +++ b/tests/loader_mt_userrace.eigs @@ -14,14 +14,18 @@ # cannot lean on it (#1489). # # The deliberate race is the racer pair: two closures each write ONCE to the -# the same element of a list they share, a race docs/CONCURRENCY.md still +# same double in a buffer they share, a race docs/CONCURRENCY.md still # leaves to the program. A ready channel plus # separate release channels form a real rendezvous: main observes both workers # at the barrier before releasing either, and neither release orders one # worker's victim write before the other's. The # racing word is not a spin/handshake word, so repeated polling cannot evict # the first conflicting access from TSan's short per-word history (#1489). -# It cannot corrupt memory because both stores are immediate numbers. +# Capture each worker's local buffer reference BEFORE the rendezvous, and use +# float index/value literals. Phase 2's integer literals take a boxing path; +# its allocation/cleanup and shared closure lookups must not sit between the +# release and the deliberately unsynchronized payload write. The float buffer +# arm writes the shared double directly without changing any heap ownership. define lm_ur_worker(k) as: local i is 0 local n is 0 @@ -32,20 +36,22 @@ define lm_ur_worker(k) as: return n define lm_ur_race_pair() as: - local victim is [0] + local victim is buffer of 1 local ready is channel of null local release_a is channel of null local release_b is channel of null define racer_a as: + local cell is victim send of [ready, 1] recv of release_a - victim[0] is 1 - return victim[0] + cell[0.0] is 1.0 + return 0 define racer_b as: + local cell is victim send of [ready, 1] recv of release_b - victim[0] is 2 - return victim[0] + cell[0.0] is 2.0 + return 0 return [racer_a, racer_b, ready, release_a, release_b] racers is lm_ur_race_pair of null diff --git a/tests/observer_corpus/README.md b/tests/observer_corpus/README.md index b0735c98c..a0eb20319 100644 --- a/tests/observer_corpus/README.md +++ b/tests/observer_corpus/README.md @@ -59,6 +59,21 @@ so the corpus watches their convergence *counts* but not their *results*. When `dynamics` next refreshes `solve.eigs` it should print the elements (or `buf_to_list`) so the numeric signal comes back. +## Local edit note (#1758 R1.3, 2026-10-19) + +`iLambdaAi__test_halting_descent` differs from its source (iLambdaAi `246bd9a`, +`eigenscript_native/tests/test_halting_descent.eigs`) in two lines: `x is 10` +became `x is 10.0`. Since #1758 R1.3, `10` is an int literal and int `*` raises +`value` on int64 overflow (decision (A)), so the source's runaway doubling stops +with `integer overflow in '*'` after about 60 doublings instead of saturating at +`1e308`. The source relied on its int silently becoming a float; `10.0` is the +literal its author must write under (A). This row pins the observer's reading of +a doubling sequence; overflow belongs to the N3/(A) rows of +`tests/phase2/MAP.md`. **The golden is unchanged**: byte-identical with the edit +in the interpreter, the default JIT and forced OSR, which is the proof that the +observer's reading did not move. Re-vendoring from `246bd9a` would undo the edit; +take the source's own fix when it lands, or keep this one. + ## Sources (same-owner repos; vendored with provenance) - **dynamics** — `solve`/`physics`/`life`: real solvers and trajectory demos using `report`, the six predicates, `unobserved`, and `prev`. diff --git a/tests/observer_corpus/golden/EigenScript__container_observer.out b/tests/observer_corpus/golden/EigenScript__container_observer.out index c3d3cc9a5..cee618b7c 100644 --- a/tests/observer_corpus/golden/EigenScript__container_observer.out +++ b/tests/observer_corpus/golden/EigenScript__container_observer.out @@ -8,9 +8,9 @@ 2.877443751081734 3.377443751081734 3.584962500721156 -2 2 stable +2.0 2.0 stable 2.584962500721156 0.5849625007211561 stable -3 0.4150374992788439 diverging +3.0 0.4150374992788439 diverging 3.321928094887362 0.3219280948873622 diverging 3.584962500721156 0.2630344058337939 diverging 3.807354922057604 0.2223924213364481 diverging diff --git a/tests/observer_corpus/golden/EigenScript__idioms.out b/tests/observer_corpus/golden/EigenScript__idioms.out index b65985233..177d1b76e 100644 --- a/tests/observer_corpus/golden/EigenScript__idioms.out +++ b/tests/observer_corpus/golden/EigenScript__idioms.out @@ -8,7 +8,7 @@ Sum of positives: 26 (5 values) 10 / 3 = 3.3333333333333335 Warning: division by zero, returning 0 15 / 0 = 0 - 20 / 4 = 5 + 20 / 4 = 5.0 Warning: division by zero, returning 0 0 / 0 = 0 100 / 7 = 14.285714285714286 @@ -34,7 +34,7 @@ GOOD (prefixed 'cs_total' is safe): --- calling conventions --- double of 7: 14 -lerp of [0, 100, 0.75]: 75 +lerp of [0, 100, 0.75]: 75.0 timestamp of null: 1712419200 --- early exit pattern --- diff --git a/tests/observer_corpus/golden/EigenScript__numerical.out b/tests/observer_corpus/golden/EigenScript__numerical.out index 9d46132e0..a7afafcfa 100644 --- a/tests/observer_corpus/golden/EigenScript__numerical.out +++ b/tests/observer_corpus/golden/EigenScript__numerical.out @@ -1,7 +1,7 @@ --- Newton's Method: Square Roots --- sqrt(2) = 1.414213562373095 -sqrt(9) = 3 -sqrt(144) = 12 +sqrt(9) = 3.0 +sqrt(144) = 12.0 sqrt(2.5) = 1.5811388300841895 --- Bisection Method: x^3 - x - 2 = 0 --- diff --git a/tests/observer_corpus/golden/EigenScript__structural_observer.out b/tests/observer_corpus/golden/EigenScript__structural_observer.out index 0abb0d3cd..2e0d0a7b8 100644 --- a/tests/observer_corpus/golden/EigenScript__structural_observer.out +++ b/tests/observer_corpus/golden/EigenScript__structural_observer.out @@ -2,8 +2,8 @@ equilibrium moving ["moving", 0.7219280948873623, -0.19636773916712724, -0.08170416594551044] 3.479597728084419e-06 -4 -1 +4.0 +1.0 1.191829090169325e-06 3.4366397314414046e-06 3.0014538431203897 diff --git a/tests/observer_corpus/golden/dynamics__physics.out b/tests/observer_corpus/golden/dynamics__physics.out index 5521d2dee..c332878fe 100644 --- a/tests/observer_corpus/golden/dynamics__physics.out +++ b/tests/observer_corpus/golden/dynamics__physics.out @@ -3,11 +3,11 @@ observe ENERGY (Lyapunov) vs DISPLACEMENT (oscillation); flags = regime visited zeta | E:osc conv div grew | x:osc conv div | reading -0.05 | E:Y - - Y | x:Y - - | DIVERGES (energy grew) - 0 | E:Y - - - | x:Y - - | x OSCILLATES, energy settles + 0.0 | E:Y - - - | x:Y - - | x OSCILLATES, energy settles 0.1 | E:- - - - | x:Y - - | x OSCILLATES, energy settles 0.4 | E:- Y - - | x:Y Y - | x OSCILLATES, energy settles - 1 | E:- Y - - | x:- Y - | settles - 2 | E:- Y - - | x:- - - | settles + 1.0 | E:- Y - - | x:- Y - | settles + 2.0 | E:- Y - - | x:- - - | settles the gem is the zeta=0 row: energy conserved -> never converges, while x oscillates. Same system, opposite verdict, set by what the observer watches. diff --git a/tests/observer_corpus/golden/dynamics__solve.out b/tests/observer_corpus/golden/dynamics__solve.out index 9a3f2aa3d..0cab89b3a 100644 --- a/tests/observer_corpus/golden/dynamics__solve.out +++ b/tests/observer_corpus/golden/dynamics__solve.out @@ -7,7 +7,7 @@ Jacobi Ax=b -> Gauss-Seidel Ax=b -> 21 iters (fresh values -> fewer than Jacobi) -Power iteration dominant eigenvalue -> 2 (14 iters, expect ~2) +Power iteration dominant eigenvalue -> 2.0 (14 iters, expect ~2) PageRank stationary distribution -> (48 iters) diff --git a/tests/observer_corpus/golden/iLambdaAi__test_interrogative_spec.out b/tests/observer_corpus/golden/iLambdaAi__test_interrogative_spec.out index 0af5af719..8c2b9a80d 100644 --- a/tests/observer_corpus/golden/iLambdaAi__test_interrogative_spec.out +++ b/tests/observer_corpus/golden/iLambdaAi__test_interrogative_spec.out @@ -39,15 +39,15 @@ WHERE returns entropy (number >= 0): --- WHY: gradient (dH/dt) --- WHY returns gradient (number): -0 +0.0 --- HOW: stability (0-1) --- HOW returns stability (0-1 range): -1 +1.0 --- ARITHMETIC COMPOSITION --- Arithmetic on interrogatives works: -102 +102.0 --- ASSIGNMENT THEN INTERROGATIVE --- 256 diff --git a/tests/observer_corpus/programs/iLambdaAi__test_halting_descent.eigs b/tests/observer_corpus/programs/iLambdaAi__test_halting_descent.eigs index 627e94b4c..174081f64 100644 --- a/tests/observer_corpus/programs/iLambdaAi__test_halting_descent.eigs +++ b/tests/observer_corpus/programs/iLambdaAi__test_halting_descent.eigs @@ -2,8 +2,8 @@ print of "=== HALTING: BOUNDED DESCENT TEST ===" print of "--- HD1: Entropy strictly decreases each iteration ---" counter is 0 -x is 10 -x is 10 +x is 10.0 +x is 10.0 loop while not converged: counter is counter + 1 diff --git a/tests/phase2/MAP.md b/tests/phase2/MAP.md new file mode 100644 index 000000000..1f63a4e57 --- /dev/null +++ b/tests/phase2/MAP.md @@ -0,0 +1,676 @@ +# tests/phase2/MAP.md: #1758 items to the test rows that pin them + +This is the skeleton, committed in round 0 with `docs/design/phase2-numbers.md` +and revised in design round 2 (row shapes, tier witnesses, the leaf +differential, the record-twice tape rows). +Round 1 writes each row as `tests/phase2/rows/.eigs` (or `.sh` for a +row that needs a shell: tape, env, files), with its exact expected stdout in +`.out`. + +`tools/phase2_check.sh ` (design: §9.2 of the +design doc) runs every row: +- against the **v0.46.0 tag** (`4a3a5c19`, built from the tag commit, not + main) and the branch; +- in four tiers: `EIGS_JIT_OFF=1`, the default JIT, + `EIGS_JIT_OSR_THRESHOLD=1`, and `trace` (`EIGS_TRACE=/t.tape`, + default JIT). The leaf evaluator is off under trace, so the `trace` tier is + the leaf differential: a row must print the same in it as in the others. + +Every change row must FAIL on the tag and PASS on the branch in every tier, +and its witness must hold on the branch (design §9.1), or it is VACUOUS. +`phase2_check.sh --through ` checks only the rows whose `[Rn]` has +landed by that commit (a row tagged `[Ra/Rb]` lands with the later); R1.1, +R1.2 and R2.1 have no rows of their own and are checked by their mechanical +gates (design §9.2). + +## Format (machine-read by `phase2_check.sh`) + +- `### ` opens an item. The items are fixed by #1758 Revision 1: `N1` + to `N14`, `N4b`, `A`, `B`, `T1`, `O1` to `O4`. Every item must have at + least one row. +- ``- `` [Rn] `` is one row. Below it, two more + lines: `-> `, and `tag: `. + A row whose program pins several items lists them in its file header + (`# ITEMS: O3 T1`); it appears here once, under its first item. + - The row id is the file stem. Every id must have a file, and every file + must be listed here. + - `[Rn]` is the commit of the round plan that makes the row pass on the + branch. +- In program sketches, ` / ` separates source lines and `{try X catch}` means: + ``` + try: + X + catch e: + print of [e.kind, ...] + ``` + The row file holds the exact program. +- `tag:` says why the row fails on v0.46.0: + - `(probed main)` is output observed on the main-checkout binary (0.46.0, + built 2026-10-13). Main differs from the tag only by #1740/#1741 + (bitwise and range conversion) and #1735. + - `(read)` is derived from the tag's source. +- **Invariants** (`[Rn, invariant]`, R1.7-D3) already pass on the pre-round base. They run on every declared branch tier and are counted separately from change rows; the v0.46 anchor predates their landed contract. Pre-registration records base-green evidence. +- **Controls** (`ctl.*`) must PASS on both binaries. They guard behaviour + the branch must preserve, and are counted separately: never in + `tag_fail`. +- **Gates** (`gate.*`) name a check the Done-when runs on its own (the + observer oracle with `--floor`, the db parameter test). `phase2_check.sh` + lists them and verifies their command exists, but does not run or count + them as rows. +- **Shape and witness.** A row without a `shape:` line is shape `S` + (straight-line module code, interpreted in every tier, witness `none`). + The other shapes carry a fourth line, `shape: witness: + ... [] ops: ...`: + - `L`: a one-line helper (the leaf evaluator); `F`: a multi-statement + function called at least 60 times (the function JIT), whose witnessed + call is its LAST call (`EIGS_JIT_HOT` records the last run); `O`: a + `loop while true:` loop, in a function or at module level, witnessed in + the `osr` tier, whose only exit is a `return` right after the claimed ops + (at module level, `print of ` first). + - witness kinds (design §9.1): `leaf ` (no `EIGS_JIT_HOT` entries for + `` in the normal run, K >= 1 in the `trace` run), `leaf-gen ` + (the same K >= 1 entries in both: every call took the generic path), + `fn ` (`jit yes`, `adv RET`), `fn-stop ` (`jit yes`, the + op at `adv` is ``), `osr ` (`osr yes` and `oadv == len`: the + last OSR entry reached the return sentinel, so it ran the body natively), + `osr-stop ` (`oadv != len`, and the op at `oent + oadv` is + ``). Each kind was calibrated on main against a plant where the + claimed op did not run as claimed (design §9.1). + - `leaf` and `leaf-gen` may name several helpers; each must hold. + - `ops:` lists the opcodes whose JIT or leaf arm the row exercises: run + natively, or stopped at exactly (a `-stop` witness's ``, which proves + the guard or the scanner's refusal of that op). Ops after a stop are not + listed. `phase2_check.sh` requires the union over all `L`/`F`/`O` rows to + cover every JIT-emitter and leaf-evaluator arm that holds a numeric site (the + inventory file's `## Numeric arms` lists them: 15 emitter arms and 5 leaf + arms today). + - The witnesses state round 1's expectation. The JIT and the leaf bail on + an int that meets an arithmetic, comparison, bitwise or index op, or on + an int literal, until round 5, so such a row's witness is a `-stop` or + `leaf-gen`: the guard fired and the interpreter produced the output. An + int that only moves (into a local, a field, a list element or a module + binding) passes through the JIT's C helpers and the leaf's borrows, so + those rows' witnesses are native, as are the float rows'. + `(witness probed main)` marks a witness observed on the main binary + (with a double where the row passes an int); the others are read from + design §2.7, §4.1 and §4.5. + +--- + +### N1 +- `N1.negative-past-int64-is-float` [R1.7, invariant] R1.7-D1: `-9223372036854775809` is a float equal to INT64_MIN; N6 prints Python repr + -> `[true, "float"]`, then `-9.223372036854776e+18` + tag: num_type undefined; R1.7-D3 prints the literal separately; observed base already prints the N6 text (15..17 round-trip writer), so the entire row is an invariant under D3. +- `N1.leaf-float-kind` [R1.3] leaf helpers return 1.0, 0.0 and 0.5; `num_type` checks their kinds before N6 changes their display + -> `["float", "float", "float"]` + tag: `num_type` undefined. + shape: L witness: leaf onef zerof halff ops: NUM_ONE NUM_ZERO CONST +- `N1.fn-float-kind` [R1.3] a hot function computes `a * 1.0 + 0.0`, then checks the result's kind + -> `["float", true]` + tag: `num_type` undefined. + shape: F witness: fn f ops: NUM_ONE NUM_ZERO MUL ADD +- `N1.literal-kind` [R1.3] `print of [num_type of 3, num_type of 3.0, num_type of 1e3, num_type of 0x10]` + -> `["int", "float", "float", "int"]` + tag: `num_type` is undefined, an error (read) +- `N1.exact-past-2p53` [R1.3] `print of [9007199254740993, 9223372036854775807]` + -> `[9007199254740993, 9223372036854775807]` + tag: `[9007199254740992, 9.223372036854776e+18]` (probed main) +- `N1.decimal-past-int64-is-float` [R1.3] `x is 9223372036854775808 / print of [num_type of x, x]` + -> `["float", 9.223372036854776e+18]` + tag: `num_type` undefined (read) +- `N1.hex-wraps` [R1.3] `print of [0x1FFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF]` + -> `[-1, -1, 9223372036854775807]` + tag: `[3.6893488147419103e+19, 1.8446744073709552e+19, 9.223372036854776e+18]` (probed main) +- `N1.canon-eq` [R1.3] `a is abs of -5 / b is 0 - 5 / print of [a == 5, -b == 5, num_type of a]` + -> `[true, true, "int"]` + tag: `num_type` undefined. (Round 0 named this row as plant 1's check; it cannot see that plant, because everything compares by value (design §2.1). Plant 1's check is `tests/phase2/slot_layer.c`.) +- `N1.leaf-int-literal` [R1.3] `define big(x) as: return 9007199254740993 / define one(x) as: return 1 / print of [big of 0, one of 0]` + -> `[9007199254740993, 1]` + tag: `[9007199254740992, 1]` (probed main, all three tiers) + shape: L witness: leaf-gen big one (the leaf scanner refuses `OP_INT_CONST` and `OP_INT_ONE`) ops: INT_CONST INT_ONE +- `N1.leaf-float-literal` [R1.7] `define onef(x) as: return 1.0 / define zerof(x) as: return 0.0 / define halff(x) as: return 0.5 / print of [onef of 7, zerof of 7, halff of 7]` + -> `[1.0, 0.0, 0.5]` (the leaf's `NUM_ONE`, `NUM_ZERO` and `CONST` keep their float meaning) + tag: `[1, 0, 0.5]` (probed main) + shape: L witness: leaf onef zerof halff (witness probed main: no entries normally, 1 each under trace) ops: NUM_ONE NUM_ZERO CONST +- `N1.leaf-dot-get` [R1.3] `define px(p) as: return p.x / d is {"x": 9007199254740993} / print of (px of d)` + -> `9007199254740993` (an int field is borrowed through the leaf with no arithmetic, so the leaf computes it even in round 1) + tag: `9007199254740992` (probed main) + shape: L witness: leaf px (witness probed main: no entries normally, 1 under trace) ops: LOCAL_DOT_GET +- `N1.fn-float-literals` [R1.7] `define f(a) as: t is a * 1.0 / u is t + 0.0 / return u`, called 60 times with `2.0`; print the last result + -> `2.0` (the JIT's `NUM_ONE`/`NUM_ZERO` emitters keep their float meaning) + tag: `2` (probed main) + shape: F witness: fn f (witness probed main: `f 60 yes ... RET`) ops: NUM_ONE NUM_ZERO MUL ADD +- `N1.fn-int-literal` [R1.3] `define g(a) as: warm is 0.5 / t is a + 1 / return t * 2`, called 60 times with `9007199254740992`; print the last result + -> `18014398509481986` + tag: `18014398509481984` (probed main) + shape: F witness: fn-stop g INT_ONE (the JIT scanner refuses the new opcode) ops: INT_ONE +- `N1.fn-dot-fields` [R1.3] `define setx(p, q) as: p.x is q / return p.x`, called 60 times with `[{"x": 1}, 9007199254740993]` (a fresh dict each time); print the last result + -> `9007199254740993` (the field store and load take their C helpers for a non-double and the thunk runs on) + tag: `9007199254740992` (probed main) + shape: F witness: fn setx (witness probed main, with a double: `setx 60 yes ... RET`) ops: LOCAL_DOT_SET LOCAL_DOT_GET +- `ctl.N1.fn-int-truthiness` (control) `define truth(a) as: if a: return 1 / return 2`, called 60 times with `0`; `print of [truth of 0.0, truth of 5, truth of 0]` (the int call is LAST, so `EIGS_JIT_HOT` records it) + -> `[2, 1, 2]` on both (0 and 0.0 are falsy) + shape: F witness: fn-stop truth JUMP_IF_FALSE (an SMI condition fails the bool and number checks) ops: JUMP_IF_FALSE +- `ctl.N1.fn-int-not` (control) `define neg(a) as: r is not a / return r`, called 60 times with `0`; `print of [neg of 0.0, neg of 5, neg of 0]` (the int call is LAST) + -> `[true, false, true]` on both + shape: F witness: fn-stop neg NOT (an SMI fails NOT's number check) ops: NOT +- `N1.fn-int-local` [R1.3] `define keep(q) as: t is q / return t`, called 60 times with `9007199254740993`; print the last result + -> `9007199254740993` (an int stored into a local takes `SET_LOCAL`'s generic swap and the thunk runs on) + tag: `9007199254740992` (probed main) + shape: F witness: fn keep (witness probed main, with a double: `keep 60 yes ... RET`) ops: SET_LOCAL POP +- `N1.osr-global-int-store` [R1.3] `x is 0 / define put(big, n) as: i is 0.0 / loop while true: (x is big / i is i + 1.0 / if i >= n: return x)`; `print of (put of [9007199254740993, 4.0])` (`x is big` inside `put` writes the module binding: `SET_NAME`) + -> `9007199254740993` + tag: `9007199254740992` (probed main) + shape: O witness: osr put (the boxed int moves through `SET_NAME` without a guard; witness probed main with a double: `oadv 106` = `len 106`) ops: SET_NAME NUM_ONE ADD GE JUMP_IF_FALSE +- `N1.osr-int-counter` [R1.3] `define count(n) as: i is 9007199254740990 / k is 0.0 / loop while true: (i is i + 1 / k is k + 1.0 / if k >= n: return i)`; `print of (count of 10.0)` + -> `9007199254741000` + tag: `9007199254740992`: the double counter stalls at 2^53 (probed main) + shape: O witness: osr-stop count INT_ONE (the OSR scanner stops at the literal `1`, the body's first int op) ops: INT_ONE + +### N2 +- `N2.sort-by-exact` [R1.3] numeric key callbacks return both int and float keys around 2^53, with a stable equal-key pair + -> `["low", "tie-a", "tie-b", "high"]` + tag: the high int rounds to the low float, so the input order survives. +- `N2.int-eq-float-kinds` [R1.3] `print of [1 == 1.0, 1 != 1.0, num_type of 1, num_type of 1.0]` + -> `[true, false, "int", "float"]` + tag: `num_type` undefined (read) +- `N2.exact-eq-past-2p53` [R1.3] `print of [9007199254740993 == 9007199254740992.0, 9007199254740993 != 9007199254740992.0]` + -> `[false, true]` + tag: `[true, false]` (probed main: the lexer rounds) +- `N2.exact-order` [R1.3] `print of [9007199254740993 > 9007199254740992.0, 9223372036854775807 < 9223372036854775808.0]` + -> `[true, true]` + tag: `[false, false]` (probed main for the second) +- `N2.membership-exact` [R1.3] `print of [list_contains of [[9007199254740993], 9007199254740992.0], list_index_of of [[1, 9007199254740993], 9007199254740992.0]]` + -> `[false, -1]` + tag: `[true, 1]` (read: everything is a double) +- `N2.sort-exact` [R1.3] `print of (sort of [9007199254740993, 9007199254740992, -1])` + -> `[-1, 9007199254740992, 9007199254740993]` + tag: the literal rounds, so `[-1, 9007199254740992, 9007199254740992]` (probed main, same shape) + round: R1.2 wrote the exact int comparator; the first int literals make it live in R1.3. +- `N2.fn-exact-compare` [R1.3] `define lt(a, b) as: t is a < b / return t`, called 60 times with `[9007199254740992, 9007199254740993]`; print the last result + -> `true` + tag: `false` (probed main) + shape: F witness: fn-stop lt LT (a boxed int fails the operand guard) ops: LT +- `N2.fn-compare-all` [R1.3] `define cmp(a, b) as: e is a == b / n is a != b / g is a > b / l is a <= b / q is a >= b / return [e, n, g, l, q]`, called 60 times with `[9007199254740993, 9007199254740992.0]`; print the last result + -> `[false, true, true, false, true]` (N2's exact cross-kind compare) + tag: `[true, false, false, true, true]` (probed main) + shape: F witness: fn-stop cmp EQ (a boxed int fails the first comparison's guard) ops: EQ +- `ctl.N2.fn-float-compare` (control) `define ltf(a, b) as: t is a < ( / b) / return t`, called 60 times with `[0.5, 0.75]`; the written comparison spans physical lines (#1425) + -> `true` on both + shape: F witness: fn ltf (the native full return must cross both operator-line markers) ops: BINARY_LINE LT BINARY_LINE_END +- `ctl.N2.float-eq` (control) `print of [0.5 + 0.25 == 0.75, 1 == 1.0, 3 < 3.5]` + -> `[true, true, true]`, both binaries + +### N3 +- `N3.int-ops-kind` [R1.3] `print of [num_type of (2 + 3), num_type of (7 - 9), num_type of (6 * 7), num_type of (2 + 0.5)]` + -> `["int", "int", "int", "float"]` + tag: `num_type` undefined +- `N3.exact-product` [R1.3] `print of [3037000499 * 3037000499, 4503599627370496 * 2 + 1]` + -> `[9223372030926249001, 9007199254740993]` + tag: `[9.223372030926249e+18, 9007199254740992]` (probed main) +- `ctl.N3.small-ints` (control) `print of [1 + 2, 10 - 3, 6 * 7, -4]` + -> `[3, 7, 42, -4]`, both binaries +- `N3.leaf-mul-exact` [R1.3] `define sq(x) as: return x * x / print of (sq of 94906267)` + -> `9007199515875289` + tag: `9007199515875288` (probed main; the critic's gdb probe shows the leaf computed it there) + shape: L witness: leaf-gen sq (an SMI operand makes the leaf's MUL bail) ops: MUL +- `N3.leaf-add-exact` [R1.3] `define add2(a, b) as: return a + b / print of (add2 of [9007199254740992, 1])` + -> `9007199254740993` + tag: `9007199254740992` (probed main) + shape: L witness: leaf-gen add2 ops: ADD +- `ctl.N3.leaf-float` (control) `define sqf(x) as: return x * x / print of (sqf of 1.5)` + -> `2.25` on both + shape: L witness: leaf sqf (witness probed main: no entries normally, 1 under trace) ops: MUL +- `N3.fn-arith-exact` [R1.3] `define f(a, b) as: t is a * ( / b) / u is t + a / return u - b`, called 60 times with `[94906267, 94906267]`; print the last result; the operator-line stamp precedes the int guard fallback + -> `9007199515875289` + tag: `9007199515875288` (probed main) + shape: F witness: fn-stop f MUL ops: BINARY_LINE MUL +- `N3.osr-sum-exact` [R1.3] `define total(n, step) as: acc is 9007199254740000 / i is 0.0 / loop while true: (acc is acc + step / i is i + 1.0 / if i >= n: return acc)`; `print of (total of [1000.0, 1])` + -> `9007199254741000` + tag: `9007199254740992`: the double sum stalls at 2^53 (probed main) + shape: O witness: osr-stop total ADD (a boxed int and an SMI fail ADD's operand guard) ops: ADD +- `N3.osr-module-sum` [R1.3] module-level `acc is 9007199254740000 / step is 1 / i is 0.0 / loop while true: (acc is acc + step / i is i + 1.0 / if i >= 1000.0: (print of acc / return acc))` + -> `9007199254741000` + tag: `9007199254740992` (probed main) + shape: O witness: osr-stop ADD (module-level OSR; a boxed int fails ADD's operand guard) ops: ADD +- `ctl.N3.osr-float` (control) `define totalf(n) as: acc is 0.5 / i is 0.0 / loop while true: (acc is acc + ( / 1.5) / i is i + 1.0 / if i >= n: return acc)`; `print of (totalf of 100.0)`; the addition spans physical lines (#1425) + -> `150.5` on both + shape: O witness: osr totalf (the sole in-body return must follow both operator-line markers) ops: BINARY_LINE ADD BINARY_LINE_END CONST NUM_ONE GE JUMP_IF_FALSE SET_LOCAL POP + +### A +- `A.add-overflow-raises` [R1.3] `{try print of (9223372036854775807 + 1) catch}`, printing `[e.kind, (index_of of [e.message, "+"]) >= 0]` + -> `["value", true]` + tag: prints a rounded double, no raise (read). **Plant 4's named check.** +- `A.sub-overflow-raises` [R1.3] `{try print of (-9223372036854775807 - 2) catch}`, the same print with `"-"` + -> `["value", true]` + tag: a double, no raise +- `A.mul-overflow-raises` [R1.3] `{try print of (4611686018427387904 * 2) catch}`, the same print with `"*"` + -> `["value", true]` + tag: `9.223372036854776e+18`, no raise +- `A.sum-overflow-raises` [R1.6b] `{try print of (sum of [9223372036854775807, 1]) catch}` + -> `["value", true]`, the message naming `sum` + tag: a double sum, no raise +- `A.tensor-scalar-overflow-raises` [R1.6b] `{try print of (multiply of [4611686018427387904, 2]) catch}` + -> `["value", true]`, the message naming `multiply` + tag: a double +- `A.leaf-mul-overflow` [R1.3] `define sq(x) as: return x * x / {try print of (sq of 3037000500) catch}`, printing `e.kind` + -> `value` (the leaf bails; the generic path raises with the caller's line) + tag: `9.22337203700025e+18`, no raise (probed main) + shape: L witness: leaf-gen sq ops: MUL +- `A.fn-add-overflow` [R1.3] `define inc(a) as: warm is 0.5 / t is a + 1 / return t`, called 60 times with small ints, then `{try print of (inc of 9223372036854775807) catch}`, printing `e.kind` + -> `value` + tag: `9.223372036854776e+18`, no raise (probed main) + shape: F witness: fn-stop inc INT_ONE ops: INT_ONE +- `A.osr-double-overflow` [R1.3] `define dbl(n, two) as: x is two / i is 1.0 / loop while true: (x is x * two / i is i + 1.0 / if i >= n: return x)`; `{try print of (dbl of [63.0, 2]) catch}`, printing `e.kind` + -> `value` (2^63 overflows on the 63rd doubling) + tag: `9.223372036854776e+18`, no raise (probed main) + shape: O witness: osr-stop dbl MUL (two SMIs fail MUL's operand guard) ops: MUL + +### B +- `B.type-of-both-num` [R1.3] `print of [type of 3, type of 3.0, (type of 3) == (type of 3.0), num_type of 3]` + -> `["num", "num", true, "int"]` + tag: `num_type` undefined +- `ctl.B.type-of` (control) `print of [type of 3, type of 3.0, type of -2]` + -> `["num", "num", "num"]`, both binaries + +### N4 +- `N4.div-always-float` [R1.7] `print of [7 / 2, 6 / 2, num_type of (6 / 2)]` + -> `[3.5, 3.0, "float"]` + tag: `[3.5, 3, ...]` then `num_type` undefined (probed main for `[3.5, 3]`) +- `N4.floordiv` [R1.4] `print of [7 // 2, -7 // 2, 7.5 // 2, num_type of (7 // 2), num_type of (7.5 // 2)]` + -> `[3, -4, 3, "int", "float"]` (R1.4 text; N6 adds `.0` to the float at R1.7) + tag: parse error, `unexpected '/'` (probed main) +- `N4.floor-mod` [R1.4] `print of [-7 % 3, 7 % -3, -7.5 % 2, 7 % 3]` + -> `[2, -2, 0.5, 1]` + tag: `[-1, 1, -1.5, 1]`, i.e. fmod (probed main) +- `N4.int-floordiv-zero-raises` [R1.4] `{try print of (7 // 0) catch}`, printing `e.kind` + -> `value` + tag: parse error +- `N4.float-div-zero-inf` [R2.2] `print of [1.0 / 0, -1.0 / 0, 1 / 0]` + -> `[inf, -inf, inf]` + tag: raises `division by zero` (read) +- `N4.float-zero-div-zero-nan` [R2.2] `x is 0.0 / 0 / print of [x == x, num_type of x]` + -> `[false, "float"]` + tag: raises +- `N4.float-mod-zero-nan` [R2.2] `x is 5.5 % 0 / print of (x == x)` + -> `false` + tag: raises `modulo by zero` +- `ctl.N4.int-mod-zero-raises` (control) `{try x is 7 % 0 catch}`, printing `e.kind` + -> `value`, both binaries (probed main) +- `N4.fn-floor-mod` [R1.4] `define m(a, b) as: t is a % b / return t`, called 60 times with `[-7, 3]`; `print of [r, m of [-7.5, 2]]` + -> `[2, 0.5]` (floor modulo for an int and a float) + tag: `[-1, -1.5]` (probed main) + shape: F witness: fn-stop m MOD (the JIT scanner refuses MOD in round 1) ops: MOD +- `N4.fn-div-float` [R1.7] `define d(a, b) as: t is a / b / return t`, called 60 times with `[6, 2]`; print the last result + -> `3.0` + tag: `3` (probed main) + shape: F witness: fn-stop d DIV (SMI operands fail DIV's operand guard) ops: DIV + +- `N4.fn-floordiv` [R1.4] a multi-statement `d(a, b)` returns `a // b`, called 60 times with `[-7.5, 2]`; print the value and kind + -> `[-4, "float"]` (R1.4 float text) + tag: parse error + shape: F witness: fn-stop d IDIV (the scanner's default refuses IDIV) ops: IDIV +- `N4.compound-floordiv` [R1.4] apply `//=` to a name, a dict field and list indices, with int and float operands of both signs + -> `[-4, -4, [-4, -4], "int", "float"]` + tag: parse error +- `N4.float-floordiv-zero-raises` [R1.4] float/mixed `// 0`, including `0.0 // 0.0` and negative zero + -> `["value", "division by zero"]` four times (same as `/`, until N11; #1758 comment 6294926822) + tag: parse error + +- `N4.float-floordiv-rounding` [R1.4] Python float-divmod quotient at decimal boundaries: `1.0 // 0.1`, mixed `1 // 0.1`, negative pairs, `7.5 // 0.1`, opposite signs, `3.0 // -0.1` quotient snapping and result kind + -> `[9, 9, 9, 74, -10, -30, "float"]` + tag: parse error +- `N4.float-floordiv-underflow` [R1.4] `1e-300 // -1e300`, the sign-reversed pair, a positive underflow and result kind + -> `[-1, -1, 0, "float"]` + tag: parse error +- `N4.fn-float-floordiv-rounding` [R1.4] multi-statement `rd(a, b)` called 60 times with `[1.0, 0.1]`, then mixed/negative/7.5 and quotient-snapping boundaries + -> `[9, 9, 9, 74, -30, "float"]` + tag: parse error + shape: F witness: fn-stop rd IDIV (the scanner's default refuses IDIV) ops: IDIV +- `N4.fn-float-floordiv-underflow` [R1.4] multi-statement `ud(a, b)` called 60 times with `[1e-300, -1e300]`, then the sign-reversed pair + -> `[-1, -1, "float"]` + tag: parse error + shape: F witness: fn-stop ud IDIV (the scanner's default refuses IDIV) ops: IDIV +- `N4.compound-float-floordiv-boundaries` [R1.4] `//=` at the decimal boundary on a name and dict field, and both underflow signs on list indices + -> `[9, 9, [-1, -1], "float", "float"]` + tag: parse error + +### N4b +- `N4b.neg-min-raises` [R1.3] `m is -9223372036854775807 - 1 / {try print of (-m) catch}` + -> `["value", true]`, the message naming the unary minus + tag: a double, no raise. **Plant 5's named check.** +- `N4b.abs-min-raises` [R1.3] `m is -9223372036854775807 - 1 / {try print of (abs of m) catch}` + -> `["value", true]` + tag: `9.223372036854776e+18`, no raise +- `N4b.floordiv-min-by-neg1-raises` [R1.4] `m is ... / {try print of (m // -1) catch}` + -> `["value", true]` + tag: parse error +- `N4b.min-mod-neg1-zero` [R1.4] `m is ... / print of [m % -1, num_type of (m % -1)]` + -> `[0, "int"]`, with no hardware fault (x86 `idiv` faults on `INT64_MIN % -1`) + tag: `num_type` undefined +- `N4b.fn-neg-min` [R1.3] `define n(a) as: t is -a / return t`, called 60 times with small ints, then `m is -9223372036854775807 - 1 / {try print of (n of m) catch}`, printing `e.kind` + -> `value` + tag: `9.223372036854776e+18`, no raise (probed main) + shape: F witness: fn-stop n NEG ops: NEG + +### N5 +- `N5.http-port-before-config` [R1.6f] C6: an effective PORT=65536 raises value in early_bind and serve before binding or freezing route configuration. Each caught refusal is followed by successful route registration. +- `N5.net-send-byte-raises` [R1.6f] R1.6d-D1 / C7: matched server builds reject buffer/list bytes 256 and 1.5 before any send; the peer receives only the subsequent valid integral-float payload `ok`. Counts are ints. The raw stored-byte half is `gate.N5.net-byte-boundary` (R1.6f-D2). +- `gate.N5.net-byte-boundary` [R1.6f] `tests/phase2/net_byte_boundary.c` through `tools/phase2_readers.sh`: R1.6d-D1 net_send byte site. Raw stored NaN/infinities, the first-NaN INVALID/diagnostic and valid 255 are checked in both strict modes through the actual client and a real socketpair; a refusal sends no valid prefix (`phase2-net-byte: PASS checks=72 failures=0`). R1.6f-D2 moved it out of the row: compiled and executed once per `--through` at or after R1.6f, outside the rows' per-tier 30 s timeout, in which its amalgamation build timed out. +- `N5.store-handle-fraction-raises` [R1.6f] R1.6f-D1 (#1924 comment 6367017285): all nine shared store handle-reader callers reject a fractional `_store_id` and a fractional `_store_gen` with `value` before store access; no frozen expectation changes. +- `gate.N5.iterator-metadata` [R1.3] `tests/phase2/slot_layer.sh` Part E builds raw iterator states and runs the interpreter and `jit_helper_iter_next` + -> float controls and both SMI/boxed index/snapshot metadata yield 42; range and malformed states preserve the pinned exhaustion/ignore rules. + tag: n/a (a C fixture of the four R1.2 refusal sites); final R1.3 arms accept exact C-int indices and clamp snapshots without narrowing. +- `N5.substr-fraction-raises` [R1.6c] `{try print of (substr of ["hello", 1.5, 2]) catch}`, printing `e.kind` + -> `value` + tag: `el` (probed main: truncation) +- `N5.store-key-fraction-raises` [R1.6b] `s is store_open of "/db" / k is store_put of [s, "c", {"v": 1}] / {try print of (store_get of [s, "c", 1.9]) catch}` + -> `value` + tag: returns record "1" (probed main) +- `N5.bitwise-fraction-raises` [R1.5] `{try print of (1.5 | 0) catch}`, printing `e.kind` + -> `value` + tag: `1` (the bar critic's probe of the tag; main already raises: #1740) +- `N5.integral-float-converts` [R1.5] `print of [6.0 | 1, num_type of (6.0 | 1), (list_slice of [[1, 2, 3], 1.0, 2.0])]` + -> `[7, "int", [2]]` + tag: `num_type` undefined +- `N5.range-fraction-raises` [R1.6b] `{try print of (range of 2.5) catch}`, printing `e.kind` + -> `value` + tag: no raise (read: `range_bound` accepts any double inside the int range) +- `N5.gather-index-fraction-raises` [R1.6d] `t is buffer of [2, 3]` filled 10, 11, 12, 20, 21, 22 / `ib is buffer of 2 / ib[0] is 1.5 / ib[1] is 2.0 / {try g is gather of [t, ib] / print of [g[0], g[1]] catch}`, printing `e.kind` + -> `value` (an index is integer-valued: N5) + tag: `[11, 22]`: `flat_index_at` truncates 1.5 to 1 (probed main; the critic's `gather.eigs`) +- `N5.pcm16-byte-raises` [R1.6d] `b is buffer of 2 / b[0] is 300 / b[1] is 0 / {try buf_from_pcm16le of [b, 0, 1] catch}` printing `e.kind`, then the same with `b[0] is 1.5` + -> `value` twice (byte values: N5, then 0..255; #1758 comment 6174964306) + tag: no raise, samples `0.009155552842799158` and `4.577776421399579e-05` (probed main) +- `N5.audio-channel-domain` [R1.6e] R1.6e-D1 (#1922 comment 6355101247): audio_open, audio_capture_open and audio_stream_open reject channels 0, 256, 257, -1 and 1.5 with `value` at the N5/domain reader; 255 and 1 pass that reader (unsupported counts remain SDL's to refuse). Validation precedes SDL/device/tape side effects. +- `gate.N5.gfx-byte-boundary` [R1.6e] `tests/phase2/gfx_byte_boundary.c` through `tools/phase2_readers.sh`: R1.6d-D1 pre-registration for buffer_read_byte's gfx_fb and ppu_render_frame callers. Both reject stored lenient NaN and every infinity with `value`; strict first NaN preserves INVALID/diagnostic before a later infinity. Q1 rejects 1.5 and 256, while integral float 255 succeeds. Actual client calls and rendered/framebuffer sentinels prove that rejection at the first palette byte or first LCDC register stops before the sink changes; the PPU retains its existing progressive writes after valid reads. +- `gate.N5.q7-readers` [R1.6e] `tests/phase2/q7_readers.c` (through the amalgamation, no SDL): `eigs_geom_shape` clips `RECT [-3e9, 0, 6e9, 10]` on a 640x480 drawable area to `[0, 0, 640, 10]` (saturating each argument would give half of that), clips a `LINE` with Liang-Barsky, bounds a `CIRCLE` of radius 3e9 to the visible rows; `eigs_bounded_arg` clamps 300 to 255; `eigs_magnitude_arg` raises `value` on 3e12 for a C `int` + -> every case as stated + tag: n/a (a C test of new readers), so it is a gate +- `N5.leaf-buffer-index` [R1.7] `define bat(b, i) as: return b[i] / b is buffer of 3 / print of (bat of [b, 1])` + -> `0.0` (a buffer element is an f64, Q2) + tag: `0` (probed main) + shape: L witness: leaf-gen bat (an SMI index makes the leaf's INDEX_GET bail in round 1) ops: INDEX_GET +- `ctl.N5.leaf-float-index` (control) `define nth(xs, i) as: return xs[i] / print of (nth of [[10, 20, 30], 1.0])` + -> `20` on both (an integral float index converts, N5) + shape: L witness: leaf nth (witness probed main: no entries normally, 1 under trace) ops: INDEX_GET +- `N5.osr-index-set` [R1.3] `define fill(big, n) as: xs is [0, 0, 0, 0] / i is 0.0 / loop while true: (xs[i] is big / i is i + 1.0 / if i >= n: return xs)`; `print of (fill of [9007199254740993, 4.0])` (the critic's `osr_ret`) + -> `[9007199254740993, 9007199254740993, 9007199254740993, 9007199254740993]` (an integral float index converts, N5; the int is stored exactly) + tag: four times `9007199254740992` (probed main) + shape: O witness: osr fill (the float counter keeps the loop native; the boxed int takes `INDEX_SET`'s C helper; witness probed main: `oadv 123` = `len 123`, 3 native `jit_helper_index_set` calls under gdb) ops: INDEX_SET NUM_ONE ADD GE JUMP_IF_FALSE SET_LOCAL POP + +### N6 +- `gate.N6.const-dedup-bits` [R1.7] `tools/phase2_readers.sh` release/asan `const_dedup_bits`: direct `chunk_add_constant` of +0.0 and -0.0 has distinct indices and memcmp-exact stored doubles (R1.7-D4); repeated identical bits deduplicate. +- `N6.float-repr` [R1.7] `print of [3.0, 1e3, -0.0, 0.1 + 0.2, 1e15, 1e16, 1e-05, 0.0001]` + -> `[3.0, 1000.0, -0.0, 0.30000000000000004, 1000000000000000.0, 1e+16, 1e-05, 0.0001]` + tag: `[3, 1000, 0, 0.30000000000000004, 1000000000000000, 1e+16, 1e-05, 0.0001]` (probed main for the first seven) +- `N6.int-exact-digits` [R1.7, invariant] `print of [9007199254740993, -9223372036854775807 - 1]` + -> `[9007199254740993, -9223372036854775808]` + tag: rounded doubles +- `N6.str-fstring` [R1.7] `x is 2.0 / print of [str of x, f"{x}", str of 2]` + -> `["2.0", "2.0", "2"]` + tag: `["2", "2", ...]` (probed main) +- `N6.neg-zero-literal-dedup` [R1.7] `a is 0.0 / b is -0.0 / print of [a, b]` + -> `[0.0, -0.0]` + tag: `[0, 0]`. (The constant pool dedup folds `-0.0` into `+0.0`, chunk.c:196; the row pins that a literal `-0.0` survives a chunk that also holds `0.0`.) +- `N6.nonfinite` [R2.5] `print of [1.0 / 0, -1.0 / 0, 0.0 / 0, -(0.0 / 0)]` + -> `[inf, -inf, nan, nan]` (never `-nan`) + tag: raises +- `ctl.N6.small-ints` (control) `print of [1, 2, 3, -4, 100000]` + -> `[1, 2, 3, -4, 100000]`, both binaries + +### N7 +- `N7.num-type-float` [R1.3] `print of [num_type of 3.0, num_type of -0.0, num_type of 1e300, num_type of 2.5]` + -> `["float", "float", "float", "float"]` + tag: `num_type` undefined. **Plant 6's named check.** +- `N7.num-type-int` [R1.3] `print of [num_type of 0, num_type of -1, num_type of 9223372036854775807, num_type of (2 * 3)]` + -> `["int", "int", "int", "int"]` + tag: undefined +- `N7.num-type-non-number` [R1.3] `print of [num_type of "3", num_type of null, num_type of true, num_type of ([1])]` + -> `[null, null, null, null]` (Lua `math.type` returns fail) + tag: undefined +- `gate.N7.embed-int` [R1.8] `make embed-smoke` with new rows: `eigs_value_new_int` round-trips 9007199254740993 exactly; `eigs_value_as_int` succeeds on an int and on an integral float, and fails with its flag on 1.5; `eigs_value_is_int` tells the kinds apart; `eigs_value_as_num` converts an int (Q3); `EIGS_TYPE_NUM` answers for both + -> every case as stated + tag: n/a (a C test of new embed functions), so it is a gate + +### N8 +- `ctl.N8.refuse-band` (control) a hot function applies `a & b` to float operands 6.0 and 3.0 + -> `2` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f BAND ops: BAND +- `ctl.N8.refuse-bor` (control) a hot function applies `a | b` to float operands 6.0 and 3.0 + -> `7` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f BOR ops: BOR +- `ctl.N8.refuse-bxor` (control) a hot function applies `a ^ b` to float operands 6.0 and 3.0 + -> `5` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f BXOR ops: BXOR +- `ctl.N8.refuse-shl` (control) a hot function applies `a << b` to float operands 6.0 and 3.0 + -> `48` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f SHL ops: SHL +- `ctl.N8.refuse-shr` (control) a hot function applies `a >> b` to float operands 6.0 and 3.0 + -> `0` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f SHR ops: SHR +- `ctl.N8.refuse-bnot` (control) a hot function applies `~a` to float operands 6.0 and 3.0 + -> `-7` on both; the JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f BNOT ops: BNOT +- `ctl.N8.refuse-mod` (control) a hot function applies `a % b` to float operands 6.0 and 3.0; + print `(r == 0)` -> `true` on both. R1.7-D7 (#1758, N6) makes the + observable text-stable while retaining the hot float-MOD witness program. + The JIT scanner refuses this opcode at R1.3. + shape: F witness: fn-stop f MOD ops: MOD +- `N8.bitwise-results-int` [R1.5] `print of [6 & 3, 6 | 3, 6 ^ 3, ~0, num_type of (6 & 3)]` + -> `[2, 7, 5, -1, "int"]` + tag: `num_type` undefined +- `N8.lua-shifts` [R1.5] `print of [1 << 64, 1 << 63, 1 << -1, -1 >> 1, 8 >> -1]` + -> `[0, -9223372036854775808, 0, 9223372036854775807, 16]` + tag: not this (probed main: `1 << 64` is `1`, `1 << 63` a negative double, `-1 >> 1` is `-1`) +- `N8.bit-builtins-int64` [R1.5] `print of [bit_or of [4611686018427387904, 1], bit_shl of [1, 64]]` + -> `[4611686018427387905, 0]` + tag: `4.611686018427388e+18`, ... +- `N8.fn-bitwise` [R1.5] `define b(a, c) as: t is a & c / return t`, called 60 times with `[9007199254740993, 9007199254740993]`; print the last result + -> `9007199254740993` + tag: `9007199254740992` (probed main) + shape: F witness: fn-stop b BAND (the JIT scanner refuses the bitwise ops in round 1) ops: BAND +- `N8.fn-bitwise-all` [R1.5] `define bits(a, c) as: o is a | c / x is a ^ c / l is a << 1 / r is a >> 1 / n is ~a / return [o, x, l, r, n]`, called 60 times with `[9007199254740993, 2]`; print the last result + -> `[9007199254740995, 9007199254740995, 18014398509481986, 4503599627370496, -9007199254740994]` + tag: `[9007199254740994, 9007199254740994, 18014398509481984, 4503599627370496, -9007199254740992]` (probed main) + shape: F witness: fn-stop bits BOR (the scanner refuses the bitwise ops) ops: BOR + +### N9 +Names, operands and operations follow the decided [#1758 comment 6304880619](https://github.com/InauguralSystems/EigenScript/issues/1758#issuecomment-6304880619); Rust std is the oracle. +- `N9.wrapping` [R1.6g] `print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]]` + -> `[-9223372036854775808, 0]` + tag: undefined +- `N9.checked-raises` [R1.6g] `{try print of (u8_checked_add of [200, 100]) catch}` + -> `["value", ...]` + tag: undefined +- `N9.saturating` [R1.6g] `print of [i8_saturating_sub of [-100, 100], u64_saturating_add of [-1, 1]]` + -> `[-128, -1]`. A u64 is carried in an int64 bit pattern, so u64 max is `-1`. + tag: undefined +- `N9.unsigned-ops` [R1.6g] `print of [u64_compare of [-1, 1], u64_to_string of [-1], u64_div of [-1, 2]]` + -> `[1, "18446744073709551615", 9223372036854775807]` + tag: undefined +- `N9.fround` [R1.6g] `print of (fround of 0.1)` + -> `0.10000000149011612` + tag: undefined + +### N10 +- `N10.partial-refused` [R1.6b] `{try print of (num of "12abc") catch}`, printing `e.kind` + -> `value` + tag: `12` (probed main) +- `N10.radix` [R1.6b] `print of [num of "0x1F", num of "0X1f"]`, then catch `num of "0b101"` and `num of "0o17"` separately and print each `e.kind` + -> `[31, 31]`, `value`, `value` (the lexer's own numeric grammar; #1758 comment 6304880619) + tag: `[31, 31]`, `0`, `0`; Lua agrees on hex and rejects both unsupported prefixes. Hex floats are the declared divergence: Lua accepts them, EigenScript's lexer and `num` refuse them. +- `N10.int-or-float` [R1.6b] `print of [num_type of (num of "3"), num_type of (num of "3.0"), num_type of (num of "9223372036854775808"), num of "9007199254740993"]` + -> `["int", "float", "float", 9007199254740993]` + tag: `num_type` undefined + +### N11 +- `N11.sqrt-neg-nan` [R2.2] `x is sqrt of -1 / print of [x == x, num_type of x]` + -> `[false, "float"]` + tag: raises `sqrt: argument out of domain` (probed main) +- `N11.log-zero` [R2.2] `print of (log of 0)` + -> `-inf` + tag: raises `log: argument out of domain` (probed main) +- `N11.no-clamp` [R2.2] `print of [1e308 * 10, 1e308 * 10 == 1e308]` + -> `[inf, false]` + tag: `1e+308` then `true` (probed main for the first) +- `N11.strict-opt-in` [R2.2] (sh row, `EIGS_STRICT=1`) `{try print of (1e308 * 10) catch}` + -> `value`. Default strict off: the same program prints `inf`. + tag: `1e+308` under either setting (read: the clamp, not a raise) +- `N11.constant-1e308-converges` [R2.2] the #1746 row: a binding assigned the literal `1e308` 12 times; `print of (converged of x)` + -> `true` + tag: `false`, because saturation reads as diverging (#861; probed on the #1746 baseline) +- `N11.fn-overflow-inf` [R2.5] `define big(a) as: t is a * 10.0 / return t`, called 60 times with `1e308`; print the last result + -> `inf` (an inf result is not a NaN, so the JIT keeps it native) + tag: `1e+308`, the clamp (probed main) + shape: F witness: fn big ops: MUL CONST +- `N11.min-max-nan` [R2.2] `x is (1e308 * 10) - (1e308 * 10) / a is min of [1, x] / b is min of [x, 1] / c is max of [x, 1] / print of [a == a, b == b, c == c]` + -> `[false, false, false]` (NaN propagates in either argument order: Q8, IEEE 754-2019 `minimum`/`maximum`) + tag: `[true, true, true]`: the clamp turns `x` into 0 (probed main) +- `N11.sort-total-order` [R2.5] `x is (1e308 * 10) - (1e308 * 10) / inf is 1e308 * 10 / print of (sort of [3, x, 1, inf, 2])` + -> `[1, 2, 3, inf, nan]` (Q8: IEEE `totalOrder`, NaN after +inf) + tag: `[0, 1, 2, 3, 1e+308]` (probed main) + +### N12 +- `N12.f64-bits` [R2.4] `print of [f64_bits of 1.0, f64_bits of -0.0, f64_from_bits of 4607182418800017408]` + -> `[4607182418800017408, -9223372036854775808, 1.0]` + tag: undefined +- `N12.nan-canonical-bits` [R2.4] `print of (f64_bits of (0.0 / 0))` + -> `9221120237041090560` (`0x7FF8000000000000`) + tag: raises +- `N12.nan-payload-tags` [R2.1/R2.4] for every tag-spelling payload `p` in `0xFFF8000000000000, 0xFFF9000000000001, 0xFFFA000000000005, 0xFFFB000000001000, 0xFFFF000000000000`, `x is f64_from_bits of p`, stored in a variable, a list, a dict and a buffer; print `[type of x, x == x, f64_bits of x]` for each + -> `["num", false, 9221120237041090560]` five times + tag: undefined. **Plant 2's named check**, run under the plant build with ASan. +- `N12.payload-through-ints` [R2.4] `i is 9218868437227405313 / print of [f64_bits of (f64_from_bits of i), of ( of [buffer of 1, 0, i])]` + -> `[9221120237041090560, 9218868437227405313]`. Ints and buffer bits keep the payload; a float does not. + tag: undefined +- `N12.fn-nan-canonical` [R2.4] `define nz(a) as: t is a - a / return t`, called 60 times with `x is 1e308 * 10`; `print of (f64_bits of (nz of x))` + -> `9221120237041090560` (the JIT bails on the NaN result; the interpreter's single writer canonicalizes) + tag: `f64_bits` undefined + shape: F witness: fn-stop nz SUB ops: SUB +- `N12.leaf-nan-canonical` [R2.4] `define dif(a, b) as: return a - b / x is 1e308 * 10 / print of (f64_bits of (dif of [x, x]))` + -> `9221120237041090560` (computed in the leaf, written through `slot_from_f64`) + tag: `f64_bits` undefined + shape: L witness: leaf dif ops: SUB + +### N13 +- `N13.binary-line-int-errors` [R1.8] warm a two-int addition helper 60 times, then exact int64 overflow and floor-division overflow/zero through written multiline operators; catch the physical operator line and inspect its numeric kind; a negative integer quotient stays exact + -> `["value", true, "int"]` three times, then `[-4, "int"]` + tag: floor-division syntax and num_type are absent; int overflow is a double operation (read). Existing Phase2 contracts plus main #1425. + shape: F witness: fn-stop binary_add ADD ops: BINARY_LINE ADD +- `N13.binary-line-parent-restore` [R1.8, invariant] warm a float sum used as a parent index; a null target raises at the right operand line restored after the successful sum; inspect exact int e.line; the final finite call crosses both markers and returns + -> `["type_mismatch", true, "int"]` then `1.5` + tag: num_type is absent; the v0.46 anchor predates the landed exact-line contract. Bank ed09f908 and merged probes both produce the exact output with fn RET; classify as an invariant under R1.7-D3. + shape: F witness: fn restored_index ops: BINARY_LINE ADD BINARY_LINE_END INDEX_GET +- `gate.N13.audio-buffer-boundary` [R1.6e] `tests/phase2/gfx_audio_boundary.c` through `tools/phase2_readers.sh`: R1.6d-D1 pre-registration for audio_convert_samples (audio_play, audio_play_loop, audio_stream_push). Stored lenient NaN and every infinity raise `value`; strict NaN retains INVALID and the first diagnostic before a later infinity. Continuous finite samples clamp to [-1,1], then round PCM half away from zero. The fixture drives the actual helper with raw payloads because strict scalar constructors refuse first; no SDL dependency is exercised. +- `N13.json-refuses-inf` [R2.3] `x is 1e308 * 10 / {try print of (json_encode of [x]) catch}`, printing `e.kind` + -> `value` + tag: `1e+308`, the clamp (read; a one-element literal list passes its element, #405). **Plant 3's named check.** +- `N13.store-refuses-nan` [R2.3] `x is (1e308 * 10) - (1e308 * 10) / s is store_open of "/db" / {try k is store_put of [s, "c", {"v": x}] catch} / print of (store_count of [s, "c"])` + -> `value` then `0` (refused, and nothing written) + tag: no raise, so the count is `1` (read: `1e308 - 1e308` is `0`). **Plant 3's second named check.** +- `N13.int-conversion-refuses` [R2.3] `x is 1e308 * 10 / {try print of (substr of ["abc", x, 1]) catch}` + -> `value` + tag: no raise (read: `(int)` of a clamped 1e308) +- `N13.pcm16-refuses-nan` [R2.6] `b is buffer of 1 / b[0] is (1e308 * 10) - (1e308 * 10) / {try o is buf_to_pcm16le of [b, 0, 1] catch}`, printing `e.kind` + -> `value` (the quantizer refuses a NaN sample: the #1746 residue) + tag: no raise, bytes `0, 0` (probed main) + +### N14 +- `N14.json-build-int-exact` [R1.3] `json_build` emits an SMI and both int64 endpoints as exact JSON digits + -> `{"small": 3, "big": 9223372036854775807, "min": -9223372036854775808}` + tag: big literals round through double. +- `N14.json-encode-int-exact` [R1.3] `json_encode` walks int list and dict slots, including int64 endpoints + -> `[3,9223372036854775807,{"min":-9223372036854775808}]` + tag: big literals round through double. +- `N14.json-path-int-exact` [R1.6b] `print of (json_path of ["{\"n\":9223372036854775807}", "n"])` + -> `9223372036854775807` + tag: the decoder rounds before the exact text arm (written in R1.2) can read it. +- `N14.encode-kinds` [R1.7] `print of (json_encode of [3, 3.0, 0.1, -0.0])` + -> `[3,3.0,0.1,-0.0]` + tag: `[3,3,0.1,0]` (probed main for the first three) +- `N14.decode-kinds` [R1.6b] `v is json_decode of "[3, 3.0, 9223372036854775807, 18446744073709551615]" / print of [num_type of v[0], num_type of v[1], v[2], num_type of v[3]]` + -> `["int", "float", 9223372036854775807, "float"]` + tag: `num_type` undefined +- `N14.decode-overflow-raises` [R2.5] `{try print of (json_decode of "1e400") catch}` + -> `value` + tag: `1e+308` (read: strtod gives inf, then the clamp) +- `N14.store-exact-digits` [R1.7] `s is store_open of "/db" / k is store_put of [s, "c", {"v": 1234567890123456, "f": 0.1 + 0.2, "t": 9007199254740991}] / r is store_get of [s, "c", 1] / print of [r["v"] == 1234567890123456, r["f"] == 0.1 + 0.2, r["t"] == 9007199254740991]` + -> `[true, true, true]` + tag: `[false, false, false]`: the record encoder's `%.15g` (#1762's reproduction, identical on main and the tag) +- `gate.N14.db-param-exact` [R1.7] `make db-params-test`: `tests/test_db_params.c` drives `db_build_query` without a server; its number rows bind `1234567`, `9007199254740993` and `0.1 + 0.2` + -> each bound as exact text (`1234567`, `9007199254740993`, `0.30000000000000004`) + tag: n/a (a C test, not a program); today's `"%g"` binds `1.23457e+06` (#1762). It needs no PostgreSQL server, so it runs in CI as a gate. + +### T1 +- `T1.float-kind-token` [R1.3] (sh row) record `x is 3.0 / z is -0.0`, assert the v9 assignment tokens mark both as floats, replay and compare stdout + -> `float tokens marked`, `float replay identical` + tag: v6 emits `x=3` and `z=-0`, losing kind. A planted v6-format float writer makes this row fail. +- `T1.v7-header` [R1.3] (sh row) `EIGS_TRACE=t.tape eigenscript p.eigs; head -1 t.tape | cut -d' ' -f1-2` + -> `V 9` (sync #10 combines the phase2 and main v8 schemas; the row also refuses a v8-stamped tape before parsing it) + tag: `V 6` +- `T1.twice-int` [R1.3] (sh row) the Done-when's "record twice": record `define sq(x) as: return x * x / x is 9007199254740993 / y is sq of 94906267 / print of [x, y]` twice (`EIGS_TRACE=t1.tape`, then `t2.tape`); print whether `cmp t1.tape t2.tape` finds them identical; replay `t1.tape` with `EIGS_REPLAY_STRICT=1` and print whether its stdout byte-equals the live run's (the live run computes `sq` in the leaf, the recording on the generic path); print the A records for `x` and `y` + -> `tapes identical`, `replay identical`, `A 0 x=9007199254740993`, `A 0 y=9007199515875289` + tag: `A 0 x=9007199254740992`, `A 0 y=9007199515875288` (the tape machinery is deterministic today: two recordings of a program without randomness differ only in recorded nondeterminism, probed main) +- `T1.twice-float` [R1.7] (sh row) the same procedure for `x is 3.0 / y is 0.1 + 0.2 / z is -0.0 / w is 0.1 / print of [x, y, z, w]` + -> `tapes identical`, `replay identical`, `A 0 x=3.0`, `A 0 y=0.30000000000000004`, `A 0 z=-0.0`, `A 0 w=0.1` (R1.7-D3; the kind on the tape; the exact text is N6's, R1.7) + tag: `A 0 x=3` and `A 0 z=-0` (read: `%.17g`) +- `T1.int-replay-kind` [R1.3] (sh row) record a replayed `random_int` draw selecting an int literal; print its exact value and `num_type`, then replay and compare bytes + -> `int replay identical`, `int assignment exact` (the int is carried by the v9 assignment record; `random_int` retains the float bridge until R1.6) + tag: `num_type` undefined +- `T1.v6-refused` [R1.3] (sh row) `EIGS_REPLAY=tests/phase2/fixtures/v6.tape eigenscript p.eigs`; print the rc and whether stderr names version 6 + -> `rc!=0 names-6` + tag: rc 0 (the fixture is its own version). **Plant 7's named check.** +- `T1.twice-inf` [R2.5] (sh row) the same procedure for `x is 1e308 * 10 / y is -x / print of [x, y]` + -> `tapes identical`, `replay identical`, `A 0 x=inf`, `A 0 y=-inf` + tag: `A 0 x=1e+308` (the clamp) +- `T1.twice-nan` [R2.5] (sh row) the same procedure for `x is (1e308 * 10) - (1e308 * 10) / print of (x == x)` + -> `tapes identical`, `replay identical`, `A 0 x=nan` + tag: `A 0 x=0` (the clamp, then `inf - inf` collapses to 0) + +The `O cfg` record's v9 shape is pinned by `O3.ocfg-no-hlow` (its file lists +`# ITEMS: O3 T1`). + +### O1 +- `gate.O1.oracle-floor` [R3] for each oracle int row (a counter, doubling to 2^62, an INT64_MIN to INT64_MAX swing), in all three tiers: `make observer-oracle` with `--floor floor.json` exits 0 + -> exit 0 + tag: n/a. This is the oracle gate of #1758 Done-when "Observer", so it is a gate, not a row. +- `O1.where-is-value` [R3] `x is 2.5 / print of (where is x)` + -> `2.5` + tag: `0.7219...`, an entropy (probed main: `where is x` after 1 and 4 gives `0.7219280948873623`) +- `O1.int-observed-numeric` [R1.3] `a is 3 / b is 140737488355328 / af is 3.0 / bf is 140737488355328.0`, then 12 times `a is a * 2 / b is b * 2 / af is af * 2.0 / bf is bf * 2.0`; `print of [report of a, report of af, report of b, report of bf, num_type of b]` + -> `["diverging", "diverging", "diverging", "diverging", "int"]`: an SMI (`a`) and a boxed int (`b`, 2^47 and up) are observed exactly as their float twins (the numeric channel, as a double until round 3) + tag: `num_type` undefined (the reports alone are all `"diverging"` on main, where every number is a double) + +### O2 +- `gate.O2.predicate-floors` [R3] the post-#1754 corpus with per-predicate TP/TN floors (`floor.json`, the corpus sha256 recorded) + -> at or above every floor + tag: n/a (the floors are measured on the tag), so it is a gate +- `O2.noise-not-oscillating` [R3] a seeded iid noise sequence (±1 around 100, 60 steps, an LCG in the program); `print of (oscillating of x)` + -> `false`, per #1754's definition (Hardy §62; iid noise is the in-control baseline, not an oscillation) + tag: `true` (#1746 prior-art report, probe P3: 99.3% of steps read `oscillating`) + +### O3 +- `O3.ocfg-no-hlow` [R3] (sh row) record any observed program; print the field count of the `O cfg` record + -> one fewer field than v6: no `h_low` + tag: has `h_low` (trace.c:1580-1584 writes it) + +### O4 +- `O4.why-is-displacement` [R3] `x is 1 / x is 4 / print of (why is x)` + -> `3`: an int, because Δv fits int64 (Q5, decided) + tag: `-0.2780719051126377`, a dH (probed main) +- `O4.how-is-settledness` [R3] a binding held constant for one window; `print of (how is x)` + -> the R3 settledness value at full rest (to be fixed by the R3 design) + tag: an entropy-derived value + +### N11 +- `N11.buffer-equal-ieee` [R1.6z] SGD stores -inf, then self-SGD stores NaN; IEEE element comparison: distinct/same NaN false, same infinity true, infinity versus finite false, signed zeros equal; strict modes 0/1 never raise or change math flags. Also exercises list_contains and list_index_of. R1.6z-D1. +- `N11.container-self-equal-ieee` [R1.6z] Raw SGD stores -inf, then self-SGD stores NaN (`fixtures/r16z-container-self-equal.eigs`). Self-comparison of a list, dict, nested list and cyclic list reaching that buffer is false; a finite-buffer list and finite cycle are true. Membership/index search for the NaN-buffer list fail. Strict modes 0/1 never raise or change math flags. A finite shared DAG built as `[x, x]` 40 times compares true within the 30-second row timeout. R1.6z-D2; pre-registered red on d8a9d2c2 before the fix. +- `gate.N11.embed-buffer-get-raw` [R1.6z] `tests/phase2/embed_buffer_get_raw.c` through `tools/phase2_readers.sh` (`release embed_buffer_get_raw`): set/get canonical NaN, payload NaN, both infinities and negative zero by memcmp; reads SGD-program-stored NaN, with no error or flag change in strict modes 0/1. Mechanical gate, compiled outside timed rows. + +### N13 +- `N13.vm-buffer-index-data` [R1.6z] SGD-stored NaN and both infinities through index; get_at oracle R1.6d-D1: value error, finite-number message (strict NaN first diagnostic/INVALID), line 2 in every execution tier. Sixty finite calls warm the function. +- `N13.vm-buffer-local-index-data` [R1.6z] SGD-stored NaN and both infinities through local-index; get_at oracle R1.6d-D1: value error, finite-number message (strict NaN first diagnostic/INVALID), line 2 in every execution tier. Sixty finite calls warm the function. +- `N13.vm-buffer-leaf-index-data` [R1.6z] SGD-stored NaN and both infinities through leaf-index; get_at oracle R1.6d-D1: value error, finite-number message (strict NaN first diagnostic/INVALID), line 2 in every execution tier. Sixty finite calls warm the function. +- `N13.vm-buffer-iter-data` [R1.6z] SGD-stored NaN and both infinities through iter (128 cells with a backedge; nonfinite copied to final cell); get_at oracle R1.6d-D1: value error, finite-number message (strict NaN first diagnostic/INVALID), line 3 in every execution tier. Sixty finite calls warm the function. +- `N13.vm-buffer-int-index-data` [R1.6z] Dynamic integer index exercises the boxed slow INDEX_GET arm on SGD-stored NaN and both infinities; same get_at error contract at line 2, strict modes 0/1 and all execution tiers. diff --git a/tests/phase2/builtins/BAR.md b/tests/phase2/builtins/BAR.md new file mode 100644 index 000000000..d1f40e7db --- /dev/null +++ b/tests/phase2/builtins/BAR.md @@ -0,0 +1,1092 @@ +# R1.6 per-builtin behaviour table: the pre-registered bar (#1758, design §9.4) + +Written before any R1.6 migration. Every `.branch` is derived from the design +(`docs/design/phase2-numbers.md` §6, §2.6, §9.4, §11 at `4ac31e67`; for +`read_text`, §6 and the code at `1fd4422c`, ruling R1.6a-D1), from the +#1758 decisions (comment 6304880619 for N10), from the orchestrator's rulings +(`RULINGS.md`) and from Lua 5.4.9. No `.branch` was copied from a binary. The +binaries only produced `.pre` (R1.5 tip `4ac31e67`, +`/home/jon/src/wt/r15-critic/build/release/eigenscript`) and `.tag` (v0.46.0 +release binary). Where a row should not change, its `.branch` equals `.pre`. +That agreement checks the derivation; it is not where the expectation came +from. Every `.branch` that changed after its first observation is listed under +"Corrections log", with its reason. + +**Fix round, 2026-10-20.** The blind critic found the first version DEFECTIVE +(witness defects F1-F8, and no wrong scalar expectation). This version applies +the orchestrator's FINAL rulings (`RULINGS.md`): A1-A5 (table structure), +B1-B6 (witnesses), and C1-C10 plus the agreed answers (semantics). Each changed +`.branch` names its ruling in the Corrections log. The hook rows' `.pre` and +`.tag` were recorded with the test shim preloaded, on the same two binaries. +The 25 non-release rows' `.pre` and `.tag` read `PENDING-PROFILE `; +R1.6a records them on matched builds. + +**R1.6a rulings, 2026-10-21 (historical, before R1.6-S2).** R1.6a-D1: +the population was 232 rows, taken at +`1fd4422c` (the R1.6a start, sync #4, which carries main's #1841 and adds +`read_text`). §6 and the inventory at `1fd4422c` differ from `4ac31e67` only by +that row (field by field; the inventory's other changes are line moves and one +accessor list). R1.6a-D2: no probe prints a value the OS or the environment +chooses; `net_port`'s port line and its siblings print a predicate instead. + +**Sync #5, R1.6-S1/S2.** Main's `rm` deletion removes no table row: the +number-reading bar never included it. R1.6-S2 adds main's `set_exit_code` +as an int-requiring release-profile row in round c, closed by main #1909. +After S10-D2, the current population is 309 rows and 310 cases, with round counts b 46, +c 76, d 53, e 38, f 19 and g 77 (b/f include R1.6b-D3's `http_post` correction). +The new `.branch` is an authorized addition recorded by SHA-256 under +R1.6-S2 in `../builtins_changes.md`; existing frozen expectations stay fixed. + +## Files +- `builtins.tsv`, 309 rows: `builtin file class loss q7 round closed_in + profile hook stdin changes_vs_pre changes_vs_tag`. `make_table.py` writes + it. Rows come from the §6 table, checked both ways against the inventory's + `## Builtins` list, field by field. Both files and `src/` are extracted with + `git archive` from `EIGS_REPO` at `EIGS_REV` (default + `/home/jon/src/InauguralSystems/EigenScriptEcosystem/EigenScript` at + `1fd4422c`), so the population is pinned to the ruled commit, not to a + checkout's state; `EIGS_TREE` overrides it for the plants. `file` is the + `src/*.c` file that defines the inventory's C function. `round` follows the + plan's rule, and the counts are checked against b 46, c 76, d 53, e 38, + f 19 and g 77 (R1.6b-D3, R1.6-S2, R1.6g-D1 and S10-D2). The other columns: + - `closed_in` (ruling A2) and `profile` (A3) come from `rowmeta.tsv`; + - `hook` (A4) and `stdin` (A5) list the hooks and the stdin fixtures of the + row's cases, in case order. `-` means none, and stdin `-` is `/dev/null`; + - `changes_vs_pre` (A1) is `yes` when `.branch` differs from `.pre` on any + of the row's cases. It is the R1.6 migration delta, and the vacuity guard + uses it. It reads `pending-profile` while a non-release row waits for its + matched build; + - `changes_vs_tag` (A1) is the same comparison against `.tag`, §9.4's + original one. It is recorded as an observation and never checked, because + the tag has no `num_type`, and `set_exit_code` also postdates the tag. +- `rowmeta.tsv`: `builtin closed_in profile`, one line per table row. +- `cases.tsv`: `case builtin hook stdin`, 310 cases. A row's primary case is + `builtins/`. The surviving second case is `gfx_poll.empty` (B3). + S10-D2 explicitly retires both `proc_read_buf` cases (A5/C10). +- `builtins/.eigs` is the probe. `.branch` is the pre-registered + stdout after R1.6, then `rc=`. `.pre` and `.tag` are the + observed stdout plus `rc=`, or `PENDING-PROFILE `. +- `builtins/_hooks/observation_hooks.c`: the LD_PRELOAD shim (A4; its repo + target is `tests/phase2/observation_hooks.c`). `run_probes.sh` compiles it + with `gcc -shared -fPIC` into its scratch directory on every run. +- `builtins/_stdin/hi` and its only users were retired by S10-D2. No live case + declares a stdin fixture; the checker still compares fixtures and declarations both ways. +- `builtins/_profiles/.eigs`: the profile witnesses (A3). +- `run_probes.sh [case...]` records `.pre` and `.tag`, never `.branch`. +- `make_table.py [--final]` writes `builtins.tsv` and runs every table check. +- `determinism.sh [case...]` records the cases three times (twice normally, + then pinned to core 0 at nice 19) and fails on any `.pre` or `.tag` that + differs between the runs (R1.6a-D2's class). +- `plants.sh` plants one fault per check in a scratch copy and requires the + check's named FAIL line. +- `oracle/.lua` and `oracle/.lua.out`: the Lua 5.4.9 expressions and + their verbatim output. + +Re-run: `./run_probes.sh` (all cases) or `./run_probes.sh abs gfx_poll.empty`, +then `python3 make_table.py`. R1.6a adds `--final` once the matched builds are +recorded. Both exit non-zero on an instrument failure. Run them on Xvfb +(`DISPLAY=:99`). R1.6a's gate runs each case the same way (environment, +stdin fixture, shim) on the branch build and compares stdout plus `rc=` with +`.branch`; this bar ships no branch-mode runner. + +## Probe conventions +- Each probe starts with the helper `nt`: `num_type of x`, catching the error. + `num_type` does not exist on the tag, so there it answers `"undefined_name"` + and the tag line still shows the value. On R1.5 and after, `nt` equals + `num_type`. +- Results print as `[value, num_type]`, following §9.4's `len of [1, 2]` -> + `[2, "int"]`. An expected raise prints `[

` and `TAG_BIN_

` (`P` is +`SERVER`, `DB` or `ZLIB`). It then checks each `.branch` on the branch's build +of that profile. Three guards keep a capability transcript from counting as a +change: +- Before recording, `run_probes.sh` runs `builtins/_profiles/.eigs` + on each given binary, and stops with exit 2 unless it prints `profile-ok`. + For server, none of `shared_size`, `net_listen` and `eigen_model_load` may + answer "capability unavailable". For db, `db_connect of null` and then + `SELECT 1` must answer 1. For zlib, `inflate of [203, 200, 4, 0]` must + decode to `hi`. On the release binary the three witnesses print + `["capabilities-unavailable", 3]`, the db error and the zlib error. +- `make_table.py` FAILs a non-release record that contains `capability + unavailable` or `compiled without`. (`http_serve` and `http_early_bind` + print a capability flag, not the message, so for those two rows the profile + witness is the guard.) +- `make_table.py --final` FAILs while any record is still pending. + +The db mechanism: this box has no PostgreSQL (no `psql`, `postgres`, +`/usr/lib/postgresql` or docker). CI's `database-worker` job +(`.github/workflows/ci.yml`) runs `make server-db` in the dev image, with a +`postgres:16` service and +`DATABASE_URL=postgres://eigs:eigs_test@db:5432/eigs_test`. It runs on every +event except pull requests. Every db probe connects with `db_connect of null`, +which reads `DATABASE_URL`, and the runner passes it through. So R1.6a records +the three db rows in that job's environment, or on any host with a +PostgreSQL, using `server-db` builds of `4ac31e67` and v0.46.0. + +## Hooks (ruling A4) +`builtins/_hooks/observation_hooks.c` starts from the critic's +`observation_hooks.c`. The runtime `dlopen()`s SDL and SDL_mixer and +`dlsym()`s each entry point, so interposing `dlsym` reaches them, and +`clock_gettime` is interposed directly. EigenScript's source and binary do not +change. `R16_HOOK` picks one hook per process: + +| hook | interposes | effect | case | +|---|---|---|---| +| `pcm` | `SDL_QueueAudio` | logs the int16 samples delivered (up to 8) | `audio_stream_push` | +| `mix` | `SDL_OpenAudioDevice` | wraps the playback callback (EigenScript's own channel mixer, `audio_mix_callback`) and logs the first non-silent buffer it delivers (up to 8 samples) | `audio_play`, `audio_play_loop`, `audio_volume` | +| `mixvol` | `Mix_VolumeMusic` | logs the volume handed to SDL_mixer | `audio_music_volume` | +| `clock` | `clock_gettime(CLOCK_MONOTONIC)` | answers 9007199 s 254740993 ns, that is 2^53 + 1 ns | `monotonic_ns` | +| `events` | `SDL_PollEvent` | delivers mouse motion (17, -9), key scancode 4, then button 1 at (23, 42); after that the queue is empty | `gfx_poll` | + +- The channel gain that `audio_volume` sets has no SDL setter: EigenScript + applies it inside its own callback. So its SDL boundary is the callback's + output buffer, which `mix` logs. +- A hooked process first writes `hook-loaded ` to `R16_HOOK_LOG`, a + file in the probe's scratch directory, and capture hooks append their + payload lines there. Every hook probe prints that log as its last output, so + the boundary payload is part of `.pre`, `.tag` and `.branch`. +- Two checks catch a missing shim. `run_probes.sh` fails (exit 2) when the + log's first line is not `hook-loaded `. `make_table.py` fails when a + hook case's `.branch`, `.pre` or `.tag` does not hold exactly one such line, + and when a case without a hook holds one. +- The mixer probes pause the device (`audio_pause of 1`) before installing a + channel and unpause it afterwards, so the first non-silent buffer starts at + the channel's first sample. They wait up to 4 s for that buffer + (`wait_for`). +- `R16_PLANT=silence` is a test mutation that only `plants.sh` uses: the + delivered PCM becomes zeros. + +## Derivation per class +**int-preserving** (§6: "returns an exact int"). With int inputs, the result's +`num_type` is `"int"`: +- arithmetic and min/max/rounding: Lua §6.7's "integer results for integer + arguments"; `math.min`/`max` return the winning argument with its subtype; +- counts, codes, indices and handles: Lua `#` and `string.byte`; +- `floor`/`ceil`: an int when the result fits int64, else a float, as in Lua + §6.7. `round` follows them (Q4: half away from zero). + +The scalar arms of `add`, `subtract`, `multiply`, `negative` and `sum` mirror +the VM operators (N3), so int64 overflow raises `value` ((A)), and negating +INT64_MIN raises (N4b). Buffer arms stay float. A mixed int/float input gives +a float, as Lua's arithmetic does. + +**int-requiring** (§6 and Q1: the parameter's meaning decides, not the C type). +An integer-valued argument converts by N5's exact rule (`num_to_i64_exact`: +an integral double in range converts, anything else has no integer +representation), then the C range applies. So `1.5` raises `value`, `2.0` +converts, and a byte outside 0..255 raises. A result that is a count, a bit +pattern or an id is an int. + +**float-producing** (§6: ints convert to float, the result is a float, as Lua's +`/`, `^` and `math.sin` do). With int inputs, `num_type` is `"float"`. Buffer +storage is float (Q2). + +**other** (§6): each row follows its note. Codecs and parsers (N10, N13, N14): +integer text decodes to an int when it fits int64, and text with `.` or an +exponent decodes to a float (MAP `N14.decode-kinds`, `N10.int-or-float`). +Writers keep today's float text until R1.7. Continuous parameters follow Q7. + +**Q7** (§2.6, §11, decided in comment 6174302316): geometry clips the WHOLE +shape in float space against the drawable area, then rounds half away from +zero, and an out-of-range shape draws nothing outside the area; bounded values +clamp to the domain, then round; magnitudes round and raise `value` when the +C call cannot hold them. A Q7 probe passes one out-of-range value of each class +in its `q7` column. + +**LOSS** (§6): the probe must show the bridge's loss, usually with an int beyond +2^53. A row whose loss was closed before R1.6 carries `closed_in` (A2). A row +whose writer loss is R1.7's changes at R1.6 through its decoder (Q-R17, +agreed). + +## Per-row derivation, round b (45 rows; R1.6b) +- `abs`: Lua `math.abs`; `-(2^53+1)` gives `2^53+1` as an int; a float stays a + float; INT64_MIN raises (N4b; Lua wraps, a declared divergence, §10). + `closed_in` R1.2/R1.3 (A2): the int arm landed in R1.3, so `.branch` equals + `.pre`. +- `add`, `subtract`, `multiply`: scalar arms mirror `+ - *` (N3/(A)). Exact sums + above 2^53; overflow raises `value`. The `add` buffer arm stays float. The + LIST arms go elementwise through the scalar arm (C4; + `tensor_elementwise_slots`), so ints stay ints: `[[1, 2^53+1], [2, 1]]` + gives `[3, 2^53+2]`, `[[5, 2^53+1], [2, 1]]` gives `[3, 2^53]`, and + `[[3, 3037000499], [2, 3037000499]]` gives `[6, 9223372030926249001]`. +- `negative`: mirrors unary `-`. `-(2^53+1)` is exact; `-INT64_MIN` raises (N4b). +- `sum`: an all-int input sums exactly, `[INT64_MAX, 1]` raises `value` with a + message naming `sum` (MAP `A.sum-overflow-raises`), and a mixed list gives a + float. `sum of []` is the int 0, in both call forms (C2). +- `floor`, `ceil`, `round`: Lua `math.floor`/`ceil`; Q4 for `round`. An int input + returns itself; `-2.5` gives `-3`/`-2`/`-3` as ints (`round of 2.5` = `3`); + `1e300` stays a float. +- `min`, `max`: Lua returns the winning argument with its own subtype. + `[2^53+1, 2^53+2]` picks exactly; `[3, 3.5]` gives the int 3; `[4, 3.5]` gives + the float 3.5; a bare `7` returns itself (the code's documented bare-number + case). In an int/float tie the FIRST argument wins, kind included (agreed; + Lua): `[3, 3.0]` gives the int 3 and `[3.0, 3]` the float 3, printed `3`. + An empty call raises `value`, in both call forms, `min of []` and + `min of ([])`, and likewise for `max` (R1.6b-D2, #1895): there is no + winning argument, and Lua's `pcall(math.min)` also refuses (`bad argument + #1 to 'math.min' (value expected)`). `pre` and the tag answer `0`. +- `num_copy`: keeps its subtype (§6 note). +- `hex`: the 2^53 refusal is a double compare (§6), and the C formatting type is + `unsigned long long`. N5 then the C range accepts every non-negative int64, + so `2^53+1` prints `20000000000001` (Q-HEX, agreed). `nibbles` is a count, + so `4.5` raises and `4.0` converts. A fraction already raised and still + does. A negative raises `value` (Q-HEX); it already does on `pre`. +- `sign_extend`: both arguments are integer-valued (a bit pattern and a width). + The algorithm is unchanged and reads exactly: `[2^53+129, 8]`, low byte 0x81, + gives `2^53+129-256` = `9007199254740865`. Results are ints (Q-SIGNEXT). + The subtraction is checked int64 arithmetic under rule (A), "overflow + raises, no wraparound" (R1.6b-D1, #1894): `[-9223372036854775553, 8]` has + low byte 0xFF, and subtracting 256 would give INT64_MIN - 1, so it raises + `value`, naming `sign_extend`. Lua's integer subtraction wraps the same + operation to 9223372036854775807; `pre` and the tag answer + `-9.223372036854776e+18`. +- `random_int`: Lua `math.random(m, n)` is an integer. The bounds follow N5. + `[2^53+1, 2^53+1]` has a span of 1, so the result is deterministic and exact. +- `seed_random`: a seed's range is all of int64 (§6, design round 2). So seeds + 2^53 and 2^53+1 must give different streams (they give the same stream on + `pre`). `1.5` raises; `42.0` seeds like `42`. `srand48` keeps only the low 32 + bits; I computed from POSIX's drand48 that low words 0 and 1 give first draws + 850048 and 400395, so they differ. The probe asserts nothing beyond the two + seeds differing (see OUT OF SCOPE, srand48). +- `task_sched_seed`: `(int64_t)` of a double today. The splitmix64 state + (task.c:245) for seeds 2^53 and 2^53+1 gives first picks over 3 ready tasks of + 1 and 2 (computed), so the receive orders differ. (Over 4 tasks both pick 3, + so 4 workers would not show the difference.) `1.5` raises; `5.0` equals `5`. +- `len`, `ord`, `index_of`, `list_index_of`, `range`: counts and indices are + ints (Lua `#`, `string.byte`). `list_index_of` compares by N2 (exact above + 2^53; 2 equals 2.0). `range`'s bounds follow N5: `2.5` raises (MAP + `N5.range-fraction-raises`), `[1.0, 3.0]` converts, and the elements are ints. +- `channel`, `spawn`, `task_spawn`, `task_self`: handle dicts with int id/gen + fields, and int task ids (main is 0). Only the kinds print; the ids are + allocation order. A spawn handle's `done` is numeric (`make_num(0)`, + builtins.c:4546), so it is an int (C1). +- `monotonic_ns` (B2): the `clock` hook makes CLOCK_MONOTONIC read 2^53+1 ns, + so the result is the exact int 9007199254740993 and equals that literal. + `pre` answers 9007199254740992, a float, unequal: a double cannot hold + 2^53+1. +- `scan_ints`, `scan_int_tokens`: integers accumulate exactly. The + line/col/offset/is_int/value fields are ints. Token rows were derived by + hand from the C scanner. Integer text beyond int64 raises `value`, and the + message names the token (C3): `9223372036854775808`. +- `num` (N10, comment 6304880619): accepts exactly the lexer's numeric grammar. + The lexer (lexer.c:721-775) takes a strtod decimal span starting at a digit or + `.digit`, so `.5` and `1.` are floats. Digits-only text is an int when it fits + int64; `0x`/`0X` hex wraps modulo 2^64. A sign and surrounding whitespace are + allowed. Everything else raises `value`. The oracle is Lua's `tonumber`, apart + from the declared hex-float divergence (`0x1p4`). `"-9223372036854775808"` + decodes to the int INT64_MIN (agreed; Lua's `tonumber` agrees). +- `json_decode`: MAP `N14.decode-kinds`, plus `2^53+1` exact, `-7` an int, and + `1e2` a float (exponent text). +- `json_path`: returns text. Decoder ints arrive at R1.6, so `2^53+1` and + INT64_MAX come back as exact digits (MAP `N14.json-path-int-exact`). A nested + list re-encodes exactly. +- `json_encode`, `json_build`, `sort`, `sort_by`: the int arms are already exact + (R1.2/R1.3), and float text stays until R1.7. The `.branch` therefore equals + today's output: `closed_in` R1.2/R1.3 (A2). +- `store_get`, `store_delete`, `store_update`: keys follow N5 (MAP + `N5.store-key-fraction-raises`). `1.9` raises `value` where today it finds or + deletes record "1"; `1.0` converts. Key 4294967297 finds the record whose + `_id` is "4294967297" (§6: "R1.6's `eigs_int_arg` for the key closes" + #1760; `pre` still misses it). Records decode integer text as ints (N14, R1.6). + Big keys (B4): `store_update` and `store_delete` act on the record whose + `_id` is `"9007199254740993"` through the numeric key 2^53+1, exact under + N5, giving `["update-big-key", true, 2]` and `["delete-big-key", true, 0]`. + `store_get` finds that record by the same numeric key. `pre` rounds the key + to 2^53 and misses it. +- `store_put`, `store_query`: the record decoder gives ints for integer text + (R1.6). Ints are written as exact digits already (R1.2a). The `%.15g` float + writer is R1.7's (Q-R17). +- `shared_incr` (server profile): the counter adds exactly, and overflow raises + ((A), §7 lists `shared_incr`). `shared_get` decodes the stored text as an int. +- `db_execute`, `db_query_value`, `db_query_json` (server-db plus PostgreSQL): + ints bind as exact digits (R1.2a), and int columns decode as ints (R1.6). + See Q-DB253. + +## Per-row derivation, round c (77 rows; R1.6c; R1.6a-D1 and R1.6-S2 additions) +- Float-producing (`acos asin atan atan2 cos sin tan pi random clock_unix + monotonic_ms get_observer_thresholds dot stream_write`): ints convert and the + result is a float (Lua `math.*`). Clocks print only their kind and an + ordering; `random` is seeded and prints its kind and its [0, 1) range. + Threshold values are the `OBSERVER_*_DEFAULT` constants (eigenscript.h:659-661). + `stream_write` takes an int (Q2) and reads back through `tensor_load` as a + float. +- `nearest_in_range`, `nearest_in_range_all`: geometry is float and the + `index` field is an int (§6 note). +- Formatting, equality and message rows (`append assert join str print write + text_builder_append text_builder_append_line throw type num_type + list_contains observe`): R1.6 changes nothing in them. N6 (float text) is + R1.7's, and ints have printed exact digits since R1.2a. Each `.branch` pins + today's text, so `3.0` still prints `3`, and an int above 2^53 is exact. + `throw` binds the thrown value itself. +- `record_history`: a truthiness flag in (`0.5` enables), the previous flag + out. The previous flag is an int (C1), and the probe prints its kind. +- `task_sleep`: virtual ticks stay a float (§6), so 2 wakes before 2.5. If 2.5 + were truncated, the two tasks would tie and FIFO would deliver "slept-2.5" + first. +- Bitwise (`bit_and bit_or bit_xor bit_not bit_shl bit_shr`): Lua §3.4.2 and + the N8 shift rules, exact since R1.5. A fraction raises `value` and an + integral float converts. `closed_in` R1.5 (A2). +- Strings and lists (`char_at substr list_insert_at list_remove_at list_slice + list_truncate fill get_at set_at copy_into`): positions, lengths, counts and + offsets follow N5, so a fraction raises `value` where today it truncates, + and `1.0` converts. Elements are returned unchanged. A buffer store converts + to float (Q2). +- `chr`, `dispatch`: "already exactness-checked" (§6 notes), so no change: + `closed_in` main (A2). +- Handles (`channel_closed close_channel send recv try_recv recv_timeout + thread_join task_alive task_join task_kill task_detach task_send`): a handle's + id and gen are integer-valued. A forged handle whose id is `id + 0.5` raises + `value`; today the `(int)` cast resolves it to the real channel or thread, + and a fractional task id reads as "unknown" (`false`/`null`/`0`). `id + 0.0` + converts and reaches the real object. `task_detach`'s 1/0 result is an int + (C1). +- `recv_timeout`, `exec_capture`, `usleep` (Q7 magnitude): `1e300` cannot be + held by the C type, so it raises `value`. Today `recv_timeout` caps it at one + year, `exec_capture` casts it to `time_t` (undefined; the deadline passed at + once), and `usleep`'s `(int)` cast gives INT_MIN (no sleep). The + `recv_timeout` probe queues values first, so no binary can block, and its + `.branch` keeps the values queued (a refusal precedes the effect). + `exec_capture` exit codes are ints (§6). +- `recv_timeout` (C5): the one-year cap is removed. The milliseconds reach + `long whole_ms = (long)ms` (builtins.c:4867), and `whole_ms / 1000` (at most + about 9.2e15 s) is added to the 64-bit `tv_sec` of `EIGS_CHANNEL_CLOCK` + (CLOCK_MONOTONIC or CLOCK_REALTIME, builtins.c:4713-4715), which cannot + overflow. So the call can honour any timeout up to LONG_MAX ms. The first + value past it is 2^63 ms (the literal `9223372036854775808`, which lexes as + a float), and it raises `value`. The probe queues three values: on `pre` + and the tag, each of the three raise cases consumes one, and none blocks. +- `exit`: N5, then the C int range. `2.5` and `4294967296` raise `value`, and + `3.0` exits with rc 3 (today `2.5` exits with rc 2). +- `set_exit_code` (R1.6-S2): N5, then 0..255, already checked by main #1909. + `1.5`, `256` and `-1` raise `value`; `3.0` succeeds, execution continues, + and graceful completion uses rc 3. Its `.pre` equals `.branch`, with + `closed_in` R1.6-S2; the tag has no such builtin. This row stays in round c + with `exit` until their bridge migration. +- Host (`random_hex read_bytes_buf proc_read_buf build_corpus token_name + state_at sandbox_run vm_run_bytecode write_bytes stream_open`): counts, caps, + fds, ids, line numbers, limits, ABI numbers and opcode bytes follow N5. Byte + values follow Q1: N5, then 0..255, so `write_bytes` 300 raises (today it + wraps to 44) and 65.5 raises (today 65). `vm_run_bytecode` code byte 256 + raises (today it wraps to 0, `OP_CONST`), and ABI `1.5` raises (today + `(int)1.5` == 1 passes). Counts returned (`write_bytes`, `build_corpus`) and + `sandbox_run`'s error `line` are ints (§6 notes). `read_bytes` returns byte + values as ints (int-preserving). +- `proc_read_buf` (A5, C10): a second case, `proc_read_buf.hi`, reads stdin + `Hi`. §6's row ("bytes stored as floats") and Q2 (buffer storage is f64) + agree on the element kind, so the case expects `[2, 72, 105, "float", + "float"]`, and then EOF answers null. The primary case keeps stdin + `/dev/null` and its EOF path. +- `stream_open`: the fraction raises `value` by §9.4's class check; today it + raises `limit`. An integral count outside 1..10,000,000 keeps `limit` + (Q-STREAMOPEN, agreed): the count 0. +- `read_text` (R1.6a-D1; round c, `builtins_host.c`, release profile, no hook, + no `closed_in`): §6 at `1fd4422c` says "string result; numeric over-cap size + is internal tape metadata (#1668), with no numeric argument or successful + numeric result", so R1.6 changes nothing and the `.branch` is the + `1fd4422c` code's answer. A missing file raises `io` (`file_io_error`; the + message names the path, so only the kind prints). A file of 10,485,761 + bytes, written under `EIGS_PROBE_TMP` with `write_text`, raises `io` with + `read_text: '' is %ld bytes, over the %d-byte cap`, a format the code + fixes, so the probe pins the digits after the path: `" is 10485761 bytes, + over the 10485760-byte cap"`, and checks the prefix with `starts_with`. A + 2-byte file reads back as the string `"hi"`. Over-cap REPLAY is not + exercised: it is the open bug #1890, and a probe must not pin a known + defect. + - `.pre` and `.tag` are observations on binaries that predate #1841: there + `read_text` answers `""` for the missing file and for the over-cap file + (`["missing-answer", ""]`, `["over-cap-answer", 0]`), so this row's + `changes_vs_pre` reads `yes` today. Class `other` is not guarded. R1.6a + re-records `.pre` on `1fd4422c` as this row's in-scope baseline (#1668 + changed it upstream), and then `changes_vs_pre` must read `no`. +- `exec_capture`'s `1e300` line prints only `type of` the answer on `pre` and + the tag (R1.6a-D2's class, found by the determinism re-run). There + `(time_t)1e300` is undefined, and the deadline arithmetic + (`builtin_exec_capture`, builtins_host.c) overflows. While the clock stays + in the second the deadline was set in, the remaining time reads as huge; + once a second boundary passes before the read loop sees EOF, it reads as + already past, and the child is killed (`[-2, "hi\n"]` instead of + `[0, "hi\n"]`). The pinned nice-19 recording showed `-2` once, and 172 + further runs of the old line did not. The branch raises `value` before the call, so + the `.branch` is unchanged. +- Structural facts R1.6 does not touch (dict key order such as `{"ok": true, + "result": 7}`, handle field names, error kinds of unrelated failures) were + read from the source or from exploratory runs on `pre`. No numeric + expectation came from a binary. + +## Per-row derivation, round d (53 rows; R1.6d) +- Float tensor math (`divide pow sqrt exp log mean norm relu leaky_relu softmax + log_softmax matmul matmul_at matmul_bt zeros_like numerical_grad sgd_update + tensor_save tensor_load buf_from_list`): ints convert and results are floats. + `/` and `^` are always float (Lua §3.4.1). Buffer and tensor-file storage is + float, so an int above 2^53 converts by IEEE round-to-nearest (Q2): + `buf_from_list`, `buf_set` and `tensor_save` of 2^53+1 hold + 9007199254740992. +- Buffer windows (`buf_copy buf_fill buf_mix buf_scale_range buf_peak buf_dot + buf_deinterleave buf_resample_linear buf_to_pcm16le buf_from_pcm16le`): + offsets, counts, channel indices and `dst_len` are indices and counts, so + they follow N5 by Q1 (`buf_fill`'s note says so explicitly; Q1 is general, + see Q-WINDOWS). A fraction raises `value` where today `(long long)` + truncates. +- `buffer`, `zeros`, `reshape`, `random_normal`: sizes, dims, rows and cols + follow N5. Elements are float 0.0 (§6). +- `buf_get`, `buf_set`: the index follows N5. The element read is a float, and + the stored value converts (Q2). +- `buf_len`, `shape`: counts and dimensions are ints (int-preserving). +- Byte readers (`str_from_bytes f64_from_bytes buf_from_pcm16le inflate`): Q1's + byte reader, so a fraction or a byte outside 0..255 raises `value`. Today + `finite_num_to_byte` wraps modulo 256 (328 is 'H', 319 is 63), or the byte is + read as a double (pcm16: MAP `N5.pcm16-byte-raises`, whose tag values I + recomputed from the C arithmetic). `f64_to_bytes` returns "8 byte ints" + (§6), so its kind changes from float to int. An int argument converts to + the nearest double first (C9, Q2): `2^53+1` gives the bits of + 9007199254740992, `[67, 64, 0, 0, 0, 0, 0, 0]`. `inflate`'s output bytes + are ints (C8, Q1): `[[104, 105], "int"]`, asserted on the zlib profile. +- `buf_to_pcm16le` (Q7 bounded): out-of-range samples clamp to [-1, 1], then + round half away from zero. That is "today's behaviour" per §6, so the clamp + does not change. Hand-derived bytes for `[0.5, 2.5, -3]`: + `[0, 64, 255, 127, 1, 128]`. Its `changes_vs_pre: yes` comes from the N5 window + (Q-PCM16OUT). +- `gather`: each index follows N5, from a buffer (MAP + `N5.gather-index-fraction-raises`) or from a list. "Returns elements + unchanged", so the list path keeps an int element an int (today + `make_num(cell)` makes it a float). The buffer path returns a float buffer. +- `scatter_add`: indices go through the N5 index reader, so `value` replaces + today's `index_range` for 1.5 (§6 note). The accumulation is float. +- `numerical_grad_rows/_cols`, `sgd_update_rows/_cols`: row and column indices + are ints (§6 notes), so N5 applies. The gradients and updates are floats. + Hand-derived: d(q[1][0]^2)/dq[1][0] at 3 with eps 0.5 is exactly + (12.25 - 6.25) / 1 = 6. +- Model rows (`eigen_model_load model_load_weights eigen_generate + eigen_eval_loss native_train_step_builtin`, server profile): each probe writes + a constant-weight v2 model (the shape of `tests/gen_tiny_model.eigs`) and a + child script, runs the child with `exec_capture of [exe_path of null, child]`, + and prints only the child's `R16|` lines. The loader prints its file path on + stdout, and the path differs each run. Token ids and `max_tokens` follow N5, + and generated ids are ints (indices). `generate_response` has no stop token, + so `max_tokens` 4 yields exactly 4 ids. The loss is a float, and the training + status is JSON text. `eigen_model_load`'s own numbers are internal, so its + row only pins the loaded config. + +## Per-row derivation, round f (20 rows; R1.6f) +- Store handles (`store_open store_close store_collections store_count + store_drop`): `store_open` returns a handle dict with int id/gen fields. A + forged handle whose `_store_id` is `id + 0.5` raises `value`; today the + `(int)` cast resolves it to the real store and closes, counts or drops it. + `id + 0.0` converts. `store_count` returns a count (int). After a close, the + handle raises `value`, as BUILTINS.md documents. +- `shared_delete`, `shared_size` (server profile): a removed count and an entry + count, so ints. Registration sets `eigs_http_active` on the main thread + (ext_http.c:1950-1958), so the shared store works without serving. +- `http_route`, `http_route_authed`, `http_static` (server profile): numbers + reach them only through `value_to_string` (N6, R1.7), so the registration + answers do not change. +- `http_post` (B6, server profile): headers JSON `{"X-Number": + 9007199254740993}` and the string body `ping`, posted to R1.6a's loopback + capture fixture, which answers with the `X-Number` value it received. The + JSON decoder gives ints at R1.6, so the header is exactly + `9007199254740993`: `["9007199254740993", "str"]`. The first probe put the + number in the body slot, which `ext_http.c:717` ignores. See "B6 capture + fixture". +- `http_early_bind`, `http_serve` (server profile): the port follows N5. Each + call runs in a child script under `exec_capture`'s timeout form, because + `http_early_bind` prints its cwd on stdout and a valid `http_serve` serves + forever. Only `R16|`-prefixed child lines are printed. `http_serve of 0.0` + binds an ephemeral port and serves until the 3 s timeout, which returns -2. + `0.5` raises `value` before binding (today it truncates to 0 and serves). + The capability flag in each line separates the N5 `value` from the release + profile's capability `value`. A port is N5, then 0..65535 (C6): 65536 + raises `value`, and 0 (ephemeral) is valid. +- `net_*` (server profile, loopback only): ports, socket handles and max bytes + follow N5. A handle is a packed number, and `net_unpack`'s `(long long)` + cast truncates `l + 0.5` to the real socket today. `net_send`'s byte values + follow Q1 (300 and 65.5 raise; today they wrap to 44 and truncate to 65), + and a raise sends nothing. Counts and ports are ints, and handles are ints + (int-requiring results that are ids). `net_recv` returns a buffer, whose + elements are floats. `net_listen of 65536` and `net_dial` to port 65536 + raise `value` (C6). `net_send` keeps `"hihi"` (C7): `ext_net.c` checks + `g_has_error` at line 363, before the byte conversion at line 364 and not + after it, so the R1.6f byte reader's raise must also stop the send, and the + probe pins that. +- R1.6a-D2: on `pre` and the tag, a fractional handle or port truncates to a + live socket, so the call answers. Those answers are a port (`net_port`, the + OS's ephemeral choice: 39805 and 40863 on R1.6a's two matched builds) or a + socket handle (`net_listen`, `net_dial`, `net_accept`: a handle-table slot + plus a generation, which depend on the build's allocation order). Each such + line prints a predicate now: `(net_port of (l + 0.5)) == pt`, `(... ) > 0` + for a handle, and `(net_accept of ...) != null`. On the branch each call + raises `value` first, so no `.branch` changes. + +## Per-row derivation, round e (38 rows; R1.6e) +Every gfx probe opens a 64x48 window under `SDL_VIDEODRIVER=dummy` (the suite's +headless gfx condition), clears it, draws, and reads pixels back with +`gfx_read` before any present. Expected pixels are derived by hand from Q7 +(§2.6) and from the source's tables (the 5x7 `font5x7` glyph for `T` is +`{0x1F,0x04,...}`, a full top row and a stem in column 2; the `gfx_fb` palette +is 0 white, 1 `0xAAAAAA`, 2 `0x555555`, 3 black). +- Geometry (Q7): `gfx_rect [-3e9, 0, 6e9, 4]` and `gfx_clip` with the same rect + intersect to `[0, 0, 64, 4]`, the top band (the MAP `gate.N5.q7-readers` + case); `gfx_line [-3e9, 5, 3e9, 5]` clips (Liang-Barsky) to the full row 5; + `gfx_circle` of radius 3e9 and `gfx_rrect [-3e9, -3e9, 6e9, 6e9, 10]` cover + the screen; `gfx_fb` of a 1x1 black texel at `[-3e9, -3e9]`, scale 6e9, + covers the screen; `gfx_text` at scale 3e9 makes the `T`'s column-0, row-0 + square cover the screen. Today the `(int)` casts give INT_MIN and nothing is + drawn (or the clip is ignored). +- A point outside the drawable area draws or reads nothing: `gfx_point` at x + `-0.6` (today it truncates to 0 and draws column 0) and `gfx_read` of + `[3e9, 0]` and `[-0.6, 0]`, which answer `null` (today `[0, 0, 0]`). +- Rounding half away from zero replaces truncation for continuous coordinates + (Q1, Q4): a rect at x 20.5 with w 2 covers 21..22 (today 20..21); a point at + 10.6 lights 11 (today 10); `T` at x 0.6 starts at column 1 (today 0); + `gfx_read [9.6, 10.4]` reads (10, 10) (today (9, 10)); `gfx_open + [64.4, 47.6]` is 64x48 (today 64x47). +- Bounded colour (Q7): `[300, -20, 127.6]` clamps and rounds to + `[255, 0, 128]` (today `(int)` then Uint8 wraps it to `[44, 236, 127]`). The + probes cover `gfx_clear`, `gfx_rect`, `gfx_line`, `gfx_point`, `gfx_circle`, + `gfx_rrect` and `gfx_text`. +- Magnitude (Q7): `gfx_open [3e9, ...]`, `gfx_delay 1e300`, + `gfx_text_width ["abc", 3e9]` and `gfx_text_height 3e9` raise `value`. A + measurement's scale rounds (`1.6` gives 2: widths 36 and 14 rather than + today's 18 and 7). Bitmap metrics are `len * 6 * scale` and `7 * scale`. +- `gfx_text_width`, `gfx_text_height`, `gfx_ticks`, `gfx_read`: int results + (pixel sizes, Uint32 ms, channel values). `gfx_ticks` prints only its kind, + an ordering, and 0 before SDL loads. +- `gfx_poll` (B3): with the `events` hook, the three injected events decode + with int fields: `[17, "int", -9, "int"]`, `[4, "int"]` and `[1, "int", 23, + "int", 42, "int"]`. The empty-queue `null` case stays as the second case, + `gfx_poll.empty`. +- `gfx_fb`: w and h follow N5 (`2.5` raises), and palette bytes follow the Q1 + byte reader (`300` and `1.5` raise; today they wrap or truncate and the + frame is drawn). +- `ppu_render_frame`: the LCDC byte follows the Q1 byte reader, so `145.5` and + `401` raise (today they read 145 and the frame renders). With LCD off the + frame is blanked. With LCDC `0x91` and BGP `0xFF`, every pixel is + `bgp[0] = 3`, derived by hand from the PPU loop (no tile data is needed). +- Audio devices (`SDL_AUDIODRIVER=dummy`): freq, channels, the pause flag, + loop counts and channel numbers follow N5. Device ids and channel numbers + are ints. A probe opens only the device it needs, because the mixer device + blocks the stream device and SDL_mixer under the dummy driver. Counters the + device drains in real time (`audio_queue_size`, `audio_stream_queued`) print + their kind and a range only. `audio_music_play` plays a 52-byte WAV that the + probe writes. `audio_volume`'s and `audio_stop`'s 1/0 answers are ints (C1). +- Delivered audio (B1, through the hooks; Q4 rounding is half away from zero): + - `audio_stream_push [2.5, -3, 0.5]` delivers `[32767, -32767, 16384]`: + clamp to [-1, 1], then round 0.5 x 32767 = 16383.5 to 16384. Today + truncation gives 16383; + - `audio_play [2.5, -3, 0.5, 0]` mixes `32767 -32767 16384 0` at volume 1; + - `audio_play_loop [[0.5, -3], 2.0]` mixes `16384 -32767` twice, since loops + 2 is two plays in `audio_mix_callback`; + - `audio_volume` of 9.0 clamps to the mixer's 4.0, on a looping `[0.1, + -0.1]` clip: round(3276.7) = 3277, times 4 is 13108. Today 3276 x 4 is + 13104, and an unclamped 9.0 would give 29493; + - `audio_music_volume` 300, -20 and 64.5 reach `Mix_VolumeMusic` as 128, 0 + and 65: clamp to 0..128, then round. Today 64.5 truncates to 64. +- Generators: samples are floats. The durations used (0.001 s, so 44.1 + samples at 44100 Hz) truncate and round to the same 44 samples. The sample + count `dur * rate` stays R2.6's (B5), so at R1.6 the generators read `dur` + through the magnitude reader only for validity; the rounding witness is in + "R2.6 row list". `audio_sine`, `audio_saw`, `audio_square` and + `audio_noise` carry `closed_in` main #1767 (their duration caps, A2). + `audio_sweep`'s wave selector follows N5. `audio_envelope`'s attack and + release are magnitudes, so `1e300` raises (today `(int)(t * rate)` is + undefined and the call answers). `audio_gain` and `audio_mix` clamp to + [-1, 1] (unchanged). + +## ORACLE (Lua 5.4.9, `/tmp/r15-critic/lua-5.4.9/src/lua`) +Each `oracle/.lua` is run as is; `oracle/.lua.out` is its verbatim output. +Lua prints floats with `%.14g`, so values are compared by kind and value, never +by text (§10). + +| row | Lua expression (file) | Lua output (verbatim, tab-separated) | agrees with `.branch` | +|---|---|---|---| +| abs | `math.abs(-9007199254740993)`, `math.abs(-2.5)`, `math.abs(math.mininteger)` with `math.type` | `9007199254740993 integer` / `2.5 float` / `-9223372036854775808 integer` | yes for 1-2; line 3 is the declared N4b divergence (Lua wraps, EigenScript raises `value`) | +| floor | `math.floor(9007199254740993)`, `(-2.5)`, `(1e300)` | `9007199254740993 integer` / `-3 integer` / `1e+300 float` | yes | +| ceil | `math.ceil(...)`, same arguments | `9007199254740993 integer` / `-2 integer` / `1e+300 float` | yes | +| min | `math.min(9007199254740993, 9007199254740994)`, `(3, 3.5)`, `(4, 3.5)`, `(7)`, `(3, 3.0)`, `(3.0, 3)` | `9007199254740993 integer` / `3 integer` / `3.5 float` / `7 integer` / `3 integer` / `3.0 float` | yes | +| max | `math.max(9007199254740993, 9007199254740992)`, `(3, 2.5)`, `(2, 2.5)`, `(7)`, `(3, 3.0)`, `(3.0, 3)` | `9007199254740993 integer` / `3 integer` / `2.5 float` / `7 integer` / `3 integer` / `3.0 float` | yes | +| len | `#{1, 2}`, `#"hello"` | `2 integer` / `5 integer` | yes | +| ord | `string.byte("A")`, `select("#", string.byte(""))` | `65 integer` / `0` | yes for "A"; `""` gives no value in Lua and EigenScript's documented `-1` (BUILTINS.md) | +| random_int | `math.randomseed(7)`; `math.random(5, 5)`; `(9007199254740993, 9007199254740993)`; `pcall(math.random, 1.5, 3)`; `math.random(2.0, 2.0)` | `5 integer` / `9007199254740993 integer` / `false bad argument #1 to 'math.random' (number has no integer representation)` / `2 integer` | yes (the raise is `value`) | +| seed_random | seeds 2^53+1 vs 2^53 then `math.random(0, 1000000)`; `pcall(math.randomseed, 1.5)`; `42.0` vs `42` | `seeds-2^53+1-and-2^53-differ true` / `false bad argument #1 to 'math.randomseed' (number has no integer representation)` / `integral-float-seed true` | yes (the values differ by RNG; kinds and verdicts agree) | +| num | `tonumber(s)` for the probe's 25 strings (`oracle/num.lua`) | see `oracle/num.lua.out`: ints for `3`, `9007199254740993`, ` 12 `, `+5`, `-0x10` (-16), `0x1F`, `0X1f`, `0xFFFFFFFFFFFFFFFF` (-1), `-9223372036854775808`; floats for `3.0`, `9223372036854775808`, `.5`, `1.`, `1e5`; `nil` for `12abc`, `0b101`, `0o17`, `1_000`, `""`, `" "`, `inf`, `nan`, `1.2.3`, `0x`; `16.0 float` for `0x1p4` | yes; `nil` maps to a raise; `0x1p4` is the declared divergence (N10) | +| acos, asin, atan, cos, sin, tan | `math.(v)` with `math.type` and `%.17g` (`oracle/.lua`) | e.g. `acos`: `0.0 float 0` / `1.5707963267949 float 1.5707963267948966` | yes: every `%.17g` value equals the `.branch` double | +| atan2 | `math.atan(1, 1)`, `math.atan(0, -1)` | `0.78539816339745 float 0.78539816339744828` / `3.1415926535898 float 3.1415926535897931` | yes | +| pi | `math.pi` | `3.1415926535898 float 3.1415926535897931` | yes | +| random | `math.randomseed(7)`; `math.random()` | `float true true` | yes | +| bit_and, bit_or, bit_xor, bit_not, bit_shl, bit_shr | the probe's operands with `& \| ~ << >>` (`oracle/bit_*.lua`) | e.g. `9007199254740993 integer` / `false ...: number has no integer representation` / `2 integer`; `1 << 63` = `-9223372036854775808`, `1 << 64` = `0`, `8 << -1` = `4`, `-1 >> 1` = `9223372036854775807`, `1 >> -1` = `2` | yes, all lines | +| char_at | `string.sub("hello", i+1, i+1)` (0-based to 1-based) | `e` / `false bad argument #2 to 'string.sub' (number has no integer representation)` / `e o` | yes | +| chr | `string.char(65)`, `(65.5)`, `(256)`, `(66.0)` | `A` / `false ... (number has no integer representation)` / `false ... (value out of range)` / `B` | yes | +| substr | `string.sub("hello", start+1, start+len)` | `el` / `false bad argument #2 ...` / `false bad argument #3 ...` / `el` | yes | +| list_insert_at | `table.insert(t, i+1, v)` | `1,9,2,3` / `false bad argument #2 to 'table.insert' (number has no integer representation)` / `7,1,9,2,3` | yes | +| list_remove_at | `table.remove(t, i+1)` | `1,3,4` / `false bad argument #2 to 'table.remove' (number has no integer representation)` / `3,4` | yes | +| divide, pow | `7 / 2`, `6 / 3`, `2 ^ 10`, `2 ^ 0.5` with `%.17g` | `3.5 float 3.5` / `2.0 float 2` / `1024.0 float 1024` / `1.4142135623731 float 1.4142135623730951` | yes | +| sqrt, exp, log | `math.sqrt(4)`, `(2)`; `math.exp(0)`, `(1)`; `math.log(1)`, `(8)` | `2.0 float 2` / `1.4142135623731 float 1.4142135623730951`; `1.0 float 1` / `2.718281828459 float 2.7182818284590451`; `0.0 float 0` / `2.0794415416798 float 2.0794415416798357` | yes | +| str_from_bytes | `string.char(72, 105)`, `(65.5)`, `(328)`, `(72.0, 105.0)` | `Hi` / `false ... (number has no integer representation)` / `false ... (value out of range)` / `Hi` | yes | + +## QUESTIONS (all ruled 2026-10-20 in `RULINGS.md`) +| question | ruling | applied in | +|---|---|---| +| Q-HIST: LOSS rows closed before R1.6 | A2: `closed_in` R1.2/R1.3, checked as `.pre == .branch` | `abs json_build json_encode sort sort_by` | +| Q-R17: the writers' loss is R1.7's | agreed: the decoders change at R1.6; the writers' float loss is R1.7 | no change | +| Q-DB253: the db 2^53 raise | agreed: exact int64; `db_check_exact_int`'s raise is deleted with its reason | no change (the db `.branch` files assumed it) | +| Q-HEX | agreed: exact digits; negatives raise | `hex` (adds `hex of -1`) | +| Q-SIGNEXT | agreed: an exact int from the existing operation; R1.6b-D1 (#1894): the subtraction is checked int64 arithmetic, so a result below INT64_MIN raises `value` | `sign_extend` (adds the overflow case) | +| Q-CAP: rows the release profile cannot observe | A3: the `profile` column and matched builds | the 25 non-release rows | +| Q-CLOCK | B2: the injected clock | `monotonic_ns` | +| Q-ALREADY: int-requiring rows exact before R1.6 | A2: `closed_in` R1.5 (`bit_*`) and main (`chr`, `dispatch`) | 8 rows | +| Q-STREAMOPEN | agreed: a fraction raises `value`; an integral count outside 1..10,000,000 keeps `limit` | `stream_open` (adds the count 0) | +| Q-FLAGKIND | C1: 1/0 status results are ints | `record_history task_detach audio_volume audio_stop spawn` | +| Q-SANDBOX | agreed: a catchable `value` | no change | +| Q-RECVTIMEOUT | C5: the one-year cap is removed; past LONG_MAX ms raises | `recv_timeout` | +| `proc_read_buf` has no reachable bytes | A5, C10: a stdin `Hi` case | `proc_read_buf.hi` | +| Q-WINDOWS | agreed: N5 for every offset and count | no change | +| Q-PCM16OUT | agreed: the clamp is unchanged; the change comes through the window | no change | +| `inflate`'s output kind; `f64_to_bytes` above 2^53 | C8; C9 | `inflate`; `f64_to_bytes` | +| Q-NETSEND-R26 | C7: keep `"hihi"`; the byte reader's raise must stop the send | no change (the probe pins it) | +| Q-HTTPPORT | C6: N5, then 0..65535; 65536 raises; 0 is valid | `http_serve http_early_bind net_listen net_dial` | +| `http_post` header values | B6: headers JSON plus a string body, and a capture fixture | `http_post` | +| Q-AUDIO-HIDDEN | B1: the hooks assert the delivered PCM or volume | `audio_stream_push audio_play audio_play_loop audio_volume audio_music_volume` | +| Q-GEN-1767 | A2: `closed_in` main #1767; B5: `dur * rate` stays R2.6 | the four generators; "R2.6 row list" | +| Q-GFXPOLL | B3: injected events; the empty-queue case stays | `gfx_poll`, `gfx_poll.empty` | +| Q-TEXT-SCALE | agreed: geometry | no change | +| Q-GFX-ENV | agreed: the declared environment | no change | +| the LIST arms of `add`/`subtract`/`multiply` | C4: elementwise through the scalar arm | `add subtract multiply` | +| `sum of []` | C2: the int 0 | `sum` | +| min/max int/float ties | agreed: the first argument wins, kind included | `min max` | +| empty `min`/`max` | R1.6b-D2 (#1895): raises `value` in both call forms | `min max` (add `of []` and `of ([])`) | +| `num of "-9223372036854775808"` | agreed: the int INT64_MIN | `num` | +| integer text beyond int64 in the scanners | C3: raises `value`, naming the token | `scan_ints scan_int_tokens` | +| the kind of a spawn handle's `done` | C1: an int (it is numeric) | `spawn` | +| the store `3.0` round trip | agreed: transitional between R1.6 and R1.7; the R1.6f commit says so | no probe (R1.6f's commit message) | + +No new question arose in this round. For C10, §6's `proc_read_buf` row +("bytes stored as floats") and Q2 (buffer storage is f64) agree on the element +kind: float. + +## Vacuity guard (rulings A1, A2) +§9.4's rule ("every LOSS, int-requiring and Q7 row has `changes: yes`") is +checked against `.pre` (`changes_vs_pre`), on every row without `closed_in`. +`make_table.py` prints `guard: guarded=139 yes=106 closed_in=17 +pending_profile=16 vacuous=0`. +- `closed_in` (17 rows), one reason per row. Each closure is checked: every + case of the row has `.pre == .branch`, and its probe still pins exactness + above 2^53 or the fraction raise: + - R1.2/R1.3: `abs json_build json_encode sort sort_by`; + - R1.5: `bit_and bit_not bit_or bit_shl bit_shr bit_xor`; + - main (already exactness-checked): `chr dispatch`; + - main #1767 (the duration caps): `audio_sine audio_saw audio_square + audio_noise`. +- Pending: the 16 guarded non-release rows. `make_table.py --final` checks + them after R1.6a's matched recordings. +- Three rows change through a class other than their Q7 one: + `buf_to_pcm16le` (Q-PCM16OUT: the clamp is unchanged, and the change comes + through the window), `audio_sweep` (B5: through its wave selector) and + `audio_volume`. Its gain already clamps to [0, 4] today, and the `mix` hook + now pins that clamp at the boundary (an unclamped 9.0 would mix 29493). The + row changes through its N5 channel, its int answer (C1) and the rounding of + its clip. +- The former partial witnesses now observe the row's own effect: + `audio_stream_push audio_play audio_play_loop audio_music_volume` (B1: the + delivered PCM or volume), `monotonic_ns` (B2: the digits), `gfx_poll` (B3: + the event fields) and `proc_read_buf` (A5: the bytes). + +## R2.6 row list (ruling B5) +Witnesses that belong to R2.6, not to R1.6: +- `audio_sine`, `audio_saw`, `audio_square`, `audio_noise`: the sample count + `dur * rate` rounds instead of truncating. At 44100 Hz, 0.00101 s is 44.541 + samples: 45 after R2.6, and 44 at R1.6 and today. The critic's executed + `enhanced_audio.eigs` gives 44, 44 and 45 samples on `pre` for 0.001, + 0.00101 and 0.001031746031746032 s; rounding gives 44, 45 and 46. At R1.6 + the generators read `dur` through the magnitude reader only for validity + (finite, and within what the call can honour). + +## B6 capture fixture (server profile; R1.6a builds it) +The `http_post` probe posts to `R16_HTTP_CAPTURE_URL` with headers JSON +`{"X-Number": 9007199254740993}` and the string body `ping`. `http_post` +turns each header value into text with `value_to_string` +(`builtin_http_post`, `ext_http.c`), so the header carries whatever number the +JSON decoder produced. R1.6a's fixture: +- listens on 127.0.0.1, on an ephemeral port, before the probe starts; the + gate exports `R16_HTTP_CAPTURE_URL=http://127.0.0.1:/capture`; +- accepts ONE request, and reads the request line and the headers up to the + blank line, then the body by `Content-Length`; +- answers `200 OK` with a body that is exactly the received `X-Number` value + (the bytes after `X-Number:` and optional spaces, up to CR LF), or `MISSING` + when that header is absent, and then closes; +- keeps the full request in a log, which the gate prints on failure. + +The `.branch` is `["9007199254740993", "str"]`. On `pre` and on the tag, the +JSON decoder gives the double 2^53, so the expected `.pre` is +`["9007199254740992", "str"]`. That is derived from the code; R1.6a records +it. Without the fixture the URL is empty, `http_post` answers `""`, and the +row fails visibly. + +## R1.6a handoff (assigned by the rulings; not done in this bar) +- Move the shim to its repo target, `tests/phase2/observation_hooks.c` (A4), + and point `run_probes.sh` at it. +- Record the 25 non-release rows on matched builds (A3): `make server` and + `make zlib` builds of `4ac31e67` and v0.46.0, and `make server-db` builds of + both with a PostgreSQL (CI's `database-worker` job, or any host with one). + Then `make_table.py --final` must exit 0. The profile witnesses' `profile-ok` + path has not run here, because no profile build exists; if a witness is + wrong, the runner refuses the build (exit 2), so the failure is loud. +- Build the B6 capture fixture to the spec in "B6 capture fixture", and export + `R16_HTTP_CAPTURE_URL`. +- Amend §9.4 with A1's measurement: `changes_vs_tag` is yes on 203 of the 207 + recorded rows (`make_table.py`'s `vs_tag` counts), because every probe calls + `num_type` and the tag has none. +- Re-record `read_text`'s `.pre` (and `.tag`) on `1fd4422c` as its in-scope + baseline (R1.6a-D1; #1668 changed it upstream of `4ac31e67`); its + `changes_vs_pre` must then read `no`. +- Run `determinism.sh` on the matched builds too (with `PRE_BIN_

` and + `TAG_BIN_

`), since the profile rows' records have never been recorded + twice (R1.6a-D2's class). +- Add the gate that runs each case on the branch build (same environment, + stdin fixture and shim) and compares stdout plus `rc=` with `.branch`. +- R1.6f's commit message states that the store `3.0` round trip is + transitional between R1.6 and R1.7 (agreed answer). +- R1.6's builder confirms the `recv_timeout` limit (LONG_MAX ms, from + `(long)ms`) against the final C types (C5: "the builder determines it from + the C types"); the probe's 2^63 ms case assumes that limit. + +## OUT OF SCOPE (found while deriving; not part of this bar) +Each item is filed or closed with a reason (the out-of-scope ledger, 2026-10-20): +- `seed_random` seeds `srand48`, which keeps only the low 32 bits. So seeds 1 + and 4294967297 give one stream on v0.46.0 and on 4ac31e67; Lua tells them + apart. That contradicts §6's "a seed's range is all of int64". Filed: + https://github.com/InauguralSystems/EigenScript/issues/1886 +- §6's counts prose was stale at the then-current branch tip: "230 numeric", "51 other", + "42 are LOSS" and "the six `bit_*`", against the table's and the + inventory's 231, 52 and 36 (232 and 53 at `1fd4422c`). Filed: + https://github.com/InauguralSystems/EigenScript/issues/1887 +- `nearest_in_range_all` returns, for EACH entity, its nearest active entity + (itself included, at distance 0). BUILTINS.md says it "returns ALL active + entities within range". Filed: + https://github.com/InauguralSystems/EigenScript/issues/1888 + Resolved by main #2010 and the orchestrator's comment 6402855103, expressly + assigned to the next main-to-phase2 sync: nearest OTHER active entity. + S10-D2 changes only the frozen result index 1 to 0; source/types stay fixed. +- `numerical_grad_rows/_cols` and `sgd_update_rows/_cols` silently skip an + out-of-range row or column index, and the model builtins clamp an + out-of-range token id. Filed: + https://github.com/InauguralSystems/EigenScript/issues/1889 +- Out-of-range gfx arguments are silently wrong today: colours wrap modulo + 256, `gfx_open of [3e9, 48]` answers `true`, and an off-window `gfx_read` + answers `[0, 0, 0]`. Duplicate of + https://github.com/InauguralSystems/EigenScript/issues/1763, which lists + every one of these casts and routes them to #1758's Q7. R1.6e's readers fix + them, and this bar's `gfx_*` rows pin each case. +- `recv_timeout` silently caps any `ms` above 3.15e10 at one year. Accepted + into R1.6 and ruled C5: R1.6 removes the cap, and the probe pins the raise + past LONG_MAX ms. +- The plan's R1.6c row says "`vm.c`'s one builtin", but no builtin is defined + in `vm.c` (`vm.c:45-46` only declares `builtin_report` and + `builtin_observe`). Accepted: this is drift in the orchestrator's brief, not + in the repo; the design doc has no such sentence, and the round counts hold. +- `gfx_poll` has no headless witness in a plain `.eigs` probe. Accepted: not a + repo gap. `make ui-sdl-input-gfx` (`tests/ui_sdl_input.c`, #1263) pushes + real SDL events into an embedded state and reads them through `gfx_poll`, + and `tests/ui_native_input.py` drives xdotool on Xvfb. Ruled B3: the shim's + `events` hook gives this bar its witness. + +## Corrections log (a `.branch` changed after its first observation) +- Every `.branch` predates the first recorded run of its probe: comparing each + `.branch` mtime with the stderr file of the probe's first `.pre` run gave + `branches=231 written-after-first-run=0`. The one later change follows. +- `gfx_fb`: an audit found that its probe lacked the int-requiring rule's + second call (an integral float that converts). I added + `gfx_fb of [fb, 2.0, 2.0, 0, 0, 1]` and the expected line + `[[0, 0, 0], [255, 0, 0]]` to the probe and the `.branch` together. The line + is derived as before: palette index 3 at (1, 1) is black, and (2, 0) keeps + the red clear colour. No existing expected line changed. The new line equals + `pre` (integral floats convert today), and the row's other lines still + differ, so `changes: yes` (now `changes_vs_pre`) stands. +- **Fix round, 2026-10-20.** Each entry below changed a `.branch` (33 rows) or + added a case (2), under the ruling it names. Every new expectation is + derived from the design and the rulings, and was written before its case's + first run in this round: comparing each changed `.branch` mtime with the + case's first stderr file gave `changed_branches=35 recorded=29 + pending_never_run=6 written-at-or-after-first-run=0`. Against a snapshot of + `builtins/` taken before the round, the changed `.branch` files and the + changed probes are the same 35 cases. A hook case's `.branch` also ends with its + `hook-loaded ` line (the record format, A4). + - B1 `audio_stream_push`: adds the delivered PCM, `SDL_QueueAudio 3: 32767 + -32767 16384`. + - B1 `audio_play`: adds the mixed PCM, `mix: 32767 -32767 16384 0 0 0 0 0`. + - B1 `audio_play_loop`: the clip `[2.5, -3]` becomes `[0.5, -3]`, because + a clamped ±1 gives ±32767 under truncation and rounding alike. Adds `mix: + 16384 -32767 16384 -32767 0 0 0 0` (two plays). + - B1, C1 `audio_volume`: the clip `[0.1, 0.2]` becomes `[0.1, -0.1]`. `1` + becomes `[1, "int"]`, and the line `mix: 13108 -13108 ...` is added (the + 9.0 volume clamps to 4.0; 3277 x 4). + - B1 `audio_music_volume`: `[300, 64.4]` becomes `[300, -20, 64.5]`, so + `[null, null]` becomes `[null, null, null]`, and the volumes reaching + `Mix_VolumeMusic` are 128, 0 and 65. (64.4 rounds and truncates to the + same 64.) + - B2 `monotonic_ns`: `["int", "int", true, true]` (two real reads) becomes + `[9007199254740993, "int", true]` under the injected clock. + - B3 `gfx_poll`: the probe reads three injected events: `[17, "int", -9, + "int"]`, `[4, "int"]` and `[1, "int", 23, "int", 42, "int"]`. The old probe + and its `.branch` (`["open", true]`, `null`) moved unchanged to the new + case `gfx_poll.empty`. + - B4 `store_update`, `store_delete`, `store_get`: add `["update-big-key", + true, 2]`, `["delete-big-key", true, 0]` and `["key-2^53+1-found", true]`. + - B6 `http_post`: `["", "str"]` (a refused port, with the number in the + ignored body slot) becomes `["9007199254740993", "str"]` from the capture + fixture. + - A5, C10 `proc_read_buf.hi` (new case): `[2, 72, 105, "float", "float"]`, + then `["eof-after", null]`. + - C1 `record_history`: `[0, 1]` twice becomes `[0, "int", 1, "int"]` twice. + `task_detach`: `1` becomes `[1, "int"]`. `audio_stop`: `[1, 0]` becomes + `[1, "int", 0, "int"]`. `spawn`: `["int", "int"]` becomes `["int", "int", + "int"]` (the kind of `done`). + - C2 `sum`: adds `[0, "int"]` twice, for `sum of []` and `sum of ([])`. + - C3 `scan_ints`, `scan_int_tokens`: each adds `["beyond-int64", "value", + true]`, where `true` says the message names `9223372036854775808`. + - C4 `add`, `subtract`, `multiply`: one int-list case each, + `[[3, 9007199254740994], "int", "int"]`, `[[3, 9007199254740992], "int", + "int"]` and `[[6, 9223372030926249001], "int", "int"]`. + - C5 `recv_timeout`: adds `["timeout-past-int64", "value"]` for 2^63 ms, + and queues a third value so that no binary blocks. + - C6 `http_serve`, `http_early_bind`, `net_listen`, `net_dial`: each adds + the port-65536 raise (`value`, with the capability flag `false`), and + `http_serve` adds its child's `["serve_c-rc", 0]`. + - C8 `inflate`: adds `[[104, 105], "int"]`. + - C9 `f64_to_bytes`: adds `[67, 64, 0, 0, 0, 0, 0, 0]` for 2^53+1. + - Agreed answers: `num` adds `["-9223372036854775808", + -9223372036854775808, "int"]`; `min` and `max` add `[3, "int", 3, + "float"]` (the ties `[3, 3.0]` and `[3.0, 3]`); `hex` adds `["negative", + "value"]`; `stream_open` adds `["count-zero", "limit"]`. +- **R1.6a rulings, 2026-10-21.** Files changed in this round, by mtime after + the previous round's last edit: the new case `read_text` (`.eigs`, + `.branch`) and five probes (`net_port`, `net_listen`, `net_dial`, + `net_accept`, `exec_capture`). No existing `.branch` changed. + - R1.6a-D1 `read_text` (new row and case): `["missing", "io"]`, + `["written", 10485761]`, `["over-cap", "io", true]`, `["over-cap-tail", " + is 10485761 bytes, over the 10485760-byte cap"]`, `["hi", "str"]`, derived + from §6 and `builtin_read_text` at `1fd4422c`. The `.branch` was written 37 + s before the probe's first run. One exploratory call ran on `pre` before + it (`read_text` of a missing path answers `""` there, as the ruling says); + the expectation comes from the `1fd4422c` code, which differs from that. + - R1.6a-D2 `net_port`: `["fraction-answer", net_port of (l + 0.5)]` became + `["fraction-answer", (net_port of (l + 0.5)) == pt]`. The `.branch` line + on that path is the raise, `["handle-fraction", "value"]`, so its shape did + not change and the `.branch` is unchanged. + - R1.6a-D2, same class: `net_listen` (the 0.5 and 65536 lines), `net_dial` + (the `pt + 0.5` and 65536 lines) and `net_accept` (the `l + 0.5` line) + printed socket handles on `pre`; they print `> 0` or `!= null` now. + `exec_capture`'s `1e300` line printed a timing-raced exit code on `pre`; + it prints `type of` the answer now. No `.branch` line changed. +- **R1.6b rulings, 2026-10-21** (frozen-bar amendments, listed with their + SHA-256 in `tests/phase2/builtins_changes.md`). Each new case is appended + after its probe's last line, so no existing line moves; the re-recorded + `.pre` and `.tag` keep every earlier line byte for byte. + - R1.6b-D1 `sign_extend`: adds `["overflow", "value"]` for + `sign_extend of [-9223372036854775553, 8]` (the subtraction is checked + int64 arithmetic; INT64_MIN - 1 raises). `pre` and the tag print + `["overflow-answer", -9.223372036854776e+18]`. + - R1.6b-D2 `min`, `max`: each adds `["empty", "value"]` (`of []`) and + `["empty-list", "value"]` (`of ([])`). `pre` and the tag print + `["empty-answer", 0]` and `["empty-list-answer", 0]`. + +## Instrument notes +- Check machinery, in lines (first version -> fix round -> R1.6a rulings): + `run_probes.sh` 46 -> 94 -> 94, `make_table.py` 98 -> 212 -> 229 (the + pinned `git archive` source), `builtins/_hooks/observation_hooks.c` new, + 159 (the critic's was 44), `builtins/_profiles/*.eigs` new, 37, `plants.sh` + new, 132 -> 140, and `determinism.sh` new in this round, 28 (it replaces a + scratch script, so R1.6a can run the same check on its matched builds). + Total 144 -> 634 -> 687. At that R1.6a import, `rowmeta.tsv` and + `cases.tsv` were declarations of 232 and 234 lines plus headers; + R1.6g's populations were 310 and 312; S10-D2's current populations are 309 and 310. +- `run_probes.sh`: + - The first version wrote stdout through a relative path after its `cd`, + so every `.pre` and `.tag` held only `rc=0`. It writes an absolute path, + deletes the old record before each run, and fails (exit 2) on an empty + stdout with rc 0. Before the deletion was added, the relative-path plant + SURVIVED, because the stale file passed the size test. + - Added in the fix round: cases from `cases.tsv` and profiles from + `rowmeta.tsv`; the shim, compiled per run and preloaded for hook cases, + with the `hook-loaded` check; the stdin fixture; `PENDING-PROFILE` and the + profile witnesses; the `DATABASE_URL` and `R16_HTTP_CAPTURE_URL` + pass-through; rc 124 as a named failure; `ulimit -v 1500000`. + - Found while recording: the new `recv_timeout` case at first blocked `pre` + and the tag for 60 s (rc 124). The old binaries answer the two earlier + raise cases by consuming the two queued values, so the third read waited + on an empty channel. A third queued value fixed the probe, and the runner + now fails on rc 124. + - The first hook recordings held two `hook-loaded` lines, because + `LD_PRELOAD` was set before `timeout`, which loaded the shim too. It is + set after `timeout` now, and `make_table.py` requires exactly one line. +- `plants.sh` runs every plant below in a fresh scratch copy of the bar (and + of the tree's `docs/design` and `src`), and requires each check's named FAIL + line. Both baselines are clean first: `make_table.py` with no FAIL, and the + runner on `abs audio_stream_push gfx_poll.empty read_bytes_buf inflate` + (S10-D2 replaces the retired stdin case with the unchanged host-byte probe). + A mutation that does not apply counts as a failure: the first run caught + two (an unanchored `hex` also matched `random_hex`, and `len` matched + `buf_len`). Result after the R1.6a rulings: `plants examined=37 table=37 + red=37 survived=0`, exit 0 (35 in the fix round, plus P28 and T1). + - table checks: P1 a §6 row deleted; P2 a §6 class edited; P3 a stale §6 + row; P4 an empty table (`examined=0 table=0`); P5 a second definition of + `builtin_abs`; P6 an inventory class edited; P7 a q7 column dropped; P8 a + missing `.tag`; P9 a stray probe; P10 `closed_in` on `hex`, whose `.pre` + differs from its `.branch` (the orchestrator's plant); P11 an edited + `.branch` on a `closed_in` row; P12 `closed_in` removed from `abs` + (vacuous); P13 `hex.branch` made equal to `hex.pre` (vacuous); P14 a hook + record without its `hook-loaded` line; P15 a `hook-loaded` line on a + non-hook case; P16 a capability transcript as `inflate.pre`; P17 + `PENDING-PROFILE` on a release row; P18 `--final` with pending records; + P19 a `rowmeta.tsv` row deleted; P20 an unknown profile; P21 a case hook + that the shim lacks; P22 a shim hook renamed; P23 the stdin fixture + removed; P24 a stray file in `builtins/`; P25 `closed_in` on an unguarded + row; P26 a profile witness removed; P27 a primary case removed; P28 the + pre-#1841 revision (`EIGS_REV=4ac31e67`), which lacks `read_text`: `FAIL: + in rowmeta.tsv, not in section 6: read_text`. + - determinism check: T1 a printed time (`monotonic_ns`) appended to the + `len` probe: `UNSTABLE len.pre` (R1.6a-D2's class). + - runner and witness checks: R1 the relative output path; R2 a hook case + run WITHOUT the shim (the orchestrator's plant): the runner prints + `FAIL: audio_stream_push.pre ran without its hook pcm` and exits 2; R3 the + same hookless recording, then `make_table.py`: `audio_stream_push.pre + holds [], not one 'hook-loaded pcm' line`; R4 the `recv_timeout` probe + without its third queued value (rc 124); R5 the release binary passed as + the zlib build; R6 the stdin fixture missing; R7 and R8 the `silence` + mutation, after which `audio_stream_push.pre` records `SDL_QueueAudio 3: + 0 0 0` and `audio_play.pre` records `["mixed", false]`, both unequal to + the recorded `.pre`. (The critic's F1: an all-silence mutation passed the + first `audio_stream_push` row.) +- Determinism, fix round: all 233 cases were recorded three times on both + binaries, twice normally and once pinned to core 0 at `nice 19` (`taskset + -c 0 nice -n 19`, load average about 2.3 from other jobs). `compared=466 + unstable=0`. The 50 pending records are among the 466 and are trivially + stable. A fourth recording, after the last runner edit (an EXIT trap that + removes the compiled shim), equals the pinned run: `run4-vs-run3 + compared=466 unstable=0`. +- `make_table.py --final` on today's records exits 1 with 50 FAIL lines, one + per pending record (`FAIL: db_execute: .pre is PENDING-PROFILE db + (--final)`, ...), as it must until R1.6a records the matched builds. +- R1.6a rulings round: `determinism.sh` over the 234 cases first gave + `compared=468 records=468 unstable=1` (`UNSTABLE exec_capture.pre`; see + round c), then `unstable=0` after the fix. The earlier rounds' four + recordings had not shown it. + +## COVERAGE (historical R1.6a import, before R1.6-S2) +The following counts and transcripts describe the population at `1fd4422c`. +They are retained as recorded, including the then-pending profile observations; +they are not current checker results. Sync #5's S2 addition makes the current +pre-N9 table 233 rows (c 77) and case files 235 each; R1.6g adds 77, as described above. +Population, both directions, at `1fd4422c`: +- §6's table equals the inventory's `## Builtins` list: 232 = 232, no name + missing either way, and class/loss/q7 equal on every row. +- `rowmeta.tsv` equals the table (232 = 232), and each table row has its + primary case in `cases.tsv`. +- The probe files equal `cases.tsv`: `.eigs .branch .pre .tag` are 234 each, + none missing and none extra, and `builtins/` holds nothing else except the + three fixture directories. +- The shim's hooks equal the cases' hooks (5 = 5: `pcm mix mixvol clock + events`); `_stdin` equals the cases' stdin (1 = 1); `_profiles` equals the + non-release profiles (3 = 3). +- `make_table.py` prints `source=/home/jon/src/InauguralSystems/EigenScriptEcosystem/EigenScript@1fd4422c` + and `examined=232 section6=232 inventory=232 rowmeta=232 cases=234`, and + exits 0. +- The Lua oracle set equals the rows whose note names a Lua counterpart, plus + `num`: 36 = 36 (`read_text`'s note names none). `min`, `max` and `num` + gained the tie and INT64_MIN cases. + +Columns: `closed_in` on 17 rows; profile release 207, server 21, db 3, zlib +1; hooks on 7 cases (7 rows); one stdin case. + +| round | rows | done | changes_vs_pre yes / no / pending-profile | changes_vs_tag yes / no / pending-profile | +|---|---|---|---|---| +| b | 45 | 45 | 36 / 5 / 4 | 41 / 0 / 4 | +| c | 76 | 76 | 40 / 36 / 0 | 72 / 4 / 0 | +| d | 53 | 53 | 27 / 20 / 6 | 47 / 0 / 6 | +| e | 38 | 38 | 32 / 6 / 0 | 38 / 0 / 0 | +| f | 20 | 20 | 5 / 0 / 15 | 5 / 0 / 15 | +| total | 232 | 232 | 140 / 67 / 25 | 203 / 4 / 25 | + +`read_text`'s `yes` in round c is against a `.pre` that predates #1841; see +its derivation. + +## R1.6g N9 addition + +R1.6g-D1 and #1758 comment 6304880619 add 77 release-profile rows, sourced +from the N9 definitions in builtins_int.c: 76 int-requiring and fround +float-producing. That addition produced 310 rows and 312 cases (release 285, +server 21, db 3, zlib 1); S10-D2 later retires one row and two cases, +leaving 309 rows and 310 cases (release 284). All new branches were captured from Rust std plus +the explicit operand/shape rulings BEFORE builtins_int.c existed; their exact +SHA-256 amendments are in builtins_changes.md. Existing frozen branches remain +unchanged. oracle/n9.rs, its input TSVs and verbatim stdout capture the Rust +arithmetic; the supplemental test_n9_contract pins double rounding and exact +zero-divisor messages. The one-time broader grid stays in the round artifacts. + +## S10-D2 retirement + +Main #1691's zero-consumer removal of `proc_read_buf` stands. S10-D2 explicitly +retires its primary and `.hi` probes (all eight case files), the sole +`_stdin/hi` fixture and dependent plants P23/R6. All other plant IDs and all +other frozen `.branch` bytes stay fixed except the explicit #1888 index +1-to-0 semantic amendment below. Exact prior hashes, `DELETED` +amendments and the immutable bank anchor are in `../builtins_changes.md`; +`RULINGS.md` records how this supersedes A5/C10. The surviving unchanged +`read_bytes_buf` probe pins host-byte buffer elements as f64 using +`[3, 1.0, "float"]` and `[3, "float"]`; no new case is required. + +S10-D2 also applies #1888's recorded nearest-OTHER decision, expressly assigned +to this sync: the existing two-active-entity scenario keeps its numeric kind +witnesses while `nearest_in_range_all.branch` changes only index 1 to 0. +Its separate exact semantic amendment and hashes are recorded in +`../builtins_changes.md`; it is not an N6 float-text re-render. diff --git a/tests/phase2/builtins/RULINGS.draft.md b/tests/phase2/builtins/RULINGS.draft.md new file mode 100644 index 000000000..0b9e49ad1 --- /dev/null +++ b/tests/phase2/builtins/RULINGS.draft.md @@ -0,0 +1,42 @@ +# R1.6 bar: orchestrator rulings (DRAFT, to be finalized after the Astra bar critic's readings) + +Each ruling settles one of BAR.md's QUESTIONS. A ruling that changes a row (its `.branch`, a probe line, or a column) is applied in R1.6a's first commit, and recorded in `tests/phase2/builtins_changes.md` with this text. + +## Vacuity exemptions: a `closed_in` column +- **Q-HIST** (`abs`, `json_build`, `json_encode`, `sort`, `sort_by`) and **Q-ALREADY** (the six `bit_*`, `chr`, `dispatch`) are LOSS or int-requiring rows whose loss closed BEFORE R1.6. They are R1.2/R1.3 for Q-HIST, R1.5 for the `bit_*` six, and main before Phase 2 for `chr`/`dispatch`. +- RULING: `builtins.tsv` gains a `closed_in` column naming the round, or `main`. +- The vacuity check (`changes: yes` for every LOSS, int-requiring and Q7 row) applies to every row WITHOUT `closed_in`. A row WITH it must have `.pre == .branch`: the closure is checked, not declared. Its probe still pins exactness above 2^53, so a regression goes red. +- Q-GEN-1767 (`audio_sine`, `audio_saw`, `audio_square`, `audio_noise`; refused since #1767) gets `closed_in: main`. + +## Semantics +- **Q-R17:** agreed. `store_put`, `db_execute`, `db_query_json` and `store_update` change at R1.6 through their DECODERS. The writers' float-digit loss is R1.7's (N14, #1762), and no probe pins the known-defect output. +- **Q-DB253:** agreed. With int columns decoded as ints, int2/int4/int8/oid columns beyond 2^53 return the exact int, and `db_check_exact_int`'s raise is deleted in the same commit, since its reason ("a double cannot represent it") is gone. +- **Q-HEX:** agreed. An int prints its exact uppercase digits (`hex of 9007199254740993` is `20000000000001`). Negatives still raise, and a float follows N5. +- **Q-SIGNEXT:** agreed. The result is an int. `bits <= 0` or `bits > 32` behaviour is unchanged (not part of R1.6). +- **Q-STREAMOPEN:** agreed. N5 comes first, so a fractional count raises `value`. An integral count outside the cap still raises `limit`. BUILTINS.md changes in the same commit. +- **Q-FLAGKIND:** 1/0 status numbers (`record_history`'s previous flag, `task_detach`'s result, `audio_volume`'s and `audio_stop`'s answers, a `spawn` handle's `done`) become INTS. Each probe adds `num_type` to pin the kind. +- **Q-SANDBOX:** agreed. A fractional limit is the host's argument, so it raises a catchable `value`. +- **Q-RECVTIMEOUT:** the magnitude rule (Q7). Round, then raise `value` when the C call cannot honour the value. Today's silent one-year cap is removed if the C call can honour longer; otherwise that cap is the C limit and raises past it. No silent clamp. +- **Q-WINDOWS:** agreed. Q1 is general: every offset, index and count follows N5. +- **Q-PCM16OUT:** agreed. The bounded clamp is unchanged by design, and the row changes through its window. The Q7 class is still exercised. +- **Q-NETSEND-R26:** agreed with the bar's reading. +- **Q-HTTPPORT:** a port is N5, then 0..65535 (`value` outside it). Port 0 (ephemeral) is valid. +- **Q-TEXT-SCALE:** agreed. `gfx_text`'s scale is geometry (its §6 note). +- **Not exercised, now settled:** + - the `add`/`subtract`/`multiply` LIST arms are elementwise through the scalar arm (ints stay ints, and (A) overflow raises). A probe line is added; + - `sum of []` is the int `0`; + - `min`/`max` ties between an int and a float keep the FIRST argument, its kind included (Lua 5.4: `math.max(1, 1.0)` is `1`); + - `num of "-9223372036854775808"` is the int INT64_MIN. The string grammar includes the sign, unlike the lexer's unary minus; Lua agrees; + - integer text beyond int64 in `scan_ints`/`scan_int_tokens` raises `value` naming the token, because an integer scanner never returns a float; + - inflate's output bytes are ints (Q1), and a probe line pins the kind; + - `f64_to_bytes` of an int converts to the nearest double first (documented, not pinned). +- **The store float round trip between R1.6 and R1.7** (`3.0` written as `3`, read back as an int) is a known TRANSITIONAL state inside the unreleased branch. The R1.6f commit says so, and R1.7's `N14.store-exact-digits` row closes it. + +## Witnesses +- **Q-CAP:** capability rows carry a `cap` column (`http`, `net`, `model`, `zlib` or `db`). The tool runs a capability row only against a binary built with that capability, and CI checks them in the lanes that have it: the server lane (`make server`: http, net, model), the zlib lane, and the db lane with its postgres:16 service. + - Their `.pre`/`.tag` are re-recorded on capability builds of the same commits (`make server` and `make zlib` locally). The db rows are recorded where PostgreSQL exists; the R1.6a builder reports the mechanism. +- **Q-CLOCK:** agreed. `monotonic_ns` changes through its kind (float to int). The digit loss above 2^53 ns (104 days of uptime) is a stated residual. +- **Q-AUDIO-HIDDEN:** `audio_stream_push` and `audio_music_volume` get `witness: none (mixer-internal)`, a VISIBLE exemption from the changes rule. Their wiring is reviewed by reading in R1.6e, and the reader itself is covered by `gate.N5.q7-readers`. +- **Q-GFXPOLL:** R1.6e adds a line to `tests/ui_sdl_input.c` (`make ui-sdl-input-gfx`, #1263) printing `num_type` of the event's `x`, `y`, `scancode` and `button`, which gives a real int witness. The table row stays null-only, with that cross-reference. +- **Unreachable** (`proc_read_buf`'s float bytes; `http_post`'s header digits): stated residuals. +- **Q-GFX-ENV:** agreed. The gate runs where the suite's gfx and audio sections run (dummy drivers, xvfb). diff --git a/tests/phase2/builtins/RULINGS.md b/tests/phase2/builtins/RULINGS.md new file mode 100644 index 000000000..271edef7f --- /dev/null +++ b/tests/phase2/builtins/RULINGS.md @@ -0,0 +1,181 @@ +# R1.6 bar: orchestrator rulings (FINAL, 2026-10-20) + +Inputs: +- the bar author's QUESTIONS (BAR.md); +- the blind bar critic's report (`/home/jon/src/wt/briefs/out/r1-6-bar-critic/REPORT.md`, verdict DEFECTIVE). The critic derived all 36 LOSS rows and 88 more independently, found no wrong scalar expectation, and found eight witness defects (F1–F8). + +Every ruling below is applied to the bar BEFORE R1.6a commits it, and each changed row is logged in BAR.md's Corrections log with the ruling's id. + +## A. Table structure +- **A1. `changes` becomes two columns.** + - `changes_vs_pre` is the R1.6 migration delta: `.branch` differs from `.pre`, the R1.5 tip. The vacuity guard uses it. + - `changes_vs_tag` is §9.4's original comparison, recorded as an observation. + - Why: measured on the table, `.branch` differs from `.tag` on 115 of 120 rows, because every probe calls `num_type` and the tag has no `num_type`. A guard against the tag is vacuous by construction (mechanical-gates §196). + - R1.6a amends §9.4 to say this, with the measurement. +- **A2. A `closed_in` column, one reason per row, never one blanket waiver.** It holds the round (or `main #`) that already closed a row's loss or check: + - `abs`, `json_build`, `json_encode`, `sort`, `sort_by`: R1.2/R1.3; + - the six `bit_*`: R1.5; + - `chr`, `dispatch`: main (already exactness-checked); + - the duration caps of `audio_sine`/`saw`/`square`/`noise`: main #1767. + + The vacuity guard (`changes_vs_pre: yes` for every LOSS, int-requiring and Q7 row) applies to rows WITHOUT `closed_in`. A row WITH it must have `.pre == .branch`, so the closure is CHECKED, not declared. Its probe keeps pinning exactness above 2^53. +- **A3. A `profile` column: `release`, `server`, `zlib` or `db`** (F2, Q-CAP). + - `.pre`, `.tag` and `.branch` for a non-release row are recorded and checked on a MATCHED capability build: the same profile at 4ac31e67, at the v0.46.0 tag, and on the branch. + - A capability-unavailable transcript is never a "change". + - The db rows need PostgreSQL. CI's db lane has a postgres:16 service. Record and check them there, or wherever a PostgreSQL exists, and report the mechanism. +- **A4. A `hook` column: the test-only interposition shim** (F1, F4, F5; Q-AUDIO-HIDDEN, Q-CLOCK, Q-GFXPOLL). + - `tests/phase2/observation_hooks.c` is built as an LD_PRELOAD `.so`, starting from the critic's `observation_hooks.c`. It intercepts `SDL_QueueAudio` (capturing the delivered PCM), the mixer gain and volume setters, `clock_gettime` (injecting a deterministic clock) and `SDL_PollEvent` (injecting deterministic events). + - A row with a hook runs with the shim preloaded, and its expectation asserts the boundary payload. + - This SUPERSEDES the draft's "witness: none" waivers: the critic executed every one of these witnesses on the existing binary. +- **A5. A `stdin` fixture column** (the critic's item 13). `proc_read_buf` gets a second case with stdin `Hi`, keeping the `/dev/null` EOF case. **S10-D2 later retires both cases explicitly; see below.** + +## B. Witness rulings (the critic's findings) +- **B1 (F1). `audio_stream_push` asserts the delivered int16 PCM.** `[2.5, -3, 0.5]` gives `[32767, -32767, 16384]`: Q4 rounding of 0.5×32767. Today the result is 16383 (truncation), and an all-silence mutation also passes the current row. + - `audio_play`, `audio_play_loop`, `audio_music_volume` and `audio_volume` assert their gain or volume at the SDL/mixer boundary through the hook, with an out-of-range value that clamps. +- **B2 (F4). `monotonic_ns` with the injected clock 9007199254740993 ns** gives `[9007199254740993, "int", true]`; pre gives `[9007199254740992, "float", false]`. +- **B3 (F5). `gfx_poll` with the injected events** gives `[17, "int", -9, "int"]`, `[4, "int"]` and `[1, "int", 23, "int", 42, "int"]`. The empty-queue `null` case stays as a second probe. +- **B4 (F6). Big store KEYS:** + - `store_update` and `store_delete` act on a record whose `_id` is `"9007199254740993"`, through that numeric key, giving `["update-big-key", true, 2]` and `["delete-big-key", true, 0]`; + - `store_get` adds a 2^53+1 key case. +- **B5 (F7, scope decision). The generators' sample-count conversion (`dur * rate`) stays R2.6**, as design §8 R2.6 lists it in the #1746 residue. So at R1.6, 0.00101 s still gives 44 samples. R1.6 reads `dur` through the magnitude reader for validity only (finite; within what the call can honour). + - The rounding witness (0.00101 s gives 45) goes into R2.6's row list. The design's staging stands, since no silent wrong answer forces it earlier. + - `audio_sweep` changes through its wave selector, as the bar has it. +- **B6 (F8). `http_post` passes headers JSON `{"X-Number": 9007199254740993}` and a STRING body.** On the server profile, a loopback capture fixture asserts that the received header is exactly `9007199254740993`. Today's probe put the number in the body slot, which `ext_http.c:717` ignores. + +## C. Semantic rulings (the bar's questions) +Where the author and the critic agreed, I adopted the answer: +- Q-R17: the decoders change at R1.6, and the writers' float loss is R1.7. +- Q-DB253: exact int64 replaces the 2^53 refusal, and `db_check_exact_int`'s raise is deleted with its reason. +- Q-HEX: exact digits; negatives raise. +- Q-SIGNEXT: an exact int result from the existing operation (`[9007199254741121, 8]` gives `9007199254740865`). +- Q-STREAMOPEN: a fraction raises `value`; an integral count outside 1..10,000,000 keeps `limit`. +- Q-SANDBOX: a catchable `value`. +- Q-WINDOWS: N5 for every offset and count. +- Q-PCM16OUT: unchanged clamp; the change comes through the window. +- Q-TEXT-SCALE: geometry. +- Q-GFX-ENV: the declared environment. +- `num of "-9223372036854775808"`: the int INT64_MIN. +- min/max int-vs-float ties: the FIRST argument wins, kind included (Lua). Add both orders. +- The store `3.0` round trip: a transitional state between R1.6 and R1.7, inside the unreleased branch. The R1.6f commit says so. + +Decided here, because the documents leave each one open: +- **C1. Q-FLAGKIND:** 1/0 status results are INTS: `record_history`'s previous flag, `task_detach`'s result, `audio_volume`'s and `audio_stop`'s answers, and a `spawn` handle's `done` when it is numeric. A flag that is always 0 or 1 has no fractional meaning; the float is the bridge's artefact. Each probe adds `num_type`. +- **C2. `sum of []` is the int 0,** the additive identity over ints (Python's `sum([])` is `0`). No input makes it a float. +- **C3. Integer text beyond int64 in `scan_ints`/`scan_int_tokens` RAISES `value`,** naming the token. An integer scanner that returns a float or wraps is a silent wrong answer (§2), and the scanners' contract is ints. +- **C4. The LIST arms of `add`/`subtract`/`multiply`** are elementwise through the scalar arm, so ints stay ints and (A) overflow raises. Add one int-list case. +- **C5. Q-RECVTIMEOUT:** + - 1e300 raises `value`; + - the silent one-year cap is REMOVED: a timeout the wait call can honour is honoured, and beyond the call's representable limit (the builder determines it from the C types) it raises `value`; + - add a case just past that limit. +- **C6. Q-HTTPPORT:** a port is N5, then 0..65535; 65536 raises `value` (add the case), and 0 (ephemeral) is valid. +- **C7. Q-NETSEND-R26:** keep `"hihi"` (no partial send). Because ext_net.c checks `g_has_error` BEFORE the byte conversion (line 363) and not after it (line 364), the R1.6f byte reader's raise must also stop the send. The probe pins it. +- **C8. inflate:** its output bytes are ints (Q1), asserted on the zlib profile. +- **C9. `f64_to_bytes` of an int converts to the nearest double first** (Q2). Add a 2^53+1 case expecting the bits of 9007199254740992. +- **C10. `proc_read_buf` with stdin `Hi`:** the expectation follows §6's row and Q2 (buffer storage is f64). If they disagree on the element kind, that is a QUESTION back to the orchestrator, not a guess. **S10-D2 below supersedes this retired-builtin witness while preserving its host-byte f64 intent.** + +## S10-D2 — main retirement of `proc_read_buf` stands (2026-10-28) + +The orchestrator ruled on PR #1776 that main #1691 (`5029856a`) stands: +after `proc_spawn` was removed (#1677), no surviving builtin produces a child-pipe +descriptor. Reading inherited fd 0 in this bar is a self-test, not a consumer +under the zero-consumer rule. This explicitly supersedes A5/C10's two +`proc_read_buf` probes, their eight case files, the sole `_stdin/hi` fixture, +and dependent plants P23 and R6. Their prior SHA-256 values and the two +`DELETED` frozen amendments are recorded in `../builtins_changes.md` under +S10-D2, as required by R1.7-D2. The only additional frozen amendment is the recorded #1888 decision below. + +The existing frozen `builtins/read_bytes_buf.eigs` preserves A5/C10's intent: +it writes host bytes `[1, 2, 3]`, then pins `[3, 1.0, "float"]` from the read +buffer and `[3, "float"]` for its integral-float cap path and `d[2]` element. +These source-bound observations cover f64 typing of host-byte buffer elements; +the literal identity of `Hi` was not the typing property. Its source and +expectation remain byte-identical. A one-time real scalar-buffer-read typing +fault must make this surviving probe red; execution is recorded in sync10 +evidence, not inferred from this note. + +### S10-D2 sibling decision: #1888 nearest OTHER active entity + +[The orchestrator's #1888 comment 6402855103](https://github.com/InauguralSystems/EigenScript/issues/1888#issuecomment-6402855103) +rules that `nearest_in_range_all` returns each entity's nearest OTHER active +entity, excludes itself and expressly assigns the next main-to-phase2 sync. +Main #1888/#2010 fixes that behavior. S10-D2 step 3 therefore replaces only +the old incidental index `1` with `0` in `nearest_in_range_all.branch`, +retaining its unchanged source/scenario and int/float kind witnesses. Exact +bank source and before/after branch hashes are in `../builtins_changes.md`. +This authorized semantic amendment is separate from N6's text-only allowance; +every other frozen expectation remains unchanged until the explicit S10-D3 diagnostic amendment below. + +## S10-D3 — main read_text cap diagnostic stands (2026-10-29) + +The PR #1776 orchestrator ruling authorizes ONLY the over-cap diagnostic line in `builtins/read_text.branch`. Main `e0d491598ee19108802bf25c99c5b59d23039117` closes [#2076](https://github.com/InauguralSystems/EigenScript/issues/2076), whose Done-when requires "the same raise message naming the escape" for `read_text`, `read_bytes` and `read_bytes_buf`, from one shared cap helper. EigenMiniSat#131 supplied the consumer: competition instances of14–21MB were refused. The bar case is a builtin self-test, not a separate Phase2 numeric rule. + +The diagnostic appends ` — pass read_text of [path, max_bytes] (hard ceiling 536870912) to raise it`. All other lines, sizes, kinds, results, exit status, source and .pre/.tag stay unchanged. The exact before/after hashes are recorded in `../builtins_changes.md`; one-time bank census and bank/main/merged observations are in sync10 evidence. This explicit diagnostic amendment is additional to S10-D2's retirements and #1888 amendment. + +For the rest of this sync, a frozen diagnostic/error-text amendment needs ALL three: only diagnostic text changes (no value, kind, status, line number or numeric rendering); the named main commit closed an issue whose Done-when/discussion requested that text (quoted for each case); and Phase2 has no own rule about that text. Each amendment gets its own ledger/RULINGS entry with commit and closed issue. Any case failing a condition remains a STOP; collect all such cases in one record. + +## S10-D3 one-time corpus diagnostic classifications + +These are individually reviewed corpus observations; none rewrites another frozen builtin case. The only additional frozen amendment beyond S10-D2 is read_text.branch above. For each path below, repeat statuses, stdout, diagnostic message, numeric rendering and source line/frame numbers are unchanged; only a source path is inserted in the header/frame. Closed [#1651](https://github.com/InauguralSystems/EigenScript/issues/1651) requires: "The uncaught-error header and every traceback frame carry a path". Main `0f6fddd43bd80dff9ad24e414099fd27f20d59a0` (#2004) implements it. Phase2 has no separate rule for this header text. Exact per-row byte evidence and line checks are retained in sync10/S10-D3-corpus-headers.json; this is the one-time merge sweep under PRINCIPLES §5. + +| Corpus path | Main diagnostic contract | +|---|---| +| `tests/bool_fuzz_gen.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/embed_roads/eigs_modules/entrywrapper/entrywrapper.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/embed_roads/entry.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/embed_roads/helper/functions.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/embed_roads/helper/peer.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/embed_roads/import_entry.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/gfx_asan_corpus/06_rejected.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/line_after_callback_halt.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/line_after_return_fn.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/line_after_return_module.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/line_after_return_null.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/loader_mt_modules/lm_cyc_a.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/loader_mt_modules/lm_cyc_b.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/eval_loaded/caller.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/hardlinks/b/child.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/hardlinks/b/entry.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/no_project/cwd_only.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/no_project/parent.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/observer/assets/observer/target.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/roads/assets/observer/entry.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_deadlock.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_deadlock_worker_try.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detach_dead.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detach_dead_nested.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detach_dead_tape.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detach_dead_worker.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detach_observed.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detached_death.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detached_drain.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detached_join.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detached_self.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_detached_tape.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_join_later.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_thread_death.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_thread_detached.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_unjoined_death.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_worker_joined_task.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/task_exit_worker_unjoined_task.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/test_assert_fail.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/test_db.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/test_http.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/test_inflate.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/test_net.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/thread_exit_join_catch.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/thread_exit_join_throw_value.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/thread_exit_join_uncaught.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/thread_exit_unjoined_death.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | +| `tests/ui_native_input.eigs` | `0f6fddd4`, closed #1651: unchanged values/status/line numbers, file-bearing header/frame text only | + +The following four rows add a worker diagnostic that the bank incorrectly suppressed; each keeps its status and stdout. These are ordinary main semantic fixes, separately classified from the path-only rows. Closed [#1808](https://github.com/InauguralSystems/EigenScript/issues/1808) requires "The rule for a task's uncaught-error print depends only on that task's own handlers", including the join-inside-try case. Main `8061e5a0e455f269e6dd0ce9a1185bdd47025f1a` (#2098) implements that rule. Phase2 has no contradictory numeric or diagnostic rule, and no frozen expectation is amended for these rows. + +| Corpus path | Specific retained behavior | +|---|---| +| `tests/task_exit_detached_try.eigs` | rc1/stdout unchanged; worker excerpt/caret/frame precedes the fatal-boundary header, unaffected by main's parked try | +| `tests/task_exit_join_catch.eigs` | rc0/stdout unchanged; worker trace prints before join re-raises into main's catch | +| `tests/test_tasks.eigs` | rc0/stdout unchanged; crasher worker diagnostic at its unchanged line88 prints before the joining task handles it | + +| `tests/task_exit_other_try.eigs` | rc1 and `main done` stdout unchanged; only the previously suppressed worker diagnostic at its actual line4 is added | + +For `task_exit_other_try.eigs` specifically, closed #1808 Done-when also states: "The program above prints the worker’s error (message and trace) on stderr." Main `8061e5a0e455f269e6dd0ce9a1185bdd47025f1a` implements it. Both repeated statuses/stdout match, no value, kind, existing line number or numeric rendering changes, and Phase2 has no rule of its own about that worker text. This S10-D3 class record is a one-time corpus observation; it changes no frozen expectation. Exact per-arm evidence is in sync10/analysis/cont5-corpus/second-pass-85rows.json. diff --git a/tests/phase2/builtins/builtins.tsv b/tests/phase2/builtins/builtins.tsv new file mode 100644 index 000000000..5dff7c0bc --- /dev/null +++ b/tests/phase2/builtins/builtins.tsv @@ -0,0 +1,310 @@ +builtin file class loss q7 round closed_in profile hook stdin changes_vs_pre changes_vs_tag +abs builtins.c int-preserving LOSS - b R1.2/R1.3 release - - no yes +acos builtins.c float-producing - - c - release - - yes yes +add builtins_tensor.c int-preserving LOSS - b - release - - yes yes +append builtins.c other - - c - release - - no yes +asin builtins.c float-producing - - c - release - - yes yes +assert builtins.c other - - c - release - - no yes +atan builtins.c float-producing - - c - release - - yes yes +atan2 builtins.c float-producing - - c - release - - no yes +audio_capture_open ext_gfx.c int-requiring - - e - release - - yes yes +audio_envelope ext_gfx.c float-producing - magnitude e - release - - yes yes +audio_gain ext_gfx.c float-producing - - e - release - - yes yes +audio_mix ext_gfx.c float-producing - - e - release - - yes yes +audio_music_play ext_gfx.c int-requiring - - e - release - - yes yes +audio_music_volume ext_gfx.c other - bounded e - release mixvol - yes yes +audio_noise ext_gfx.c float-producing - magnitude e main #1767 release - - no yes +audio_open ext_gfx.c int-requiring - - e - release - - yes yes +audio_pause ext_gfx.c int-requiring - - e - release - - yes yes +audio_play ext_gfx.c other - bounded e - release mix - yes yes +audio_play_loop ext_gfx.c int-requiring - bounded e - release mix - yes yes +audio_queue_size ext_gfx.c int-preserving - - e - release - - yes yes +audio_saw ext_gfx.c float-producing - magnitude e main #1767 release - - no yes +audio_sine ext_gfx.c float-producing - magnitude e main #1767 release - - no yes +audio_square ext_gfx.c float-producing - magnitude e main #1767 release - - no yes +audio_stop ext_gfx.c int-requiring - - e - release - - yes yes +audio_stream_open ext_gfx.c int-requiring - - e - release - - yes yes +audio_stream_push ext_gfx.c other - bounded e - release pcm - yes yes +audio_stream_queued ext_gfx.c int-preserving - - e - release - - yes yes +audio_sweep ext_gfx.c float-producing - magnitude e - release - - yes yes +audio_volume ext_gfx.c int-requiring - bounded e - release mix - yes yes +bit_and builtins.c int-requiring - - c R1.5 release - - no yes +bit_not builtins.c int-requiring - - c R1.5 release - - no yes +bit_or builtins.c int-requiring - - c R1.5 release - - no yes +bit_shl builtins.c int-requiring - - c R1.5 release - - no yes +bit_shr builtins.c int-requiring - - c R1.5 release - - no yes +bit_xor builtins.c int-requiring - - c R1.5 release - - no yes +buf_copy builtins_buf.c int-requiring - - d - release - - yes yes +buf_deinterleave builtins_buf.c int-requiring - - d - release - - yes yes +buf_dot builtins_buf.c float-producing - - d - release - - yes yes +buf_fill builtins_buf.c float-producing - - d - release - - yes yes +buf_from_list builtins_buf.c float-producing - - d - release - - yes yes +buf_from_pcm16le builtins_buf.c int-requiring - - d - release - - yes yes +buf_get builtins_buf.c int-requiring - - d - release - - yes yes +buf_len builtins_buf.c int-preserving - - d - release - - yes yes +buf_mix builtins_buf.c float-producing - - d - release - - yes yes +buf_peak builtins_buf.c float-producing - - d - release - - yes yes +buf_resample_linear builtins_buf.c float-producing - - d - release - - yes yes +buf_scale_range builtins_buf.c float-producing - - d - release - - yes yes +buf_set builtins_buf.c int-requiring - - d - release - - yes yes +buf_to_pcm16le builtins_buf.c other - bounded d - release - - yes yes +buffer builtins_buf.c int-requiring - - d - release - - yes yes +build_corpus builtins_host.c int-requiring - - c - release - - yes yes +ceil builtins.c int-preserving LOSS - b - release - - yes yes +channel builtins.c int-preserving - - b - release - - yes yes +channel_closed builtins.c int-requiring - - c - release - - yes yes +char_at builtins.c int-requiring - - c - release - - yes yes +chr builtins.c int-requiring - - c main release - - no no +clock_unix builtins.c float-producing - - c - release - - no yes +close_channel builtins.c int-requiring - - c - release - - yes yes +copy_into builtins.c int-requiring - - c - release - - yes yes +cos builtins.c float-producing - - c - release - - yes yes +db_execute ext_db.c other LOSS - b - db - - yes yes +db_query_json ext_db.c other LOSS - b - db - - yes yes +db_query_value ext_db.c other LOSS - b - db - - yes yes +dispatch builtins.c int-requiring - - c main release - - no no +divide builtins_tensor.c float-producing - - d - release - - yes yes +dot builtins.c float-producing - - c - release - - yes yes +eigen_eval_loss model_infer.c float-producing - - d - server - - yes yes +eigen_generate model_infer.c int-requiring - - d - server - - yes yes +eigen_model_load model_io.c other - - d - server - - no no +exec_capture builtins_host.c other - magnitude c - release - - yes yes +exit builtins.c int-requiring - - c - release - - yes yes +exp builtins_tensor.c float-producing - - d - release - - yes yes +f64_from_bytes builtins_buf.c int-requiring - - d - release - - yes yes +f64_to_bytes builtins_buf.c other - - d - release - - yes yes +fill builtins.c int-requiring - - c - release - - yes yes +floor builtins.c int-preserving LOSS - b - release - - yes yes +fround builtins_int.c float-producing - - g - release - - yes yes +gather builtins_tensor.c int-requiring - - d - release - - yes yes +get_at builtins.c int-requiring - - c - release - - yes yes +get_observer_thresholds builtins.c float-producing - - c - release - - no yes +gfx_circle ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_clear ext_gfx.c other - bounded e - release - - yes yes +gfx_clip ext_gfx.c other - geometry e - release - - yes yes +gfx_delay ext_gfx.c other - magnitude e - release - - yes yes +gfx_fb ext_gfx.c int-requiring - geometry e - release - - yes yes +gfx_line ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_open ext_gfx.c other - magnitude e - release - - yes yes +gfx_point ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_poll ext_gfx.c int-preserving - - e - release events,- - yes yes +gfx_read ext_gfx.c other - geometry e - release - - yes yes +gfx_rect ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_rrect ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_text ext_gfx.c other - geometry,bounded e - release - - yes yes +gfx_text_height ext_gfx.c int-preserving - magnitude e - release - - yes yes +gfx_text_width ext_gfx.c int-preserving - magnitude e - release - - yes yes +gfx_ticks ext_gfx.c int-preserving - - e - release - - yes yes +hex builtins.c int-requiring LOSS - b - release - - yes yes +http_early_bind ext_http.c int-requiring - - f - server - - yes yes +http_post ext_http.c other LOSS - b - server - - yes yes +http_route ext_http.c other - - f - server - - no no +http_route_authed ext_http.c other - - f - server - - no no +http_serve ext_http.c int-requiring - - f - server - - yes yes +http_static ext_http.c other - - f - server - - no no +i16_checked_add builtins_int.c int-requiring - - g - release - - yes yes +i16_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +i16_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +i16_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +i16_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +i16_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +i16_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +i16_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +i16_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +i32_checked_add builtins_int.c int-requiring - - g - release - - yes yes +i32_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +i32_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +i32_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +i32_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +i32_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +i32_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +i32_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +i32_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +i64_checked_add builtins_int.c int-requiring - - g - release - - yes yes +i64_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +i64_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +i64_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +i64_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +i64_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +i64_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +i64_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +i64_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +i8_checked_add builtins_int.c int-requiring - - g - release - - yes yes +i8_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +i8_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +i8_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +i8_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +i8_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +i8_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +i8_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +i8_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +index_of builtins.c int-preserving - - b - release - - yes yes +inflate builtins_buf.c int-requiring - - d - zlib - - yes yes +join builtins.c other - - c - release - - yes yes +json_build builtins.c other LOSS - b R1.7 release - - yes yes +json_decode builtins.c other LOSS - b - release - - yes yes +json_encode builtins.c other LOSS - b R1.7 release - - yes yes +json_path builtins.c other LOSS - b - release - - yes yes +leaky_relu builtins_tensor.c float-producing - - d - release - - yes yes +len builtins.c int-preserving - - b - release - - yes yes +list_contains builtins.c other - - c - release - - no yes +list_index_of builtins.c int-preserving - - b - release - - yes yes +list_insert_at builtins.c int-requiring - - c - release - - yes yes +list_remove_at builtins.c int-requiring - - c - release - - yes yes +list_slice builtins.c int-requiring - - c - release - - yes yes +list_truncate builtins.c int-requiring - - c - release - - yes yes +log builtins_tensor.c float-producing - - d - release - - yes yes +log_softmax builtins_tensor.c float-producing - - d - release - - no yes +matmul builtins_tensor.c float-producing - - d - release - - yes yes +matmul_at builtins_tensor.c float-producing - - d - release - - yes yes +matmul_bt builtins_tensor.c float-producing - - d - release - - yes yes +max builtins.c int-preserving LOSS - b - release - - yes yes +mean builtins_tensor.c float-producing - - d - release - - yes yes +min builtins.c int-preserving LOSS - b - release - - yes yes +model_load_weights model_io.c other - - d - server - - no no +monotonic_ms builtins.c float-producing - - c - release - - no yes +monotonic_ns builtins.c int-preserving LOSS - b - release clock - yes yes +multiply builtins_tensor.c int-preserving LOSS - b - release - - yes yes +native_train_step_builtin model_train.c int-requiring - - d - server - - yes yes +nearest_in_range builtins.c float-producing - - c - release - - yes yes +nearest_in_range_all builtins.c float-producing - - c - release - - yes yes +negative builtins_tensor.c int-preserving LOSS - b - release - - yes yes +net_accept ext_net.c int-requiring - - f - server - - yes yes +net_close ext_net.c int-requiring - - f - server - - yes yes +net_dial ext_net.c int-requiring - - f - server - - yes yes +net_listen ext_net.c int-requiring - - f - server - - yes yes +net_port ext_net.c int-requiring - - f - server - - yes yes +net_recv ext_net.c int-requiring - - f - server - - yes yes +net_send ext_net.c int-requiring - - f - server - - yes yes +norm builtins_tensor.c float-producing - - d - release - - yes yes +num builtins.c other LOSS - b - release - - yes yes +num_copy builtins.c int-preserving LOSS - b - release - - yes yes +num_type builtins.c other - - c - release - - no yes +numerical_grad builtins_tensor.c float-producing - - d - release - - yes yes +numerical_grad_cols builtins_tensor.c float-producing - - d - release - - yes yes +numerical_grad_rows builtins_tensor.c float-producing - - d - release - - yes yes +observe builtins.c other - - c - release - - yes yes +ord builtins.c int-preserving - - b - release - - yes yes +pi builtins.c float-producing - - c - release - - no yes +pow builtins_tensor.c float-producing - - d - release - - yes yes +ppu_render_frame ext_gfx.c int-requiring - - e - release - - yes yes +print builtins.c other - - c - release - - yes yes +random builtins.c float-producing - - c - release - - no yes +random_hex builtins_host.c int-requiring - - c - release - - yes yes +random_int builtins.c int-requiring LOSS - b - release - - yes yes +random_normal builtins_tensor.c float-producing - - d - release - - yes yes +range builtins.c int-preserving - - b - release - - yes yes +read_bytes builtins_host.c int-preserving - - c - release - - yes yes +read_bytes_buf builtins_host.c int-requiring - - c - release - - yes yes +read_text builtins_host.c other - - c - release - - yes yes +record_history builtins.c other - - c - release - - yes yes +recv builtins.c int-requiring - - c - release - - yes yes +recv_timeout builtins.c int-requiring - magnitude c - release - - yes yes +relu builtins_tensor.c float-producing - - d - release - - yes yes +reshape builtins_buf.c int-requiring - - d - release - - yes yes +round builtins.c int-preserving LOSS - b - release - - yes yes +sandbox_run builtins.c int-requiring - - c - release - - yes yes +scan_int_tokens builtins.c int-preserving LOSS - b - release - - yes yes +scan_ints builtins.c int-preserving LOSS - b - release - - yes yes +scatter_add builtins_tensor.c float-producing - - d - release - - yes yes +seed_random builtins.c int-requiring LOSS - b - release - - yes yes +send builtins.c int-requiring - - c - release - - yes yes +set_at builtins.c int-requiring - - c - release - - yes yes +set_exit_code builtins.c int-requiring - - c R1.6-S2 release - - no yes +sgd_update builtins_tensor.c float-producing - - d - release - - yes yes +sgd_update_cols builtins_tensor.c float-producing - - d - release - - yes yes +sgd_update_rows builtins_tensor.c float-producing - - d - release - - yes yes +shape builtins_tensor.c int-preserving - - d - release - - yes yes +shared_delete ext_http.c int-preserving - - f - server - - yes yes +shared_incr ext_http.c int-preserving LOSS - b - server - - yes yes +shared_size ext_http.c int-preserving - - f - server - - yes yes +sign_extend builtins.c int-requiring LOSS - b - release - - yes yes +sin builtins.c float-producing - - c - release - - yes yes +softmax builtins_tensor.c float-producing - - d - release - - no yes +sort builtins.c other LOSS - b R1.2/R1.3 release - - no yes +sort_by builtins.c other LOSS - b R1.2/R1.3 release - - no yes +spawn builtins.c int-preserving - - b - release - - yes yes +sqrt builtins_tensor.c float-producing - - d - release - - yes yes +state_at builtins.c int-requiring - - c - release - - yes yes +store_close ext_store.c int-requiring - - f - release - - yes yes +store_collections ext_store.c int-requiring - - f - release - - yes yes +store_count ext_store.c int-requiring - - f - release - - yes yes +store_delete ext_store.c int-requiring LOSS - b - release - - yes yes +store_drop ext_store.c int-requiring - - f - release - - yes yes +store_get ext_store.c int-requiring LOSS - b - release - - yes yes +store_open ext_store.c int-preserving - - f - release - - yes yes +store_put ext_store.c other LOSS - b - release - - yes yes +store_query ext_store.c other LOSS - b - release - - yes yes +store_update ext_store.c other LOSS - b - release - - yes yes +str builtins.c other - - c - release - - yes yes +str_from_bytes builtins_buf.c int-requiring - - d - release - - yes yes +stream_open builtins_host.c int-requiring - - c - release - - yes yes +stream_write builtins_host.c float-producing - - c - release - - yes yes +substr builtins.c int-requiring - - c - release - - yes yes +subtract builtins_tensor.c int-preserving LOSS - b - release - - yes yes +sum builtins_tensor.c int-preserving LOSS - b - release - - yes yes +tan builtins.c float-producing - - c - release - - yes yes +task_alive builtins.c int-requiring - - c - release - - yes yes +task_detach builtins.c int-requiring - - c - release - - yes yes +task_join builtins.c int-requiring - - c - release - - yes yes +task_kill builtins.c int-requiring - - c - release - - yes yes +task_sched_seed builtins.c int-requiring LOSS - b - release - - yes yes +task_self builtins.c int-preserving - - b - release - - yes yes +task_send builtins.c int-requiring - - c - release - - yes yes +task_sleep builtins.c other - - c - release - - no no +task_spawn builtins.c int-preserving - - b - release - - yes yes +tensor_load builtins_tensor.c float-producing - - d - release - - yes yes +tensor_save builtins_tensor.c float-producing - - d - release - - yes yes +text_builder_append builtins.c other - - c - release - - yes yes +text_builder_append_line builtins.c other - - c - release - - no yes +thread_join builtins.c int-requiring - - c - release - - yes yes +throw builtins.c other - - c - release - - no yes +token_name builtins.c int-requiring - - c - release - - yes yes +try_recv builtins.c int-requiring - - c - release - - yes yes +type builtins.c other - - c - release - - no no +u16_checked_add builtins_int.c int-requiring - - g - release - - yes yes +u16_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +u16_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +u16_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +u16_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +u16_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +u16_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +u16_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +u16_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +u32_checked_add builtins_int.c int-requiring - - g - release - - yes yes +u32_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +u32_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +u32_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +u32_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +u32_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +u32_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +u32_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +u32_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +u64_checked_add builtins_int.c int-requiring - - g - release - - yes yes +u64_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +u64_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +u64_compare builtins_int.c int-requiring - - g - release - - yes yes +u64_div builtins_int.c int-requiring - - g - release - - yes yes +u64_rem builtins_int.c int-requiring - - g - release - - yes yes +u64_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +u64_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +u64_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +u64_to_string builtins_int.c int-requiring - - g - release - - yes yes +u64_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +u64_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +u64_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +u8_checked_add builtins_int.c int-requiring - - g - release - - yes yes +u8_checked_mul builtins_int.c int-requiring - - g - release - - yes yes +u8_checked_sub builtins_int.c int-requiring - - g - release - - yes yes +u8_saturating_add builtins_int.c int-requiring - - g - release - - yes yes +u8_saturating_mul builtins_int.c int-requiring - - g - release - - yes yes +u8_saturating_sub builtins_int.c int-requiring - - g - release - - yes yes +u8_wrapping_add builtins_int.c int-requiring - - g - release - - yes yes +u8_wrapping_mul builtins_int.c int-requiring - - g - release - - yes yes +u8_wrapping_sub builtins_int.c int-requiring - - g - release - - yes yes +usleep builtins.c other - magnitude c - release - - yes yes +vm_run_bytecode builtins.c int-requiring - - c - release - - yes yes +write builtins.c other - - c - release - - yes yes +write_bytes builtins_host.c int-requiring - - c - release - - yes yes +zeros builtins_tensor.c int-requiring - - d - release - - yes yes +zeros_like builtins_tensor.c float-producing - - d - release - - yes yes diff --git a/tests/phase2/builtins/builtins/_profiles/db.eigs b/tests/phase2/builtins/builtins/_profiles/db.eigs new file mode 100644 index 000000000..a7f7f30bb --- /dev/null +++ b/tests/phase2/builtins/builtins/_profiles/db.eigs @@ -0,0 +1,7 @@ +# profile witness: server-db = server + DB compiled in, with DATABASE_URL naming a live PostgreSQL. +try: + c is db_connect of null + if (db_query_value of "SELECT 1") == 1: + print of "profile-ok" +catch e: + print of ["db-unavailable", e.message] diff --git a/tests/phase2/builtins/builtins/_profiles/server.eigs b/tests/phase2/builtins/builtins/_profiles/server.eigs new file mode 100644 index 000000000..1695ab9c1 --- /dev/null +++ b/tests/phase2/builtins/builtins/_profiles/server.eigs @@ -0,0 +1,24 @@ +# profile witness: server = HTTP + NET + MODEL compiled in (Makefile FLAGS_server). +# Prints profile-ok only when no capability call answers "capability unavailable". +define unavailable(e) as: + return contains of [e.message, "capability unavailable"] +m is 0 +try: + shared_size of null +catch e: + if unavailable of e: + m is m + 1 +try: + net_close of (net_listen of 0) +catch e: + if unavailable of e: + m is m + 1 +try: + eigen_model_load of "/nonexistent/r16-profile-witness" +catch e: + if unavailable of e: + m is m + 1 +if m == 0: + print of "profile-ok" +else: + print of ["capabilities-unavailable", m] diff --git a/tests/phase2/builtins/builtins/_profiles/zlib.eigs b/tests/phase2/builtins/builtins/_profiles/zlib.eigs new file mode 100644 index 000000000..dbb9cd6df --- /dev/null +++ b/tests/phase2/builtins/builtins/_profiles/zlib.eigs @@ -0,0 +1,6 @@ +# profile witness: zlib = EIGENSCRIPT_EXT_ZLIB compiled in (Makefile FLAGS_zlib). +try: + if (str_from_bytes of (inflate of [203, 200, 4, 0])) == "hi": + print of "profile-ok" +catch e: + print of ["zlib-unavailable", e.message] diff --git a/tests/phase2/builtins/builtins/abs.branch b/tests/phase2/builtins/builtins/abs.branch new file mode 100644 index 000000000..433e70a6d --- /dev/null +++ b/tests/phase2/builtins/builtins/abs.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[2.5, "float"] +["int64-min", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/abs.eigs b/tests/phase2/builtins/builtins/abs.eigs new file mode 100644 index 000000000..e10d41f55 --- /dev/null +++ b/tests/phase2/builtins/builtins/abs.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: abs (int-preserving, LOSS; Lua math.abs; N4b) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is abs of -9007199254740993 +print of [r, nt of r] +f is abs of -2.5 +print of [f, nt of f] +try: + print of (abs of (-9223372036854775807 - 1)) +catch e: + print of ["int64-min", e.kind] diff --git a/tests/phase2/builtins/builtins/abs.pre b/tests/phase2/builtins/builtins/abs.pre new file mode 100644 index 000000000..433e70a6d --- /dev/null +++ b/tests/phase2/builtins/builtins/abs.pre @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[2.5, "float"] +["int64-min", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/abs.tag b/tests/phase2/builtins/builtins/abs.tag new file mode 100644 index 000000000..c1e90ddcf --- /dev/null +++ b/tests/phase2/builtins/builtins/abs.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +[2.5, "undefined_name"] +9.223372036854776e+18 +rc=0 diff --git a/tests/phase2/builtins/builtins/acos.branch b/tests/phase2/builtins/builtins/acos.branch new file mode 100644 index 000000000..0d016631a --- /dev/null +++ b/tests/phase2/builtins/builtins/acos.branch @@ -0,0 +1,3 @@ +[0.0, "float"] +[1.5707963267948966, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/acos.eigs b/tests/phase2/builtins/builtins/acos.eigs new file mode 100644 index 000000000..f4c2f04c2 --- /dev/null +++ b/tests/phase2/builtins/builtins/acos.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: acos (float-producing; Lua math.acos) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is acos of 1 +print of [a, nt of a] +b is acos of 0 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/acos.pre b/tests/phase2/builtins/builtins/acos.pre new file mode 100644 index 000000000..21e7c33cf --- /dev/null +++ b/tests/phase2/builtins/builtins/acos.pre @@ -0,0 +1,3 @@ +[0, "float"] +[1.5707963267948966, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/acos.tag b/tests/phase2/builtins/builtins/acos.tag new file mode 100644 index 000000000..0bba81cbd --- /dev/null +++ b/tests/phase2/builtins/builtins/acos.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[1.5707963267948966, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/add.branch b/tests/phase2/builtins/builtins/add.branch new file mode 100644 index 000000000..16bdcad70 --- /dev/null +++ b/tests/phase2/builtins/builtins/add.branch @@ -0,0 +1,5 @@ +[9007199254740994, "int"] +["overflow", "value"] +[2.0, "float", "buffer"] +[[3, 9007199254740994], "int", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/add.eigs b/tests/phase2/builtins/builtins/add.eigs new file mode 100644 index 000000000..ceeb84728 --- /dev/null +++ b/tests/phase2/builtins/builtins/add.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: add (int-preserving, LOSS; scalar arm mirrors +, (A); buffer arm float) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is add of [9007199254740993, 1] +print of [r, nt of r] +try: + print of (add of [9223372036854775807, 1]) +catch e: + print of ["overflow", e.kind] +b is buf_from_list of [1, 2] +s is add of [b, 1] +print of [s[0], nt of s[0], type of s] +l is add of [[1, 9007199254740993], [2, 1]] +print of [l, nt of l[0], nt of l[1]] diff --git a/tests/phase2/builtins/builtins/add.pre b/tests/phase2/builtins/builtins/add.pre new file mode 100644 index 000000000..434e38fb5 --- /dev/null +++ b/tests/phase2/builtins/builtins/add.pre @@ -0,0 +1,5 @@ +[9007199254740992, "float"] +9.223372036854776e+18 +[2, "float", "buffer"] +[[3, 9007199254740992], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/add.tag b/tests/phase2/builtins/builtins/add.tag new file mode 100644 index 000000000..faa383fea --- /dev/null +++ b/tests/phase2/builtins/builtins/add.tag @@ -0,0 +1,5 @@ +[9007199254740992, "undefined_name"] +9.223372036854776e+18 +[2, "undefined_name", "buffer"] +[[3, 9007199254740992], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/append.branch b/tests/phase2/builtins/builtins/append.branch new file mode 100644 index 000000000..b90676864 --- /dev/null +++ b/tests/phase2/builtins/builtins/append.branch @@ -0,0 +1,2 @@ +[[1, 9007199254740993, 2.5], "int", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/append.eigs b/tests/phase2/builtins/builtins/append.eigs new file mode 100644 index 000000000..187cbd5db --- /dev/null +++ b/tests/phase2/builtins/builtins/append.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: append (other; appends any value) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [1] +append of [xs, 9007199254740993] +append of [xs, 2.5] +print of [xs, nt of xs[1], nt of xs[2]] diff --git a/tests/phase2/builtins/builtins/append.pre b/tests/phase2/builtins/builtins/append.pre new file mode 100644 index 000000000..b90676864 --- /dev/null +++ b/tests/phase2/builtins/builtins/append.pre @@ -0,0 +1,2 @@ +[[1, 9007199254740993, 2.5], "int", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/append.tag b/tests/phase2/builtins/builtins/append.tag new file mode 100644 index 000000000..42f29c446 --- /dev/null +++ b/tests/phase2/builtins/builtins/append.tag @@ -0,0 +1,2 @@ +[[1, 9007199254740992, 2.5], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/asin.branch b/tests/phase2/builtins/builtins/asin.branch new file mode 100644 index 000000000..0d016631a --- /dev/null +++ b/tests/phase2/builtins/builtins/asin.branch @@ -0,0 +1,3 @@ +[0.0, "float"] +[1.5707963267948966, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/asin.eigs b/tests/phase2/builtins/builtins/asin.eigs new file mode 100644 index 000000000..fca08986c --- /dev/null +++ b/tests/phase2/builtins/builtins/asin.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: asin (float-producing; Lua math.asin) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is asin of 0 +print of [a, nt of a] +b is asin of 1 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/asin.pre b/tests/phase2/builtins/builtins/asin.pre new file mode 100644 index 000000000..21e7c33cf --- /dev/null +++ b/tests/phase2/builtins/builtins/asin.pre @@ -0,0 +1,3 @@ +[0, "float"] +[1.5707963267948966, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/asin.tag b/tests/phase2/builtins/builtins/asin.tag new file mode 100644 index 000000000..0bba81cbd --- /dev/null +++ b/tests/phase2/builtins/builtins/asin.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[1.5707963267948966, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/assert.branch b/tests/phase2/builtins/builtins/assert.branch new file mode 100644 index 000000000..92d30834f --- /dev/null +++ b/tests/phase2/builtins/builtins/assert.branch @@ -0,0 +1,3 @@ +["assert", "ASSERT FAIL: 9007199254740993"] +["assert", "ASSERT FAIL: 2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/assert.eigs b/tests/phase2/builtins/builtins/assert.eigs new file mode 100644 index 000000000..0f872e2e1 --- /dev/null +++ b/tests/phase2/builtins/builtins/assert.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: assert (other; message via value_to_string (N6)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +assert of [true, "unused"] +try: + assert of [false, 9007199254740993] +catch e: + print of [e.kind, e.message] +try: + assert of [false, 2.5] +catch e: + print of [e.kind, e.message] diff --git a/tests/phase2/builtins/builtins/assert.pre b/tests/phase2/builtins/builtins/assert.pre new file mode 100644 index 000000000..92d30834f --- /dev/null +++ b/tests/phase2/builtins/builtins/assert.pre @@ -0,0 +1,3 @@ +["assert", "ASSERT FAIL: 9007199254740993"] +["assert", "ASSERT FAIL: 2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/assert.tag b/tests/phase2/builtins/builtins/assert.tag new file mode 100644 index 000000000..eddd8ec82 --- /dev/null +++ b/tests/phase2/builtins/builtins/assert.tag @@ -0,0 +1,3 @@ +["assert", "ASSERT FAIL: 9007199254740992"] +["assert", "ASSERT FAIL: 2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan.branch b/tests/phase2/builtins/builtins/atan.branch new file mode 100644 index 000000000..db6afadc4 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan.branch @@ -0,0 +1,3 @@ +[0.7853981633974483, "float"] +[0.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan.eigs b/tests/phase2/builtins/builtins/atan.eigs new file mode 100644 index 000000000..c704286c1 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: atan (float-producing; Lua math.atan) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is atan of 1 +print of [a, nt of a] +b is atan of 0 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/atan.pre b/tests/phase2/builtins/builtins/atan.pre new file mode 100644 index 000000000..6954a46e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan.pre @@ -0,0 +1,3 @@ +[0.7853981633974483, "float"] +[0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan.tag b/tests/phase2/builtins/builtins/atan.tag new file mode 100644 index 000000000..8b9eeb1fa --- /dev/null +++ b/tests/phase2/builtins/builtins/atan.tag @@ -0,0 +1,3 @@ +[0.7853981633974483, "undefined_name"] +[0, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan2.branch b/tests/phase2/builtins/builtins/atan2.branch new file mode 100644 index 000000000..a6bbc5c57 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan2.branch @@ -0,0 +1,3 @@ +[0.7853981633974483, "float"] +[3.141592653589793, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan2.eigs b/tests/phase2/builtins/builtins/atan2.eigs new file mode 100644 index 000000000..ddadc2b60 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan2.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: atan2 (float-producing; Lua math.atan(y, x)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is atan2 of [1, 1] +print of [a, nt of a] +b is atan2 of [0, -1] +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/atan2.pre b/tests/phase2/builtins/builtins/atan2.pre new file mode 100644 index 000000000..a6bbc5c57 --- /dev/null +++ b/tests/phase2/builtins/builtins/atan2.pre @@ -0,0 +1,3 @@ +[0.7853981633974483, "float"] +[3.141592653589793, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/atan2.tag b/tests/phase2/builtins/builtins/atan2.tag new file mode 100644 index 000000000..8cf47a90a --- /dev/null +++ b/tests/phase2/builtins/builtins/atan2.tag @@ -0,0 +1,3 @@ +[0.7853981633974483, "undefined_name"] +[3.141592653589793, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_capture_open.branch b/tests/phase2/builtins/builtins/audio_capture_open.branch new file mode 100644 index 000000000..34c851a71 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_capture_open.branch @@ -0,0 +1,3 @@ +["freq-fraction", "value"] +[true, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_capture_open.eigs b/tests/phase2/builtins/builtins/audio_capture_open.eigs new file mode 100644 index 000000000..7ea2882ca --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_capture_open.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: audio_capture_open (int-requiring; freq and channels are counts: N5, then the C int range (Q1); returns a device id); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of ["fraction-answer", audio_capture_open of [44100.5, 1]] +catch e: + print of ["freq-fraction", e.kind] +c is audio_capture_open of [44100.0, 1.0] +print of [c >= 0, nt of c] +audio_capture_close of null diff --git a/tests/phase2/builtins/builtins/audio_capture_open.pre b/tests/phase2/builtins/builtins/audio_capture_open.pre new file mode 100644 index 000000000..fbe060369 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_capture_open.pre @@ -0,0 +1,3 @@ +["fraction-answer", 2] +[true, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_capture_open.tag b/tests/phase2/builtins/builtins/audio_capture_open.tag new file mode 100644 index 000000000..6db37b293 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_capture_open.tag @@ -0,0 +1,3 @@ +["fraction-answer", 2] +[true, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_envelope.branch b/tests/phase2/builtins/builtins/audio_envelope.branch new file mode 100644 index 000000000..b80449765 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_envelope.branch @@ -0,0 +1,4 @@ +[[0.5, 0.5, 0.5, 0.5], "float"] +["attack-magnitude", "value"] +["release-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_envelope.eigs b/tests/phase2/builtins/builtins/audio_envelope.eigs new file mode 100644 index 000000000..f9842a824 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_envelope.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: audio_envelope (float-producing, Q7 magnitude; attack and release are continuous durations: a sample count the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_envelope of [[1, 1, 1, 1], 0, 0, 0.5, 0] +print of [s, nt of s[0]] +try: + print of ["huge-attack-answer", len of (audio_envelope of [[1, 1], 1e300, 0, 0.5, 0])] +catch e: + print of ["attack-magnitude", e.kind] +try: + print of ["huge-release-answer", len of (audio_envelope of [[1, 1], 0, 0, 0.5, 1e300])] +catch e: + print of ["release-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_envelope.pre b/tests/phase2/builtins/builtins/audio_envelope.pre new file mode 100644 index 000000000..d3d87d803 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_envelope.pre @@ -0,0 +1,4 @@ +[[0.5, 0.5, 0.5, 0.5], "float"] +["huge-attack-answer", 2] +["huge-release-answer", 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_envelope.tag b/tests/phase2/builtins/builtins/audio_envelope.tag new file mode 100644 index 000000000..86fff701d --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_envelope.tag @@ -0,0 +1,4 @@ +[[0.5, 0.5, 0.5, 0.5], "undefined_name"] +["huge-attack-answer", 2] +["huge-release-answer", 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_gain.branch b/tests/phase2/builtins/builtins/audio_gain.branch new file mode 100644 index 000000000..baecc92c5 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_gain.branch @@ -0,0 +1,2 @@ +[[1.0, -1.0, 0.5], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_gain.eigs b/tests/phase2/builtins/builtins/audio_gain.eigs new file mode 100644 index 000000000..14247ec06 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_gain.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: audio_gain (float-producing; float samples) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +g is audio_gain of [[1, -0.5, 0.25], 2] +print of [g, nt of g[2]] diff --git a/tests/phase2/builtins/builtins/audio_gain.pre b/tests/phase2/builtins/builtins/audio_gain.pre new file mode 100644 index 000000000..d4a0a418d --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_gain.pre @@ -0,0 +1,2 @@ +[[1, -1, 0.5], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_gain.tag b/tests/phase2/builtins/builtins/audio_gain.tag new file mode 100644 index 000000000..2abaea804 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_gain.tag @@ -0,0 +1,2 @@ +[[1, -1, 0.5], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_mix.branch b/tests/phase2/builtins/builtins/audio_mix.branch new file mode 100644 index 000000000..e3efa6d35 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_mix.branch @@ -0,0 +1,2 @@ +[[0.75, 1.0], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_mix.eigs b/tests/phase2/builtins/builtins/audio_mix.eigs new file mode 100644 index 000000000..5cc82b593 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_mix.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: audio_mix (float-producing; float samples) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +m is audio_mix of [[0.5, 1], [0.25, 0.5]] +print of [m, nt of m[0]] diff --git a/tests/phase2/builtins/builtins/audio_mix.pre b/tests/phase2/builtins/builtins/audio_mix.pre new file mode 100644 index 000000000..bcf573a26 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_mix.pre @@ -0,0 +1,2 @@ +[[0.75, 1], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_mix.tag b/tests/phase2/builtins/builtins/audio_mix.tag new file mode 100644 index 000000000..51e267a9d --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_mix.tag @@ -0,0 +1,2 @@ +[[0.75, 1], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_play.branch b/tests/phase2/builtins/builtins/audio_music_play.branch new file mode 100644 index 000000000..e7e7b7bbc --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_play.branch @@ -0,0 +1,3 @@ +["loops-fraction", "value"] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_play.eigs b/tests/phase2/builtins/builtins/audio_music_play.eigs new file mode 100644 index 000000000..d74288bce --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_play.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: audio_music_play (int-requiring; loop count: N5, then the C int range (Q1); a 4-sample WAV the probe writes); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +wav is p + "/m.wav" +write_bytes of [wav, [82, 73, 70, 70, 44, 0, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0, 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] +try: + print of ["fraction-answer", audio_music_play of [wav, 2.5]] +catch e: + print of ["loops-fraction", e.kind] +print of (audio_music_play of [wav, 2.0]) diff --git a/tests/phase2/builtins/builtins/audio_music_play.pre b/tests/phase2/builtins/builtins/audio_music_play.pre new file mode 100644 index 000000000..9b0df76fd --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_play.pre @@ -0,0 +1,3 @@ +["fraction-answer", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_play.tag b/tests/phase2/builtins/builtins/audio_music_play.tag new file mode 100644 index 000000000..9b0df76fd --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_play.tag @@ -0,0 +1,3 @@ +["fraction-answer", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_volume.branch b/tests/phase2/builtins/builtins/audio_music_volume.branch new file mode 100644 index 000000000..aa49dd806 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_volume.branch @@ -0,0 +1,7 @@ +true +[null, null, null] +hook-loaded mixvol +Mix_VolumeMusic 128 +Mix_VolumeMusic 0 +Mix_VolumeMusic 65 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_volume.eigs b/tests/phase2/builtins/builtins/audio_music_volume.eigs new file mode 100644 index 000000000..665cf4582 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_volume.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: audio_music_volume (other, Q7 bounded; the volume clamps to SDL_mixer's 0..128, then rounds; HOOK mixvol: the volume reaching Mix_VolumeMusic (ruling B1)); phase2-exercises: {"q7":["bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +log is env_get of "R16_HOOK_LOG" +p is env_get of "EIGS_PROBE_TMP" +wav is p + "/m.wav" +write_bytes of [wav, [82, 73, 70, 70, 44, 0, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0, 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] +print of (audio_music_play of [wav, 1]) +print of [audio_music_volume of 300, audio_music_volume of -20, audio_music_volume of 64.5] +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/audio_music_volume.pre b/tests/phase2/builtins/builtins/audio_music_volume.pre new file mode 100644 index 000000000..762c80ff7 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_volume.pre @@ -0,0 +1,7 @@ +true +[null, null, null] +hook-loaded mixvol +Mix_VolumeMusic 128 +Mix_VolumeMusic 0 +Mix_VolumeMusic 64 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_music_volume.tag b/tests/phase2/builtins/builtins/audio_music_volume.tag new file mode 100644 index 000000000..762c80ff7 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_music_volume.tag @@ -0,0 +1,7 @@ +true +[null, null, null] +hook-loaded mixvol +Mix_VolumeMusic 128 +Mix_VolumeMusic 0 +Mix_VolumeMusic 64 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_noise.branch b/tests/phase2/builtins/builtins/audio_noise.branch new file mode 100644 index 000000000..bc88264ca --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_noise.branch @@ -0,0 +1,3 @@ +[44, "float", true] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_noise.eigs b/tests/phase2/builtins/builtins/audio_noise.eigs new file mode 100644 index 000000000..f89295e35 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_noise.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: audio_noise (float-producing, Q7 magnitude; float samples (seeded); a sample count the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +seed_random of 7 +s is audio_noise of [0.001, 0.5] +print of [len of s, nt of s[0], (abs of s[0]) <= 0.5] +try: + print of ["huge-duration-answer", len of (audio_noise of [1e300, 0.5])] +catch e: + print of ["duration-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_noise.pre b/tests/phase2/builtins/builtins/audio_noise.pre new file mode 100644 index 000000000..bc88264ca --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_noise.pre @@ -0,0 +1,3 @@ +[44, "float", true] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_noise.tag b/tests/phase2/builtins/builtins/audio_noise.tag new file mode 100644 index 000000000..49cdb38a2 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_noise.tag @@ -0,0 +1,3 @@ +[44, "undefined_name", true] +["huge-duration-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_open.branch b/tests/phase2/builtins/builtins/audio_open.branch new file mode 100644 index 000000000..4cb1a9b2a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_open.branch @@ -0,0 +1,4 @@ +["freq-fraction", "value"] +["channels-fraction", "value"] +[true, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_open.eigs b/tests/phase2/builtins/builtins/audio_open.eigs new file mode 100644 index 000000000..d80f99ade --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_open.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: audio_open (int-requiring; freq and channels are counts: N5, then the C int range (Q1); returns a device id); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of ["fraction-answer", audio_open of [44100.5, 1]] +catch e: + print of ["freq-fraction", e.kind] +try: + print of ["fraction-answer", audio_open of [44100, 1.5]] +catch e: + print of ["channels-fraction", e.kind] +d is audio_open of [44100.0, 1.0] +print of [d >= 2, nt of d] +audio_close of null diff --git a/tests/phase2/builtins/builtins/audio_open.pre b/tests/phase2/builtins/builtins/audio_open.pre new file mode 100644 index 000000000..42a4d2bc1 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_open.pre @@ -0,0 +1,4 @@ +["fraction-answer", 2] +["fraction-answer", 0] +[false, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_open.tag b/tests/phase2/builtins/builtins/audio_open.tag new file mode 100644 index 000000000..d664c4c83 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_open.tag @@ -0,0 +1,4 @@ +["fraction-answer", 2] +["fraction-answer", 0] +[false, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_pause.branch b/tests/phase2/builtins/builtins/audio_pause.branch new file mode 100644 index 000000000..7dc73c3c0 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_pause.branch @@ -0,0 +1,3 @@ +["flag-fraction", "value"] +[null, null] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_pause.eigs b/tests/phase2/builtins/builtins/audio_pause.eigs new file mode 100644 index 000000000..b640ff593 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_pause.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: audio_pause (int-requiring; the pause flag is discrete: N5 (Q1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is audio_open of [44100, 1] +try: + audio_pause of 0.5 + print of "fraction accepted" +catch e: + print of ["flag-fraction", e.kind] +print of [audio_pause of 1.0, audio_pause of 0] +audio_close of null diff --git a/tests/phase2/builtins/builtins/audio_pause.pre b/tests/phase2/builtins/builtins/audio_pause.pre new file mode 100644 index 000000000..4545a45f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_pause.pre @@ -0,0 +1,3 @@ +fraction accepted +[null, null] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_pause.tag b/tests/phase2/builtins/builtins/audio_pause.tag new file mode 100644 index 000000000..4545a45f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_pause.tag @@ -0,0 +1,3 @@ +fraction accepted +[null, null] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play.branch b/tests/phase2/builtins/builtins/audio_play.branch new file mode 100644 index 000000000..efa020f75 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play.branch @@ -0,0 +1,5 @@ +[1, "int"] +["mixed", true] +hook-loaded mix +mix: 32767 -32767 16384 0 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play.eigs b/tests/phase2/builtins/builtins/audio_play.eigs new file mode 100644 index 000000000..1dbea2593 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play.eigs @@ -0,0 +1,23 @@ +# R1.6 behaviour-table probe: audio_play (other, Q7 bounded; samples clamp to [-1, 1], then round to int16; returns a channel number; HOOK mix: the delivered mixer PCM (ruling B1)); phase2-exercises: {"q7":["bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define wait_for(path, want) as: + local i is 0 + loop while i < 200: + if contains of [read_text of path, want]: + return true + usleep of 20000 + i is i + 1 + return false +log is env_get of "R16_HOOK_LOG" +d is audio_open of [44100, 1] +audio_pause of 1 +c is audio_play of [2.5, -3, 0.5, 0] +print of [c, nt of c] +audio_pause of 0 +print of ["mixed", wait_for of [log, "mix:"]] +audio_close of null +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/audio_play.pre b/tests/phase2/builtins/builtins/audio_play.pre new file mode 100644 index 000000000..90c3bd66a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play.pre @@ -0,0 +1,5 @@ +[1, "float"] +["mixed", true] +hook-loaded mix +mix: 32767 -32767 16383 0 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play.tag b/tests/phase2/builtins/builtins/audio_play.tag new file mode 100644 index 000000000..acecb2fdd --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play.tag @@ -0,0 +1,5 @@ +[1, "undefined_name"] +["mixed", true] +hook-loaded mix +mix: 32767 -32767 16383 0 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play_loop.branch b/tests/phase2/builtins/builtins/audio_play_loop.branch new file mode 100644 index 000000000..d2fd00ff7 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play_loop.branch @@ -0,0 +1,6 @@ +["loops-fraction", "value"] +[1, "int"] +["mixed", true] +hook-loaded mix +mix: 16384 -32767 16384 -32767 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play_loop.eigs b/tests/phase2/builtins/builtins/audio_play_loop.eigs new file mode 100644 index 000000000..e885e4892 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play_loop.eigs @@ -0,0 +1,27 @@ +# R1.6 behaviour-table probe: audio_play_loop (int-requiring, Q7 bounded; loop count by N5; samples clamp and round to int16; returns a channel; HOOK mix: the delivered mixer PCM (ruling B1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"q7":["bounded"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define wait_for(path, want) as: + local i is 0 + loop while i < 200: + if contains of [read_text of path, want]: + return true + usleep of 20000 + i is i + 1 + return false +log is env_get of "R16_HOOK_LOG" +d is audio_open of [44100, 1] +audio_pause of 1 +try: + print of ["fraction-answer", audio_play_loop of [[0.1, 0.2], 2.5]] +catch e: + print of ["loops-fraction", e.kind] +c is audio_play_loop of [[0.5, -3], 2.0] +print of [c, nt of c] +audio_pause of 0 +print of ["mixed", wait_for of [log, "mix:"]] +audio_close of null +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/audio_play_loop.pre b/tests/phase2/builtins/builtins/audio_play_loop.pre new file mode 100644 index 000000000..87988e32c --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play_loop.pre @@ -0,0 +1,6 @@ +["fraction-answer", 1] +[2, "float"] +["mixed", true] +hook-loaded mix +mix: 19659 -26214 19659 -26214 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_play_loop.tag b/tests/phase2/builtins/builtins/audio_play_loop.tag new file mode 100644 index 000000000..2098949b6 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_play_loop.tag @@ -0,0 +1,6 @@ +["fraction-answer", 1] +[2, "undefined_name"] +["mixed", true] +hook-loaded mix +mix: 19659 -26214 19659 -26214 0 0 0 0 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_queue_size.branch b/tests/phase2/builtins/builtins/audio_queue_size.branch new file mode 100644 index 000000000..a6a4a2e22 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_queue_size.branch @@ -0,0 +1,2 @@ +["int", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_queue_size.eigs b/tests/phase2/builtins/builtins/audio_queue_size.eigs new file mode 100644 index 000000000..111dabac3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_queue_size.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: audio_queue_size (int-preserving; byte count; a kind and a range only (the device drains in real time)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is audio_open of [44100, 1] +c is audio_play of [0.5, 0.5, 0.5, 0.5] +q is audio_queue_size of null +print of [nt of q, q >= 0, q <= 8] +audio_close of null diff --git a/tests/phase2/builtins/builtins/audio_queue_size.pre b/tests/phase2/builtins/builtins/audio_queue_size.pre new file mode 100644 index 000000000..a188fded6 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_queue_size.pre @@ -0,0 +1,2 @@ +["float", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_queue_size.tag b/tests/phase2/builtins/builtins/audio_queue_size.tag new file mode 100644 index 000000000..4bd2350b5 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_queue_size.tag @@ -0,0 +1,2 @@ +["undefined_name", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_saw.branch b/tests/phase2/builtins/builtins/audio_saw.branch new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_saw.branch @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_saw.eigs b/tests/phase2/builtins/builtins/audio_saw.eigs new file mode 100644 index 000000000..b77d2d159 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_saw.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: audio_saw (float-producing, Q7 magnitude; float samples; a sample count the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_saw of [440, 0.001, 0.5] +print of [len of s, nt of s[0], nt of s[43]] +try: + print of ["huge-duration-answer", len of (audio_saw of [440, 1e300, 0.5])] +catch e: + print of ["duration-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_saw.pre b/tests/phase2/builtins/builtins/audio_saw.pre new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_saw.pre @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_saw.tag b/tests/phase2/builtins/builtins/audio_saw.tag new file mode 100644 index 000000000..f52c78f4a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_saw.tag @@ -0,0 +1,3 @@ +[44, "undefined_name", "undefined_name"] +["huge-duration-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sine.branch b/tests/phase2/builtins/builtins/audio_sine.branch new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sine.branch @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sine.eigs b/tests/phase2/builtins/builtins/audio_sine.eigs new file mode 100644 index 000000000..ba29830b3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sine.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: audio_sine (float-producing, Q7 magnitude; float samples; a sample count the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_sine of [440, 0.001, 0.5] +print of [len of s, nt of s[0], nt of s[43]] +try: + print of ["huge-duration-answer", len of (audio_sine of [440, 1e300, 0.5])] +catch e: + print of ["duration-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_sine.pre b/tests/phase2/builtins/builtins/audio_sine.pre new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sine.pre @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sine.tag b/tests/phase2/builtins/builtins/audio_sine.tag new file mode 100644 index 000000000..f52c78f4a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sine.tag @@ -0,0 +1,3 @@ +[44, "undefined_name", "undefined_name"] +["huge-duration-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_square.branch b/tests/phase2/builtins/builtins/audio_square.branch new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_square.branch @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_square.eigs b/tests/phase2/builtins/builtins/audio_square.eigs new file mode 100644 index 000000000..7ff330012 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_square.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: audio_square (float-producing, Q7 magnitude; float samples; a sample count the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_square of [440, 0.001, 0.5] +print of [len of s, nt of s[0], nt of s[43]] +try: + print of ["huge-duration-answer", len of (audio_square of [440, 1e300, 0.5])] +catch e: + print of ["duration-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_square.pre b/tests/phase2/builtins/builtins/audio_square.pre new file mode 100644 index 000000000..58d9dbbe3 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_square.pre @@ -0,0 +1,3 @@ +[44, "float", "float"] +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_square.tag b/tests/phase2/builtins/builtins/audio_square.tag new file mode 100644 index 000000000..f52c78f4a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_square.tag @@ -0,0 +1,3 @@ +[44, "undefined_name", "undefined_name"] +["huge-duration-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stop.branch b/tests/phase2/builtins/builtins/audio_stop.branch new file mode 100644 index 000000000..629267552 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stop.branch @@ -0,0 +1,3 @@ +["channel-fraction", "value"] +[1, "int", 0, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stop.eigs b/tests/phase2/builtins/builtins/audio_stop.eigs new file mode 100644 index 000000000..1dfac60b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stop.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: audio_stop (int-requiring; channel number); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is audio_open of [44100, 1] +c is audio_play_loop of [[0.1, 0.2], -1] +try: + print of ["fraction-answer", audio_stop of (c + 0.5)] +catch e: + print of ["channel-fraction", e.kind] +a is audio_stop of (c + 0.0) +b is audio_stop of c +print of [a, nt of a, b, nt of b] +audio_close of null diff --git a/tests/phase2/builtins/builtins/audio_stop.pre b/tests/phase2/builtins/builtins/audio_stop.pre new file mode 100644 index 000000000..8e997c7c5 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stop.pre @@ -0,0 +1,3 @@ +["fraction-answer", 1] +[0, "float", 0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stop.tag b/tests/phase2/builtins/builtins/audio_stop.tag new file mode 100644 index 000000000..7b256a646 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stop.tag @@ -0,0 +1,3 @@ +["fraction-answer", 1] +[0, "undefined_name", 0, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_open.branch b/tests/phase2/builtins/builtins/audio_stream_open.branch new file mode 100644 index 000000000..34c851a71 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_open.branch @@ -0,0 +1,3 @@ +["freq-fraction", "value"] +[true, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_open.eigs b/tests/phase2/builtins/builtins/audio_stream_open.eigs new file mode 100644 index 000000000..56551bf13 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_open.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: audio_stream_open (int-requiring; freq and channels are counts: N5, then the C int range (Q1); returns a device id); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of ["fraction-answer", audio_stream_open of [44100.5, 1]] +catch e: + print of ["freq-fraction", e.kind] +s is audio_stream_open of [44100.0, 1.0] +print of [s >= 0, nt of s] +audio_stream_close of null diff --git a/tests/phase2/builtins/builtins/audio_stream_open.pre b/tests/phase2/builtins/builtins/audio_stream_open.pre new file mode 100644 index 000000000..fbe060369 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_open.pre @@ -0,0 +1,3 @@ +["fraction-answer", 2] +[true, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_open.tag b/tests/phase2/builtins/builtins/audio_stream_open.tag new file mode 100644 index 000000000..6db37b293 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_open.tag @@ -0,0 +1,3 @@ +["fraction-answer", 2] +[true, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_push.branch b/tests/phase2/builtins/builtins/audio_stream_push.branch new file mode 100644 index 000000000..1db085c07 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_push.branch @@ -0,0 +1,4 @@ +[true, true] +hook-loaded pcm +SDL_QueueAudio 3: 32767 -32767 16384 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_push.eigs b/tests/phase2/builtins/builtins/audio_stream_push.eigs new file mode 100644 index 000000000..8e29f6b51 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_push.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: audio_stream_push (other, Q7 bounded; samples clamp to [-1, 1], then round to int16; HOOK pcm: the delivered SDL_QueueAudio PCM (ruling B1)); phase2-exercises: {"q7":["bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +log is env_get of "R16_HOOK_LOG" +s is audio_stream_open of [44100, 1] +print of [s >= 0, audio_stream_push of [2.5, -3, 0.5]] +audio_stream_close of null +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/audio_stream_push.pre b/tests/phase2/builtins/builtins/audio_stream_push.pre new file mode 100644 index 000000000..ef83cf427 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_push.pre @@ -0,0 +1,4 @@ +[true, true] +hook-loaded pcm +SDL_QueueAudio 3: 32767 -32767 16383 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_push.tag b/tests/phase2/builtins/builtins/audio_stream_push.tag new file mode 100644 index 000000000..ef83cf427 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_push.tag @@ -0,0 +1,4 @@ +[true, true] +hook-loaded pcm +SDL_QueueAudio 3: 32767 -32767 16383 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_queued.branch b/tests/phase2/builtins/builtins/audio_stream_queued.branch new file mode 100644 index 000000000..a6a4a2e22 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_queued.branch @@ -0,0 +1,2 @@ +["int", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_queued.eigs b/tests/phase2/builtins/builtins/audio_stream_queued.eigs new file mode 100644 index 000000000..6ff35f4f6 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_queued.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: audio_stream_queued (int-preserving; sample count; a kind and a range only (the device drains in real time)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_stream_open of [44100, 1] +audio_stream_push of [0.1, 0.2, 0.3] +q is audio_stream_queued of null +print of [nt of q, q >= 0, q <= 3] +audio_stream_close of null diff --git a/tests/phase2/builtins/builtins/audio_stream_queued.pre b/tests/phase2/builtins/builtins/audio_stream_queued.pre new file mode 100644 index 000000000..a188fded6 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_queued.pre @@ -0,0 +1,2 @@ +["float", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_stream_queued.tag b/tests/phase2/builtins/builtins/audio_stream_queued.tag new file mode 100644 index 000000000..4bd2350b5 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_stream_queued.tag @@ -0,0 +1,2 @@ +["undefined_name", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sweep.branch b/tests/phase2/builtins/builtins/audio_sweep.branch new file mode 100644 index 000000000..0a45f5699 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sweep.branch @@ -0,0 +1,5 @@ +[44, "float"] +["wave-fraction", "value"] +44 +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sweep.eigs b/tests/phase2/builtins/builtins/audio_sweep.eigs new file mode 100644 index 000000000..716d2488a --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sweep.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: audio_sweep (float-producing, Q7 magnitude; the duration is continuous; the wave selector is integer-valued (N5, Q1)); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is audio_sweep of [440, 880, 0.001, 0.5, 0] +print of [len of s, nt of s[0]] +try: + print of ["fraction-answer", len of (audio_sweep of [440, 880, 0.001, 0.5, 0.5])] +catch e: + print of ["wave-fraction", e.kind] +print of (len of (audio_sweep of [440, 880, 0.001, 0.5, 1.0])) +try: + print of ["huge-duration-answer", len of (audio_sweep of [440, 880, 1e300, 0.5, 0])] +catch e: + print of ["duration-magnitude", e.kind] diff --git a/tests/phase2/builtins/builtins/audio_sweep.pre b/tests/phase2/builtins/builtins/audio_sweep.pre new file mode 100644 index 000000000..3298a7e56 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sweep.pre @@ -0,0 +1,5 @@ +[44, "float"] +["fraction-answer", 44] +44 +["duration-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_sweep.tag b/tests/phase2/builtins/builtins/audio_sweep.tag new file mode 100644 index 000000000..d1a892c9b --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_sweep.tag @@ -0,0 +1,5 @@ +[44, "undefined_name"] +["fraction-answer", 44] +44 +["huge-duration-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_volume.branch b/tests/phase2/builtins/builtins/audio_volume.branch new file mode 100644 index 000000000..e52c9cbac --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_volume.branch @@ -0,0 +1,6 @@ +["channel-fraction", "value"] +[1, "int"] +["mixed", true] +hook-loaded mix +mix: 13108 -13108 13108 -13108 13108 -13108 13108 -13108 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_volume.eigs b/tests/phase2/builtins/builtins/audio_volume.eigs new file mode 100644 index 000000000..ebb8d7b31 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_volume.eigs @@ -0,0 +1,29 @@ +# R1.6 behaviour-table probe: audio_volume (int-requiring, Q7 bounded; channel index by N5; the volume clamps to the mixer's range [0, 4]; HOOK mix: the delivered mixer PCM (rulings B1, C1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"q7":["bounded"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define wait_for(path, want) as: + local i is 0 + loop while i < 200: + if contains of [read_text of path, want]: + return true + usleep of 20000 + i is i + 1 + return false +log is env_get of "R16_HOOK_LOG" +d is audio_open of [44100, 1] +audio_pause of 1 +c is audio_play_loop of [[0.1, -0.1], -1] +try: + print of ["fraction-answer", audio_volume of [c + 0.5, 1.0]] +catch e: + print of ["channel-fraction", e.kind] +v is audio_volume of [c + 0.0, 9.0] +print of [v, nt of v] +audio_pause of 0 +print of ["mixed", wait_for of [log, "mix:"]] +audio_stop of c +audio_close of null +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/audio_volume.pre b/tests/phase2/builtins/builtins/audio_volume.pre new file mode 100644 index 000000000..54a9744f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_volume.pre @@ -0,0 +1,6 @@ +["fraction-answer", 1] +[1, "float"] +["mixed", true] +hook-loaded mix +mix: 13104 -13104 13104 -13104 13104 -13104 13104 -13104 +rc=0 diff --git a/tests/phase2/builtins/builtins/audio_volume.tag b/tests/phase2/builtins/builtins/audio_volume.tag new file mode 100644 index 000000000..9c219144d --- /dev/null +++ b/tests/phase2/builtins/builtins/audio_volume.tag @@ -0,0 +1,6 @@ +["fraction-answer", 1] +[1, "undefined_name"] +["mixed", true] +hook-loaded mix +mix: 13104 -13104 13104 -13104 13104 -13104 13104 -13104 +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_and.branch b/tests/phase2/builtins/builtins/bit_and.branch new file mode 100644 index 000000000..5ecc575a6 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_and.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +["fraction", "value"] +[2, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_and.eigs b/tests/phase2/builtins/builtins/bit_and.eigs new file mode 100644 index 000000000..dc34296ca --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_and.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: bit_and (int-requiring; Lua & (3.4.2): exact int64 operands (N8, R1.5)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_and of [9007199254740993, 9007199254740993] +print of [a, nt of a] +try: + print of ["fraction-answer", bit_and of [1.5, 1]] +catch e: + print of ["fraction", e.kind] +f is bit_and of [6.0, 3.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_and.pre b/tests/phase2/builtins/builtins/bit_and.pre new file mode 100644 index 000000000..5ecc575a6 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_and.pre @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +["fraction", "value"] +[2, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_and.tag b/tests/phase2/builtins/builtins/bit_and.tag new file mode 100644 index 000000000..7df1846e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_and.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +["fraction-answer", 1] +[2, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_not.branch b/tests/phase2/builtins/builtins/bit_not.branch new file mode 100644 index 000000000..c106b8831 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_not.branch @@ -0,0 +1,4 @@ +[-9007199254740993, "int"] +["fraction", "value"] +[-1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_not.eigs b/tests/phase2/builtins/builtins/bit_not.eigs new file mode 100644 index 000000000..298445d10 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_not.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: bit_not (int-requiring; Lua unary ~); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_not of 9007199254740992 +print of [a, nt of a] +try: + print of ["fraction-answer", bit_not of 1.5] +catch e: + print of ["fraction", e.kind] +f is bit_not of 0.0 +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_not.pre b/tests/phase2/builtins/builtins/bit_not.pre new file mode 100644 index 000000000..c106b8831 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_not.pre @@ -0,0 +1,4 @@ +[-9007199254740993, "int"] +["fraction", "value"] +[-1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_not.tag b/tests/phase2/builtins/builtins/bit_not.tag new file mode 100644 index 000000000..f8f58d3a1 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_not.tag @@ -0,0 +1,4 @@ +[-9007199254740992, "undefined_name"] +["fraction-answer", -2] +[-1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_or.branch b/tests/phase2/builtins/builtins/bit_or.branch new file mode 100644 index 000000000..2da081080 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_or.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +["fraction", "value"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_or.eigs b/tests/phase2/builtins/builtins/bit_or.eigs new file mode 100644 index 000000000..b3e1b07da --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_or.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: bit_or (int-requiring; Lua |); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_or of [9007199254740992, 1] +print of [a, nt of a] +try: + print of ["fraction-answer", bit_or of [1.5, 1]] +catch e: + print of ["fraction", e.kind] +f is bit_or of [4.0, 1.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_or.pre b/tests/phase2/builtins/builtins/bit_or.pre new file mode 100644 index 000000000..2da081080 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_or.pre @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +["fraction", "value"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_or.tag b/tests/phase2/builtins/builtins/bit_or.tag new file mode 100644 index 000000000..01ab7d1ae --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_or.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +["fraction-answer", 1] +[5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shl.branch b/tests/phase2/builtins/builtins/bit_shl.branch new file mode 100644 index 000000000..d2cdf16d9 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shl.branch @@ -0,0 +1,5 @@ +[-9223372036854775808, "int"] +[0, 4] +["fraction", "value"] +[16, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shl.eigs b/tests/phase2/builtins/builtins/bit_shl.eigs new file mode 100644 index 000000000..45575c916 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shl.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: bit_shl (int-requiring; Lua << (R1.5): count >=64 gives 0, a negative count shifts right); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_shl of [1, 63] +print of [a, nt of a] +print of [bit_shl of [1, 64], bit_shl of [8, -1]] +try: + print of ["fraction-answer", bit_shl of [1, 1.5]] +catch e: + print of ["fraction", e.kind] +f is bit_shl of [1.0, 4.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_shl.pre b/tests/phase2/builtins/builtins/bit_shl.pre new file mode 100644 index 000000000..d2cdf16d9 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shl.pre @@ -0,0 +1,5 @@ +[-9223372036854775808, "int"] +[0, 4] +["fraction", "value"] +[16, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shl.tag b/tests/phase2/builtins/builtins/bit_shl.tag new file mode 100644 index 000000000..8e6fe7aaf --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shl.tag @@ -0,0 +1,5 @@ +[-9.223372036854776e+18, "undefined_name"] +[1, 0] +["fraction-answer", 2] +[16, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shr.branch b/tests/phase2/builtins/builtins/bit_shr.branch new file mode 100644 index 000000000..6bafdcc7b --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shr.branch @@ -0,0 +1,5 @@ +[9223372036854775807, "int"] +[0, 2] +["fraction", "value"] +[4, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shr.eigs b/tests/phase2/builtins/builtins/bit_shr.eigs new file mode 100644 index 000000000..a8ae2bcd7 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shr.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: bit_shr (int-requiring; Lua >> (N8, R1.5) is logical); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_shr of [-1, 1] +print of [a, nt of a] +print of [bit_shr of [1, 64], bit_shr of [1, -1]] +try: + print of ["fraction-answer", bit_shr of [16, 1.5]] +catch e: + print of ["fraction", e.kind] +f is bit_shr of [16.0, 2.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_shr.pre b/tests/phase2/builtins/builtins/bit_shr.pre new file mode 100644 index 000000000..6bafdcc7b --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shr.pre @@ -0,0 +1,5 @@ +[9223372036854775807, "int"] +[0, 2] +["fraction", "value"] +[4, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_shr.tag b/tests/phase2/builtins/builtins/bit_shr.tag new file mode 100644 index 000000000..04dd50098 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_shr.tag @@ -0,0 +1,5 @@ +[-1, "undefined_name"] +[1, 0] +["fraction-answer", 8] +[4, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_xor.branch b/tests/phase2/builtins/builtins/bit_xor.branch new file mode 100644 index 000000000..5712e430a --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_xor.branch @@ -0,0 +1,4 @@ +[9007199254740992, "int"] +["fraction", "value"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_xor.eigs b/tests/phase2/builtins/builtins/bit_xor.eigs new file mode 100644 index 000000000..243333340 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_xor.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: bit_xor (int-requiring; Lua binary ~); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is bit_xor of [9007199254740993, 1] +print of [a, nt of a] +try: + print of ["fraction-answer", bit_xor of [1.5, 1]] +catch e: + print of ["fraction", e.kind] +f is bit_xor of [6.0, 3.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/bit_xor.pre b/tests/phase2/builtins/builtins/bit_xor.pre new file mode 100644 index 000000000..5712e430a --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_xor.pre @@ -0,0 +1,4 @@ +[9007199254740992, "int"] +["fraction", "value"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/bit_xor.tag b/tests/phase2/builtins/builtins/bit_xor.tag new file mode 100644 index 000000000..1d1206ac7 --- /dev/null +++ b/tests/phase2/builtins/builtins/bit_xor.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +["fraction-answer", 0] +[5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_copy.branch b/tests/phase2/builtins/builtins/buf_copy.branch new file mode 100644 index 000000000..bbd9de68c --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_copy.branch @@ -0,0 +1,5 @@ +[2.0, 3.0, 0.0] +["offset-fraction", "value"] +["count-fraction", "value"] +[2.0, 1.0, 2.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_copy.eigs b/tests/phase2/builtins/builtins/buf_copy.eigs new file mode 100644 index 000000000..0a43bb887 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_copy.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: buf_copy (int-requiring; offsets and counts); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is buf_from_list of [1, 2, 3, 4] +d is buffer of 4 +buf_copy of [s, 1, d, 0, 2] +print of [d[0], d[1], d[2]] +try: + buf_copy of [s, 0.5, d, 0, 1] + print of ["fraction accepted", d[0]] +catch e: + print of ["offset-fraction", e.kind] +try: + buf_copy of [s, 0, d, 0, 1.5] + print of ["fraction accepted", d[0]] +catch e: + print of ["count-fraction", e.kind] +buf_copy of [s, 0.0, d, 2.0, 2.0] +print of [d[0], d[2], d[3]] diff --git a/tests/phase2/builtins/builtins/buf_copy.pre b/tests/phase2/builtins/builtins/buf_copy.pre new file mode 100644 index 000000000..1bd34c83b --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_copy.pre @@ -0,0 +1,5 @@ +[2, 3, 0] +["fraction accepted", 1] +["fraction accepted", 1] +[1, 1, 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_copy.tag b/tests/phase2/builtins/builtins/buf_copy.tag new file mode 100644 index 000000000..1bd34c83b --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_copy.tag @@ -0,0 +1,5 @@ +[2, 3, 0] +["fraction accepted", 1] +["fraction accepted", 1] +[1, 1, 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_deinterleave.branch b/tests/phase2/builtins/builtins/buf_deinterleave.branch new file mode 100644 index 000000000..13f15752b --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_deinterleave.branch @@ -0,0 +1,5 @@ +[3, 10.0, 30.0] +["channel-fraction", "value"] +["nch-fraction", "value"] +[1.0, 2.0, 3.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_deinterleave.eigs b/tests/phase2/builtins/builtins/buf_deinterleave.eigs new file mode 100644 index 000000000..42f3e5f0d --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_deinterleave.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: buf_deinterleave (int-requiring; channel count and index); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is buf_from_list of [1, 10, 2, 20, 3, 30] +r is buf_deinterleave of [s, 1, 2] +print of [buf_len of r, r[0], r[2]] +try: + print of ["fraction-answer", buf_len of (buf_deinterleave of [s, 0.5, 2])] +catch e: + print of ["channel-fraction", e.kind] +try: + print of ["fraction-answer", buf_len of (buf_deinterleave of [s, 0, 2.5])] +catch e: + print of ["nch-fraction", e.kind] +f is buf_deinterleave of [s, 0.0, 2.0] +print of [f[0], f[1], f[2]] diff --git a/tests/phase2/builtins/builtins/buf_deinterleave.pre b/tests/phase2/builtins/builtins/buf_deinterleave.pre new file mode 100644 index 000000000..519fd5ace --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_deinterleave.pre @@ -0,0 +1,5 @@ +[3, 10, 30] +["fraction-answer", 3] +["fraction-answer", 3] +[1, 2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_deinterleave.tag b/tests/phase2/builtins/builtins/buf_deinterleave.tag new file mode 100644 index 000000000..519fd5ace --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_deinterleave.tag @@ -0,0 +1,5 @@ +[3, 10, 30] +["fraction-answer", 3] +["fraction-answer", 3] +[1, 2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_dot.branch b/tests/phase2/builtins/builtins/buf_dot.branch new file mode 100644 index 000000000..2ee948452 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_dot.branch @@ -0,0 +1,4 @@ +[32.0, "float"] +["offset-fraction", "value"] +28.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_dot.eigs b/tests/phase2/builtins/builtins/buf_dot.eigs new file mode 100644 index 000000000..368d93c7e --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_dot.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: buf_dot (float-producing; buffer dot product; offsets and counts by N5 (Q1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is buf_from_list of [1, 2, 3] +b is buf_from_list of [4, 5, 6] +d is buf_dot of [a, b, 0, 0, 3] +print of [d, nt of d] +try: + print of ["fraction-answer", buf_dot of [a, b, 0.5, 0, 1]] +catch e: + print of ["offset-fraction", e.kind] +print of (buf_dot of [a, b, 1.0, 1.0, 2.0]) diff --git a/tests/phase2/builtins/builtins/buf_dot.pre b/tests/phase2/builtins/builtins/buf_dot.pre new file mode 100644 index 000000000..e65cd8c09 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_dot.pre @@ -0,0 +1,4 @@ +[32, "float"] +["fraction-answer", 4] +28 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_dot.tag b/tests/phase2/builtins/builtins/buf_dot.tag new file mode 100644 index 000000000..1fb43e268 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_dot.tag @@ -0,0 +1,4 @@ +[32, "undefined_name"] +["fraction-answer", 4] +28 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_fill.branch b/tests/phase2/builtins/builtins/buf_fill.branch new file mode 100644 index 000000000..e1d0bbf17 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_fill.branch @@ -0,0 +1,5 @@ +[0.0, 7.0, 7.0, 0.0, "float"] +["offset-fraction", "value"] +["count-fraction", "value"] +[0.0, 5.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_fill.eigs b/tests/phase2/builtins/builtins/buf_fill.eigs new file mode 100644 index 000000000..690e20422 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_fill.eigs @@ -0,0 +1,21 @@ +# R1.6 behaviour-table probe: buf_fill (float-producing; fills float storage; offsets by N5) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buffer of 4 +buf_fill of [b, 1, 2, 7] +print of [b[0], b[1], b[2], b[3], nt of b[1]] +try: + buf_fill of [b, 0.5, 1, 9] + print of ["fraction accepted", b[0]] +catch e: + print of ["offset-fraction", e.kind] +try: + buf_fill of [b, 0, 1.5, 9] + print of ["fraction accepted", b[0]] +catch e: + print of ["count-fraction", e.kind] +buf_fill of [b, 3.0, 1.0, 5] +print of [b[0], b[3]] diff --git a/tests/phase2/builtins/builtins/buf_fill.pre b/tests/phase2/builtins/builtins/buf_fill.pre new file mode 100644 index 000000000..9dbe9907a --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_fill.pre @@ -0,0 +1,5 @@ +[0, 7, 7, 0, "float"] +["fraction accepted", 9] +["fraction accepted", 9] +[9, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_fill.tag b/tests/phase2/builtins/builtins/buf_fill.tag new file mode 100644 index 000000000..e217e7c81 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_fill.tag @@ -0,0 +1,5 @@ +[0, 7, 7, 0, "undefined_name"] +["fraction accepted", 9] +["fraction accepted", 9] +[9, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_list.branch b/tests/phase2/builtins/builtins/buf_from_list.branch new file mode 100644 index 000000000..62168d251 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_list.branch @@ -0,0 +1,2 @@ +[1.0, "float", 9007199254740992.0, "float", 2.5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_list.eigs b/tests/phase2/builtins/builtins/buf_from_list.eigs new file mode 100644 index 000000000..afc480de0 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_list.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: buf_from_list (float-producing; buffer storage is float: an int element converts (Q2)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [1, 9007199254740993, 2.5] +print of [b[0], nt of b[0], b[1], nt of b[1], b[2]] diff --git a/tests/phase2/builtins/builtins/buf_from_list.pre b/tests/phase2/builtins/builtins/buf_from_list.pre new file mode 100644 index 000000000..652b245e0 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_list.pre @@ -0,0 +1,2 @@ +[1, "float", 9007199254740992, "float", 2.5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_list.tag b/tests/phase2/builtins/builtins/buf_from_list.tag new file mode 100644 index 000000000..b2f860126 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_list.tag @@ -0,0 +1,2 @@ +[1, "undefined_name", 9007199254740992, "undefined_name", 2.5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_pcm16le.branch b/tests/phase2/builtins/builtins/buf_from_pcm16le.branch new file mode 100644 index 000000000..7105dfded --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_pcm16le.branch @@ -0,0 +1,5 @@ +["byte-300", "value"] +["byte-fraction", "value"] +[1.0, "float"] +[-1.000030518509476, 0.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_pcm16le.eigs b/tests/phase2/builtins/builtins/buf_from_pcm16le.eigs new file mode 100644 index 000000000..eb7bf4cda --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_pcm16le.eigs @@ -0,0 +1,27 @@ +# R1.6 behaviour-table probe: buf_from_pcm16le (int-requiring; byte values through the Q1 byte reader (#1758 comment 6174964306)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buffer of 2 +b[0] is 300 +b[1] is 0 +try: + r is buf_from_pcm16le of [b, 0, 1] + print of ["300-answer", r[0]] +catch e: + print of ["byte-300", e.kind] +b[0] is 1.5 +try: + r is buf_from_pcm16le of [b, 0, 1] + print of ["fraction-answer", r[0]] +catch e: + print of ["byte-fraction", e.kind] +b[0] is 255 +b[1] is 127 +u is buf_from_pcm16le of [b, 0, 1] +print of [u[0], nt of u[0]] +c is buf_from_list of [0, 128, 0, 0] +s is buf_from_pcm16le of [c, 0.0, 2.0] +print of [s[0], s[1]] diff --git a/tests/phase2/builtins/builtins/buf_from_pcm16le.pre b/tests/phase2/builtins/builtins/buf_from_pcm16le.pre new file mode 100644 index 000000000..999f5bd6a --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_pcm16le.pre @@ -0,0 +1,5 @@ +["300-answer", 0.009155552842799158] +["fraction-answer", 4.577776421399579e-05] +[1, "float"] +[-1.000030518509476, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_from_pcm16le.tag b/tests/phase2/builtins/builtins/buf_from_pcm16le.tag new file mode 100644 index 000000000..10eff87aa --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_from_pcm16le.tag @@ -0,0 +1,5 @@ +["300-answer", 0.009155552842799158] +["fraction-answer", 4.577776421399579e-05] +[1, "undefined_name"] +[-1.000030518509476, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_get.branch b/tests/phase2/builtins/builtins/buf_get.branch new file mode 100644 index 000000000..def5932f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_get.branch @@ -0,0 +1,4 @@ +[20.0, "float"] +["index-fraction", "value"] +[30.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_get.eigs b/tests/phase2/builtins/builtins/buf_get.eigs new file mode 100644 index 000000000..76c1f7882 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_get.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: buf_get (int-requiring; index; returns the float element); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [10, 20, 30] +g is buf_get of [b, 1] +print of [g, nt of g] +try: + print of ["fraction-answer", buf_get of [b, 1.5]] +catch e: + print of ["index-fraction", e.kind] +h is buf_get of [b, 2.0] +print of [h, nt of h] diff --git a/tests/phase2/builtins/builtins/buf_get.pre b/tests/phase2/builtins/builtins/buf_get.pre new file mode 100644 index 000000000..f3f1fa29d --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_get.pre @@ -0,0 +1,4 @@ +[20, "float"] +["fraction-answer", 20] +[30, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_get.tag b/tests/phase2/builtins/builtins/buf_get.tag new file mode 100644 index 000000000..3b8708330 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_get.tag @@ -0,0 +1,4 @@ +[20, "undefined_name"] +["fraction-answer", 20] +[30, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_len.branch b/tests/phase2/builtins/builtins/buf_len.branch new file mode 100644 index 000000000..54e1be9c1 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_len.branch @@ -0,0 +1,2 @@ +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_len.eigs b/tests/phase2/builtins/builtins/buf_len.eigs new file mode 100644 index 000000000..ebb5fb178 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_len.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: buf_len (int-preserving; element count) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +n is buf_len of (buffer of 5) +print of [n, nt of n] diff --git a/tests/phase2/builtins/builtins/buf_len.pre b/tests/phase2/builtins/builtins/buf_len.pre new file mode 100644 index 000000000..a7f117d08 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_len.pre @@ -0,0 +1,2 @@ +[5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_len.tag b/tests/phase2/builtins/builtins/buf_len.tag new file mode 100644 index 000000000..ba2c22cb7 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_len.tag @@ -0,0 +1,2 @@ +[5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_mix.branch b/tests/phase2/builtins/builtins/buf_mix.branch new file mode 100644 index 000000000..189331421 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_mix.branch @@ -0,0 +1,4 @@ +[2.0, 3.0, "float"] +["offset-fraction", "value"] +[2.0, 5.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_mix.eigs b/tests/phase2/builtins/builtins/buf_mix.eigs new file mode 100644 index 000000000..9d8b4ef7e --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_mix.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: buf_mix (float-producing; float storage; offsets and counts by N5 (Q1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is buf_from_list of [1, 1] +s is buf_from_list of [2, 4] +buf_mix of [d, s, 0, 0, 2, 0.5] +print of [d[0], d[1], nt of d[0]] +try: + buf_mix of [d, s, 0.5, 0, 1, 1] + print of ["fraction accepted", d[0]] +catch e: + print of ["offset-fraction", e.kind] +buf_mix of [d, s, 1.0, 0.0, 1.0, 1] +print of [d[0], d[1]] diff --git a/tests/phase2/builtins/builtins/buf_mix.pre b/tests/phase2/builtins/builtins/buf_mix.pre new file mode 100644 index 000000000..5541aa467 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_mix.pre @@ -0,0 +1,4 @@ +[2, 3, "float"] +["fraction accepted", 4] +[4, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_mix.tag b/tests/phase2/builtins/builtins/buf_mix.tag new file mode 100644 index 000000000..b3c216aa4 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_mix.tag @@ -0,0 +1,4 @@ +[2, 3, "undefined_name"] +["fraction accepted", 4] +[4, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_peak.branch b/tests/phase2/builtins/builtins/buf_peak.branch new file mode 100644 index 000000000..08f7d0feb --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_peak.branch @@ -0,0 +1,4 @@ +[5.0, "float"] +["offset-fraction", "value"] +3.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_peak.eigs b/tests/phase2/builtins/builtins/buf_peak.eigs new file mode 100644 index 000000000..a2d35a3cc --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_peak.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: buf_peak (float-producing; float max abs; offsets and counts by N5 (Q1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [1, -5, 3] +p is buf_peak of [b, 0, 3] +print of [p, nt of p] +try: + print of ["fraction-answer", buf_peak of [b, 0.5, 1]] +catch e: + print of ["offset-fraction", e.kind] +print of (buf_peak of [b, 2.0, 1.0]) diff --git a/tests/phase2/builtins/builtins/buf_peak.pre b/tests/phase2/builtins/builtins/buf_peak.pre new file mode 100644 index 000000000..58f7b12b1 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_peak.pre @@ -0,0 +1,4 @@ +[5, "float"] +["fraction-answer", 1] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_peak.tag b/tests/phase2/builtins/builtins/buf_peak.tag new file mode 100644 index 000000000..e806d9f2e --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_peak.tag @@ -0,0 +1,4 @@ +[5, "undefined_name"] +["fraction-answer", 1] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_resample_linear.branch b/tests/phase2/builtins/builtins/buf_resample_linear.branch new file mode 100644 index 000000000..3a21e2a59 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_resample_linear.branch @@ -0,0 +1,4 @@ +[0.0, 5.0, 10.0, "float"] +["count-fraction", "value"] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_resample_linear.eigs b/tests/phase2/builtins/builtins/buf_resample_linear.eigs new file mode 100644 index 000000000..201c3c14e --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_resample_linear.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: buf_resample_linear (float-producing; float storage; dst_len is a count (N5, Q1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is buf_from_list of [0, 10] +r is buf_resample_linear of [s, 3] +print of [r[0], r[1], r[2], nt of r[1]] +try: + print of ["fraction-answer", buf_len of (buf_resample_linear of [s, 2.5])] +catch e: + print of ["count-fraction", e.kind] +print of (buf_len of (buf_resample_linear of [s, 3.0])) diff --git a/tests/phase2/builtins/builtins/buf_resample_linear.pre b/tests/phase2/builtins/builtins/buf_resample_linear.pre new file mode 100644 index 000000000..cb6d2fe68 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_resample_linear.pre @@ -0,0 +1,4 @@ +[0, 5, 10, "float"] +["fraction-answer", 2] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_resample_linear.tag b/tests/phase2/builtins/builtins/buf_resample_linear.tag new file mode 100644 index 000000000..9e52d7eb8 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_resample_linear.tag @@ -0,0 +1,4 @@ +[0, 5, 10, "undefined_name"] +["fraction-answer", 2] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_scale_range.branch b/tests/phase2/builtins/builtins/buf_scale_range.branch new file mode 100644 index 000000000..07b9d3d71 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_scale_range.branch @@ -0,0 +1,4 @@ +[1.0, 4.0, 6.0, "float"] +["offset-fraction", "value"] +3.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_scale_range.eigs b/tests/phase2/builtins/builtins/buf_scale_range.eigs new file mode 100644 index 000000000..b512edcba --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_scale_range.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: buf_scale_range (float-producing; float storage; offsets and counts by N5 (Q1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [1, 2, 3] +buf_scale_range of [b, 1, 2, 2] +print of [b[0], b[1], b[2], nt of b[1]] +try: + buf_scale_range of [b, 0.5, 1, 10] + print of ["fraction accepted", b[0]] +catch e: + print of ["offset-fraction", e.kind] +buf_scale_range of [b, 0.0, 1.0, 3] +print of b[0] diff --git a/tests/phase2/builtins/builtins/buf_scale_range.pre b/tests/phase2/builtins/builtins/buf_scale_range.pre new file mode 100644 index 000000000..e2cf8b1d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_scale_range.pre @@ -0,0 +1,4 @@ +[1, 4, 6, "float"] +["fraction accepted", 10] +30 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_scale_range.tag b/tests/phase2/builtins/builtins/buf_scale_range.tag new file mode 100644 index 000000000..5f59ba331 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_scale_range.tag @@ -0,0 +1,4 @@ +[1, 4, 6, "undefined_name"] +["fraction accepted", 10] +30 +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_set.branch b/tests/phase2/builtins/builtins/buf_set.branch new file mode 100644 index 000000000..9b7a1c21d --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_set.branch @@ -0,0 +1,4 @@ +[7.0, "float"] +["index-fraction", "value"] +[9007199254740992.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_set.eigs b/tests/phase2/builtins/builtins/buf_set.eigs new file mode 100644 index 000000000..07d855a72 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_set.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: buf_set (int-requiring; index; the value converts to float storage (Q2)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buffer of 3 +buf_set of [b, 1, 7] +print of [b[1], nt of b[1]] +try: + buf_set of [b, 1.5, 9] + print of ["fraction accepted", b[1]] +catch e: + print of ["index-fraction", e.kind] +buf_set of [b, 2.0, 9007199254740993] +print of [b[2], nt of b[2]] diff --git a/tests/phase2/builtins/builtins/buf_set.pre b/tests/phase2/builtins/builtins/buf_set.pre new file mode 100644 index 000000000..4f46c4bd8 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_set.pre @@ -0,0 +1,4 @@ +[7, "float"] +["fraction accepted", 9] +[9007199254740992, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_set.tag b/tests/phase2/builtins/builtins/buf_set.tag new file mode 100644 index 000000000..2d4b13ab8 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_set.tag @@ -0,0 +1,4 @@ +[7, "undefined_name"] +["fraction accepted", 9] +[9007199254740992, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_to_pcm16le.branch b/tests/phase2/builtins/builtins/buf_to_pcm16le.branch new file mode 100644 index 000000000..d4c2871d0 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_to_pcm16le.branch @@ -0,0 +1,4 @@ +[0.0, 64.0, 255.0, 127.0, 1.0, 128.0, "float"] +["offset-fraction", "value"] +["count-fraction", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_to_pcm16le.eigs b/tests/phase2/builtins/builtins/buf_to_pcm16le.eigs new file mode 100644 index 000000000..3cd8c8224 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_to_pcm16le.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: buf_to_pcm16le (other, Q7 bounded; samples clamp to [-1, 1] and round (today's behaviour); offsets and counts by N5); phase2-exercises: {"q7":["bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [0.5, 2.5, -3] +r is buf_to_pcm16le of [b, 0, 3] +print of [r[0], r[1], r[2], r[3], r[4], r[5], nt of r[0]] +try: + print of ["fraction-answer", buf_len of (buf_to_pcm16le of [b, 0.5, 1])] +catch e: + print of ["offset-fraction", e.kind] +try: + print of ["fraction-answer", buf_len of (buf_to_pcm16le of [b, 0, 1.5])] +catch e: + print of ["count-fraction", e.kind] diff --git a/tests/phase2/builtins/builtins/buf_to_pcm16le.pre b/tests/phase2/builtins/builtins/buf_to_pcm16le.pre new file mode 100644 index 000000000..19349d649 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_to_pcm16le.pre @@ -0,0 +1,4 @@ +[0, 64, 255, 127, 1, 128, "float"] +["fraction-answer", 2] +["fraction-answer", 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/buf_to_pcm16le.tag b/tests/phase2/builtins/builtins/buf_to_pcm16le.tag new file mode 100644 index 000000000..ebcc4c9a6 --- /dev/null +++ b/tests/phase2/builtins/builtins/buf_to_pcm16le.tag @@ -0,0 +1,4 @@ +[0, 64, 255, 127, 1, 128, "undefined_name"] +["fraction-answer", 2] +["fraction-answer", 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/buffer.branch b/tests/phase2/builtins/builtins/buffer.branch new file mode 100644 index 000000000..54c03ef70 --- /dev/null +++ b/tests/phase2/builtins/builtins/buffer.branch @@ -0,0 +1,5 @@ +[3, "float"] +["size-fraction", "value"] +["cols-fraction", "value"] +[2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/buffer.eigs b/tests/phase2/builtins/builtins/buffer.eigs new file mode 100644 index 000000000..cff1962f4 --- /dev/null +++ b/tests/phase2/builtins/builtins/buffer.eigs @@ -0,0 +1,20 @@ +# R1.6 behaviour-table probe: buffer (int-requiring; size or [rows, cols]); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buffer of 3 +print of [buf_len of b, nt of b[0]] +try: + c is buffer of 2.5 + print of ["fraction accepted", buf_len of c] +catch e: + print of ["size-fraction", e.kind] +try: + d is buffer of [2, 1.5] + print of ["fraction accepted", buf_len of d] +catch e: + print of ["cols-fraction", e.kind] +f is buffer of [2.0, 3.0] +print of (shape of f) diff --git a/tests/phase2/builtins/builtins/buffer.pre b/tests/phase2/builtins/builtins/buffer.pre new file mode 100644 index 000000000..e41b50908 --- /dev/null +++ b/tests/phase2/builtins/builtins/buffer.pre @@ -0,0 +1,5 @@ +[3, "float"] +["fraction accepted", 2] +["fraction accepted", 2] +[2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/buffer.tag b/tests/phase2/builtins/builtins/buffer.tag new file mode 100644 index 000000000..3887d307f --- /dev/null +++ b/tests/phase2/builtins/builtins/buffer.tag @@ -0,0 +1,5 @@ +[3, "undefined_name"] +["fraction accepted", 2] +["fraction accepted", 2] +[2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/build_corpus.branch b/tests/phase2/builtins/builtins/build_corpus.branch new file mode 100644 index 000000000..12b65809a --- /dev/null +++ b/tests/phase2/builtins/builtins/build_corpus.branch @@ -0,0 +1,4 @@ +[1, "int", "int", "int"] +["top_n-fraction", "value"] +1 +rc=0 diff --git a/tests/phase2/builtins/builtins/build_corpus.eigs b/tests/phase2/builtins/builtins/build_corpus.eigs new file mode 100644 index 000000000..284dd2a86 --- /dev/null +++ b/tests/phase2/builtins/builtins/build_corpus.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: build_corpus (int-requiring; counts; returns counts); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +src is p + "/a.eigs" +write_text of [src, "x is 1\ny is x + 2\n"] +r is build_corpus of [[src], 10, p + "/s.bin", p + "/v.json"] +print of [r[2], nt of r[0], nt of r[1], nt of r[2]] +try: + q is build_corpus of [[src], 2.5, p + "/s2.bin", p + "/v2.json"] + print of ["fraction accepted", q[2]] +catch e: + print of ["top_n-fraction", e.kind] +f is build_corpus of [[src], 10.0, p + "/s3.bin", p + "/v3.json"] +print of f[2] diff --git a/tests/phase2/builtins/builtins/build_corpus.pre b/tests/phase2/builtins/builtins/build_corpus.pre new file mode 100644 index 000000000..32daefec1 --- /dev/null +++ b/tests/phase2/builtins/builtins/build_corpus.pre @@ -0,0 +1,4 @@ +[1, "float", "float", "float"] +["fraction accepted", 1] +1 +rc=0 diff --git a/tests/phase2/builtins/builtins/build_corpus.tag b/tests/phase2/builtins/builtins/build_corpus.tag new file mode 100644 index 000000000..af69dbf3d --- /dev/null +++ b/tests/phase2/builtins/builtins/build_corpus.tag @@ -0,0 +1,4 @@ +[1, "undefined_name", "undefined_name", "undefined_name"] +["fraction accepted", 1] +1 +rc=0 diff --git a/tests/phase2/builtins/builtins/ceil.branch b/tests/phase2/builtins/builtins/ceil.branch new file mode 100644 index 000000000..87b268ae8 --- /dev/null +++ b/tests/phase2/builtins/builtins/ceil.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[-2, "int"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ceil.eigs b/tests/phase2/builtins/builtins/ceil.eigs new file mode 100644 index 000000000..495466b1f --- /dev/null +++ b/tests/phase2/builtins/builtins/ceil.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: ceil (int-preserving, LOSS; Lua math.ceil: an int when the result fits int64, else a float) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is ceil of 9007199254740993 +print of [a, nt of a] +b is ceil of -2.5 +print of [b, nt of b] +c is ceil of 1e300 +print of [c, nt of c] diff --git a/tests/phase2/builtins/builtins/ceil.pre b/tests/phase2/builtins/builtins/ceil.pre new file mode 100644 index 000000000..5f90a8663 --- /dev/null +++ b/tests/phase2/builtins/builtins/ceil.pre @@ -0,0 +1,4 @@ +[9007199254740992, "float"] +[-2, "float"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ceil.tag b/tests/phase2/builtins/builtins/ceil.tag new file mode 100644 index 000000000..e6750dfda --- /dev/null +++ b/tests/phase2/builtins/builtins/ceil.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +[-2, "undefined_name"] +[1e+300, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/channel.branch b/tests/phase2/builtins/builtins/channel.branch new file mode 100644 index 000000000..eacbdabe1 --- /dev/null +++ b/tests/phase2/builtins/builtins/channel.branch @@ -0,0 +1,3 @@ +["int", "int"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/channel.eigs b/tests/phase2/builtins/builtins/channel.eigs new file mode 100644 index 000000000..32b3acb5f --- /dev/null +++ b/tests/phase2/builtins/builtins/channel.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: channel (int-preserving; handle dict with int id/gen fields) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +print of [nt of c["_channel_id"], nt of c["_channel_gen"]] +send of [c, 5] +v is recv of c +print of [v, nt of v] +close_channel of c diff --git a/tests/phase2/builtins/builtins/channel.pre b/tests/phase2/builtins/builtins/channel.pre new file mode 100644 index 000000000..ce3a9fd68 --- /dev/null +++ b/tests/phase2/builtins/builtins/channel.pre @@ -0,0 +1,3 @@ +["float", "float"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/channel.tag b/tests/phase2/builtins/builtins/channel.tag new file mode 100644 index 000000000..126a7d1fb --- /dev/null +++ b/tests/phase2/builtins/builtins/channel.tag @@ -0,0 +1,3 @@ +["undefined_name", "undefined_name"] +[5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/channel_closed.branch b/tests/phase2/builtins/builtins/channel_closed.branch new file mode 100644 index 000000000..01384beb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/channel_closed.branch @@ -0,0 +1,5 @@ +false +["id-fraction", "value"] +false +true +rc=0 diff --git a/tests/phase2/builtins/builtins/channel_closed.eigs b/tests/phase2/builtins/builtins/channel_closed.eigs new file mode 100644 index 000000000..e885d3180 --- /dev/null +++ b/tests/phase2/builtins/builtins/channel_closed.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: channel_closed (int-requiring; channel handle ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +print of (channel_closed of c) +try: + print of ["fraction-answer", channel_closed of {"_channel_id": id + 0.5, "_channel_gen": gen}] +catch e: + print of ["id-fraction", e.kind] +print of (channel_closed of {"_channel_id": id + 0.0, "_channel_gen": gen + 0.0}) +close_channel of c +print of (channel_closed of c) diff --git a/tests/phase2/builtins/builtins/channel_closed.pre b/tests/phase2/builtins/builtins/channel_closed.pre new file mode 100644 index 000000000..bc64ec4ce --- /dev/null +++ b/tests/phase2/builtins/builtins/channel_closed.pre @@ -0,0 +1,5 @@ +false +["fraction-answer", false] +false +true +rc=0 diff --git a/tests/phase2/builtins/builtins/channel_closed.tag b/tests/phase2/builtins/builtins/channel_closed.tag new file mode 100644 index 000000000..bc64ec4ce --- /dev/null +++ b/tests/phase2/builtins/builtins/channel_closed.tag @@ -0,0 +1,5 @@ +false +["fraction-answer", false] +false +true +rc=0 diff --git a/tests/phase2/builtins/builtins/char_at.branch b/tests/phase2/builtins/builtins/char_at.branch new file mode 100644 index 000000000..2268ee43b --- /dev/null +++ b/tests/phase2/builtins/builtins/char_at.branch @@ -0,0 +1,4 @@ +e +["index-fraction", "value"] +["e", "o"] +rc=0 diff --git a/tests/phase2/builtins/builtins/char_at.eigs b/tests/phase2/builtins/builtins/char_at.eigs new file mode 100644 index 000000000..8899833da --- /dev/null +++ b/tests/phase2/builtins/builtins/char_at.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: char_at (int-requiring; string position (Lua string.sub)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (char_at of ["hello", 1]) +try: + print of ["fraction-answer", char_at of ["hello", 1.5]] +catch e: + print of ["index-fraction", e.kind] +print of [char_at of ["hello", 1.0], char_at of ["hello", -1.0]] diff --git a/tests/phase2/builtins/builtins/char_at.pre b/tests/phase2/builtins/builtins/char_at.pre new file mode 100644 index 000000000..dd721531d --- /dev/null +++ b/tests/phase2/builtins/builtins/char_at.pre @@ -0,0 +1,4 @@ +e +["fraction-answer", "e"] +["e", "o"] +rc=0 diff --git a/tests/phase2/builtins/builtins/char_at.tag b/tests/phase2/builtins/builtins/char_at.tag new file mode 100644 index 000000000..dd721531d --- /dev/null +++ b/tests/phase2/builtins/builtins/char_at.tag @@ -0,0 +1,4 @@ +e +["fraction-answer", "e"] +["e", "o"] +rc=0 diff --git a/tests/phase2/builtins/builtins/chr.branch b/tests/phase2/builtins/builtins/chr.branch new file mode 100644 index 000000000..1dde1c661 --- /dev/null +++ b/tests/phase2/builtins/builtins/chr.branch @@ -0,0 +1,5 @@ +A +["byte-fraction", "value"] +["byte-256", "value"] +B +rc=0 diff --git a/tests/phase2/builtins/builtins/chr.eigs b/tests/phase2/builtins/builtins/chr.eigs new file mode 100644 index 000000000..30dab74cb --- /dev/null +++ b/tests/phase2/builtins/builtins/chr.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: chr (int-requiring; byte 1..255, already exactness-checked (Lua string.char)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (chr of 65) +try: + print of ["fraction-answer", chr of 65.5] +catch e: + print of ["byte-fraction", e.kind] +try: + print of ["256-answer", chr of 256] +catch e: + print of ["byte-256", e.kind] +print of (chr of 66.0) diff --git a/tests/phase2/builtins/builtins/chr.pre b/tests/phase2/builtins/builtins/chr.pre new file mode 100644 index 000000000..1dde1c661 --- /dev/null +++ b/tests/phase2/builtins/builtins/chr.pre @@ -0,0 +1,5 @@ +A +["byte-fraction", "value"] +["byte-256", "value"] +B +rc=0 diff --git a/tests/phase2/builtins/builtins/chr.tag b/tests/phase2/builtins/builtins/chr.tag new file mode 100644 index 000000000..1dde1c661 --- /dev/null +++ b/tests/phase2/builtins/builtins/chr.tag @@ -0,0 +1,5 @@ +A +["byte-fraction", "value"] +["byte-256", "value"] +B +rc=0 diff --git a/tests/phase2/builtins/builtins/clock_unix.branch b/tests/phase2/builtins/builtins/clock_unix.branch new file mode 100644 index 000000000..27da9a054 --- /dev/null +++ b/tests/phase2/builtins/builtins/clock_unix.branch @@ -0,0 +1,2 @@ +["float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/clock_unix.eigs b/tests/phase2/builtins/builtins/clock_unix.eigs new file mode 100644 index 000000000..bb55566ff --- /dev/null +++ b/tests/phase2/builtins/builtins/clock_unix.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: clock_unix (float-producing; fractional seconds) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is clock_unix of null +print of [nt of c, c > 1000000000] diff --git a/tests/phase2/builtins/builtins/clock_unix.pre b/tests/phase2/builtins/builtins/clock_unix.pre new file mode 100644 index 000000000..27da9a054 --- /dev/null +++ b/tests/phase2/builtins/builtins/clock_unix.pre @@ -0,0 +1,2 @@ +["float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/clock_unix.tag b/tests/phase2/builtins/builtins/clock_unix.tag new file mode 100644 index 000000000..8f295afa7 --- /dev/null +++ b/tests/phase2/builtins/builtins/clock_unix.tag @@ -0,0 +1,2 @@ +["undefined_name", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/close_channel.branch b/tests/phase2/builtins/builtins/close_channel.branch new file mode 100644 index 000000000..4ffaef4cf --- /dev/null +++ b/tests/phase2/builtins/builtins/close_channel.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/close_channel.eigs b/tests/phase2/builtins/builtins/close_channel.eigs new file mode 100644 index 000000000..9e7c61036 --- /dev/null +++ b/tests/phase2/builtins/builtins/close_channel.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: close_channel (int-requiring; channel handle ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +try: + close_channel of {"_channel_id": id + 0.5, "_channel_gen": gen} + print of ["fraction closed it", channel_closed of c] +catch e: + print of ["id-fraction", e.kind] +close_channel of {"_channel_id": id + 0.0, "_channel_gen": gen + 0.0} +print of (channel_closed of c) diff --git a/tests/phase2/builtins/builtins/close_channel.pre b/tests/phase2/builtins/builtins/close_channel.pre new file mode 100644 index 000000000..693519bb9 --- /dev/null +++ b/tests/phase2/builtins/builtins/close_channel.pre @@ -0,0 +1,3 @@ +["fraction closed it", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/close_channel.tag b/tests/phase2/builtins/builtins/close_channel.tag new file mode 100644 index 000000000..693519bb9 --- /dev/null +++ b/tests/phase2/builtins/builtins/close_channel.tag @@ -0,0 +1,3 @@ +["fraction closed it", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/copy_into.branch b/tests/phase2/builtins/builtins/copy_into.branch new file mode 100644 index 000000000..c231045e4 --- /dev/null +++ b/tests/phase2/builtins/builtins/copy_into.branch @@ -0,0 +1,5 @@ +[0, 7, 8] +["offset-fraction", "value"] +[5, 7, 8] +[3.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/copy_into.eigs b/tests/phase2/builtins/builtins/copy_into.eigs new file mode 100644 index 000000000..4a5ae9543 --- /dev/null +++ b/tests/phase2/builtins/builtins/copy_into.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: copy_into (int-requiring; offset; values convert to float storage (Q2)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [0, 0, 0] +copy_into of [xs, 1, [7, 8]] +print of xs +try: + copy_into of [xs, 0.5, [9]] + print of ["fraction accepted", xs] +catch e: + print of ["offset-fraction", e.kind] +copy_into of [xs, 0.0, [5]] +print of xs +b is buffer of 2 +copy_into of [b, 0, [3, 4]] +print of [b[0], nt of b[0]] diff --git a/tests/phase2/builtins/builtins/copy_into.pre b/tests/phase2/builtins/builtins/copy_into.pre new file mode 100644 index 000000000..d55bba20a --- /dev/null +++ b/tests/phase2/builtins/builtins/copy_into.pre @@ -0,0 +1,5 @@ +[0, 7, 8] +["fraction accepted", [9, 7, 8]] +[5, 7, 8] +[3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/copy_into.tag b/tests/phase2/builtins/builtins/copy_into.tag new file mode 100644 index 000000000..a7b6616f1 --- /dev/null +++ b/tests/phase2/builtins/builtins/copy_into.tag @@ -0,0 +1,5 @@ +[0, 7, 8] +["fraction accepted", [9, 7, 8]] +[5, 7, 8] +[3, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/cos.branch b/tests/phase2/builtins/builtins/cos.branch new file mode 100644 index 000000000..8ef8ee243 --- /dev/null +++ b/tests/phase2/builtins/builtins/cos.branch @@ -0,0 +1,3 @@ +[1.0, "float"] +[0.5403023058681398, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/cos.eigs b/tests/phase2/builtins/builtins/cos.eigs new file mode 100644 index 000000000..1ffaa4733 --- /dev/null +++ b/tests/phase2/builtins/builtins/cos.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: cos (float-producing; Lua math.cos) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is cos of 0 +print of [a, nt of a] +b is cos of 1 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/cos.pre b/tests/phase2/builtins/builtins/cos.pre new file mode 100644 index 000000000..447c0fc5b --- /dev/null +++ b/tests/phase2/builtins/builtins/cos.pre @@ -0,0 +1,3 @@ +[1, "float"] +[0.5403023058681398, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/cos.tag b/tests/phase2/builtins/builtins/cos.tag new file mode 100644 index 000000000..f2c8ae567 --- /dev/null +++ b/tests/phase2/builtins/builtins/cos.tag @@ -0,0 +1,3 @@ +[1, "undefined_name"] +[0.5403023058681398, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_execute.branch b/tests/phase2/builtins/builtins/db_execute.branch new file mode 100644 index 000000000..23693414c --- /dev/null +++ b/tests/phase2/builtins/builtins/db_execute.branch @@ -0,0 +1,3 @@ +ok +[9007199254740993, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_execute.eigs b/tests/phase2/builtins/builtins/db_execute.eigs new file mode 100644 index 000000000..57aaaf27f --- /dev/null +++ b/tests/phase2/builtins/builtins/db_execute.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: db_execute (other, LOSS; PROFILE server-db (make server-db) plus DATABASE_URL naming a live PostgreSQL) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + c is db_connect of null + db_execute of "CREATE TEMP TABLE r16 (n bigint)" + r is db_execute of ["INSERT INTO r16 (n) VALUES ($1)", 9007199254740993] + print of r + v is db_query_value of "SELECT n FROM r16" + print of [v, nt of v] +catch e: + print of [e.kind, e.message] diff --git a/tests/phase2/builtins/builtins/db_execute.pre b/tests/phase2/builtins/builtins/db_execute.pre new file mode 100644 index 000000000..2c6f8a07e --- /dev/null +++ b/tests/phase2/builtins/builtins/db_execute.pre @@ -0,0 +1,3 @@ +ok +["value", "db: column 'n' value 9007199254740993 exceeds the exact-integer range of a number (2^53); select it as text (n::text) to keep the digits"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_execute.tag b/tests/phase2/builtins/builtins/db_execute.tag new file mode 100644 index 000000000..bc3d0d999 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_execute.tag @@ -0,0 +1,2 @@ +["io", "db: execute failed: ERROR: invalid input syntax for type bigint: \"9.0072e+15\""] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_json.branch b/tests/phase2/builtins/builtins/db_query_json.branch new file mode 100644 index 000000000..9b37cf8a4 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_json.branch @@ -0,0 +1,3 @@ +[{"n": 9007199254740993, "f": 2.5}] +[9007199254740993, "int", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_json.eigs b/tests/phase2/builtins/builtins/db_query_json.eigs new file mode 100644 index 000000000..5104e366d --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_json.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: db_query_json (other, LOSS; PROFILE server-db (make server-db) plus DATABASE_URL naming a live PostgreSQL) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + c is db_connect of null + j is db_query_json of "SELECT 9007199254740993::bigint AS n, 2.5::float8 AS f" + print of j + rows is json_decode of j + print of [rows[0]["n"], nt of rows[0]["n"], nt of rows[0]["f"]] +catch e: + print of [e.kind, e.message] diff --git a/tests/phase2/builtins/builtins/db_query_json.pre b/tests/phase2/builtins/builtins/db_query_json.pre new file mode 100644 index 000000000..df8b84bc3 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_json.pre @@ -0,0 +1,2 @@ +["value", "db: column 'n' value 9007199254740993 exceeds the exact-integer range of a number (2^53); select it as text (n::text) to keep the digits"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_json.tag b/tests/phase2/builtins/builtins/db_query_json.tag new file mode 100644 index 000000000..df8b84bc3 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_json.tag @@ -0,0 +1,2 @@ +["value", "db: column 'n' value 9007199254740993 exceeds the exact-integer range of a number (2^53); select it as text (n::text) to keep the digits"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_value.branch b/tests/phase2/builtins/builtins/db_query_value.branch new file mode 100644 index 000000000..aa1c44843 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_value.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[9007199254740993, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_value.eigs b/tests/phase2/builtins/builtins/db_query_value.eigs new file mode 100644 index 000000000..0ad3d2d07 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_value.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: db_query_value (other, LOSS; PROFILE server-db (make server-db) plus DATABASE_URL naming a live PostgreSQL) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + c is db_connect of null + v is db_query_value of "SELECT 9007199254740993::bigint" + print of [v, nt of v] + w is db_query_value of ["SELECT $1::bigint + 0", 9007199254740993] + print of [w, nt of w] + f is db_query_value of "SELECT 2.5::float8" + print of [f, nt of f] +catch e: + print of [e.kind, e.message] diff --git a/tests/phase2/builtins/builtins/db_query_value.pre b/tests/phase2/builtins/builtins/db_query_value.pre new file mode 100644 index 000000000..ce3a67326 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_value.pre @@ -0,0 +1,2 @@ +["value", "db: column 'int8' value 9007199254740993 exceeds the exact-integer range of a number (2^53); select it as text (int8::text) to keep the digits"] +rc=0 diff --git a/tests/phase2/builtins/builtins/db_query_value.tag b/tests/phase2/builtins/builtins/db_query_value.tag new file mode 100644 index 000000000..ce3a67326 --- /dev/null +++ b/tests/phase2/builtins/builtins/db_query_value.tag @@ -0,0 +1,2 @@ +["value", "db: column 'int8' value 9007199254740993 exceeds the exact-integer range of a number (2^53); select it as text (int8::text) to keep the digits"] +rc=0 diff --git a/tests/phase2/builtins/builtins/dispatch.branch b/tests/phase2/builtins/builtins/dispatch.branch new file mode 100644 index 000000000..b6de76f93 --- /dev/null +++ b/tests/phase2/builtins/builtins/dispatch.branch @@ -0,0 +1,4 @@ +42 +["key-fraction", "value"] +42 +rc=0 diff --git a/tests/phase2/builtins/builtins/dispatch.eigs b/tests/phase2/builtins/builtins/dispatch.eigs new file mode 100644 index 000000000..cffdcc61d --- /dev/null +++ b/tests/phase2/builtins/builtins/dispatch.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: dispatch (int-requiring; integer table key, already exactness-checked); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define f0(x) as: + return x + 1 +define f1(x) as: + return x * 2 +t is [f0, f1] +print of (dispatch of [t, 1, 21]) +try: + print of ["fraction-answer", dispatch of [t, 0.5, 1]] +catch e: + print of ["key-fraction", e.kind] +print of (dispatch of [t, 1.0, 21]) diff --git a/tests/phase2/builtins/builtins/dispatch.pre b/tests/phase2/builtins/builtins/dispatch.pre new file mode 100644 index 000000000..b6de76f93 --- /dev/null +++ b/tests/phase2/builtins/builtins/dispatch.pre @@ -0,0 +1,4 @@ +42 +["key-fraction", "value"] +42 +rc=0 diff --git a/tests/phase2/builtins/builtins/dispatch.tag b/tests/phase2/builtins/builtins/dispatch.tag new file mode 100644 index 000000000..b6de76f93 --- /dev/null +++ b/tests/phase2/builtins/builtins/dispatch.tag @@ -0,0 +1,4 @@ +42 +["key-fraction", "value"] +42 +rc=0 diff --git a/tests/phase2/builtins/builtins/divide.branch b/tests/phase2/builtins/builtins/divide.branch new file mode 100644 index 000000000..cd22636ef --- /dev/null +++ b/tests/phase2/builtins/builtins/divide.branch @@ -0,0 +1,3 @@ +[3.5, "float"] +[2.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/divide.eigs b/tests/phase2/builtins/builtins/divide.eigs new file mode 100644 index 000000000..82234dd24 --- /dev/null +++ b/tests/phase2/builtins/builtins/divide.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: divide (float-producing; mirrors / (Lua 3.4.1: always float)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is divide of [7, 2] +print of [a, nt of a] +b is divide of [6, 3] +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/divide.pre b/tests/phase2/builtins/builtins/divide.pre new file mode 100644 index 000000000..a573216a2 --- /dev/null +++ b/tests/phase2/builtins/builtins/divide.pre @@ -0,0 +1,3 @@ +[3.5, "float"] +[2, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/divide.tag b/tests/phase2/builtins/builtins/divide.tag new file mode 100644 index 000000000..f227fe2b0 --- /dev/null +++ b/tests/phase2/builtins/builtins/divide.tag @@ -0,0 +1,3 @@ +[3.5, "undefined_name"] +[2, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/dot.branch b/tests/phase2/builtins/builtins/dot.branch new file mode 100644 index 000000000..3cc7f2389 --- /dev/null +++ b/tests/phase2/builtins/builtins/dot.branch @@ -0,0 +1,2 @@ +[11.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/dot.eigs b/tests/phase2/builtins/builtins/dot.eigs new file mode 100644 index 000000000..8acafdd83 --- /dev/null +++ b/tests/phase2/builtins/builtins/dot.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: dot (float-producing; buffer dot product) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is dot of [buf_from_list of [1, 2], buf_from_list of [3, 4]] +print of [d, nt of d] diff --git a/tests/phase2/builtins/builtins/dot.pre b/tests/phase2/builtins/builtins/dot.pre new file mode 100644 index 000000000..20df20dfa --- /dev/null +++ b/tests/phase2/builtins/builtins/dot.pre @@ -0,0 +1,2 @@ +[11, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/dot.tag b/tests/phase2/builtins/builtins/dot.tag new file mode 100644 index 000000000..deee26701 --- /dev/null +++ b/tests/phase2/builtins/builtins/dot.tag @@ -0,0 +1,2 @@ +[11, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_eval_loss.branch b/tests/phase2/builtins/builtins/eigen_eval_loss.branch new file mode 100644 index 000000000..422fee68f --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_eval_loss.branch @@ -0,0 +1,6 @@ +["float", true] +["token-fraction", "value"] +["target-fraction", "value"] +["float", true] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_eval_loss.eigs b/tests/phase2/builtins/builtins/eigen_eval_loss.eigs new file mode 100644 index 000000000..19d78e368 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_eval_loss.eigs @@ -0,0 +1,33 @@ +# R1.6 behaviour-table probe: eigen_eval_loss (float-producing; token ids are indices, the loss is a float; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define mk_mat(r, c) as: + local m is [] + for i in range of r: + local row is [] + for j in range of c: + append of [row, 0.5] + append of [m, row] + return m +define mk_vec(k, v) as: + local out is [] + for i in range of k: + append of [out, v] + return out +p is env_get of "EIGS_PROBE_TMP" +lay is "{\"w_q\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_k\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_v\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_o\":" + (json_encode of (mk_mat of [4, 4])) +lay is lay + ",\"w_ff1\":" + (json_encode of (mk_mat of [4, 8])) + ",\"w_ff2\":" + (json_encode of (mk_mat of [8, 4])) +lay is lay + ",\"ln1_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln1_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + ",\"ln2_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln2_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + "}" +cfg is "{\"vocab_size\":8,\"d_model\":4,\"n_layers\":1,\"d_ff\":8,\"max_seq_len\":16}" +model is "{\"format_version\":2,\"weight_format\":\"ternary_weight_only\",\"config\":" + cfg + ",\"token_embeddings\":" + (json_encode of (mk_mat of [8, 4])) + ",\"output_proj\":" + (json_encode of (mk_mat of [4, 8])) + ",\"layers\":[" + lay + "]}" +write_text of [p + "/model.json", model] +child is ["define nt(x) as:", " try:", " return num_type of x", " catch e:", " return e.kind", "define say(v) as:", " print of (\"R16|\" + (str of v))", "p is env_get of \"EIGS_PROBE_TMP\"", "try:", " s is eigen_model_load of (p + \"/model.json\")", "catch e:", " say of [\"capability\", e.kind, e.message]", " exit of 0", "l is eigen_eval_loss of [[1, 2, 3], 4]", "say of [nt of l, l >= 0]", "try:", " say of [\"fraction-answer\", eigen_eval_loss of [[1.5, 2], 3]]", "catch e:", " say of [\"token-fraction\", e.kind]", "try:", " say of [\"fraction-answer\", eigen_eval_loss of [[1, 2], 3.5]]", "catch e:", " say of [\"target-fraction\", e.kind]", "m is eigen_eval_loss of [[1.0, 2.0], 3.0]", "say of [nt of m, m >= 0]"] +write_text of [p + "/child.eigs", join of [child, "\n"]] +r is exec_capture of [exe_path of null, p + "/child.eigs"] +for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) +print of ["child-rc", r[0]] diff --git a/tests/phase2/builtins/builtins/eigen_eval_loss.pre b/tests/phase2/builtins/builtins/eigen_eval_loss.pre new file mode 100644 index 000000000..f5428bd0f --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_eval_loss.pre @@ -0,0 +1,6 @@ +["float", true] +["fraction-answer", 2.0794415416798357] +["fraction-answer", 2.0794415416798357] +["float", true] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_eval_loss.tag b/tests/phase2/builtins/builtins/eigen_eval_loss.tag new file mode 100644 index 000000000..7eba46f19 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_eval_loss.tag @@ -0,0 +1,6 @@ +["undefined_name", true] +["fraction-answer", 2.0794415416798357] +["fraction-answer", 2.0794415416798357] +["undefined_name", true] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_generate.branch b/tests/phase2/builtins/builtins/eigen_generate.branch new file mode 100644 index 000000000..9fec7194e --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_generate.branch @@ -0,0 +1,6 @@ +[4, "int"] +["token-fraction", "value"] +["max_tokens-fraction", "value"] +[2, "int"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_generate.eigs b/tests/phase2/builtins/builtins/eigen_generate.eigs new file mode 100644 index 000000000..77a461527 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_generate.eigs @@ -0,0 +1,33 @@ +# R1.6 behaviour-table probe: eigen_generate (int-requiring; token ids and max_tokens are integer-valued (N5, Q1); PROFILE server (make server)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define mk_mat(r, c) as: + local m is [] + for i in range of r: + local row is [] + for j in range of c: + append of [row, 0.5] + append of [m, row] + return m +define mk_vec(k, v) as: + local out is [] + for i in range of k: + append of [out, v] + return out +p is env_get of "EIGS_PROBE_TMP" +lay is "{\"w_q\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_k\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_v\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_o\":" + (json_encode of (mk_mat of [4, 4])) +lay is lay + ",\"w_ff1\":" + (json_encode of (mk_mat of [4, 8])) + ",\"w_ff2\":" + (json_encode of (mk_mat of [8, 4])) +lay is lay + ",\"ln1_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln1_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + ",\"ln2_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln2_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + "}" +cfg is "{\"vocab_size\":8,\"d_model\":4,\"n_layers\":1,\"d_ff\":8,\"max_seq_len\":16}" +model is "{\"format_version\":2,\"weight_format\":\"ternary_weight_only\",\"config\":" + cfg + ",\"token_embeddings\":" + (json_encode of (mk_mat of [8, 4])) + ",\"output_proj\":" + (json_encode of (mk_mat of [4, 8])) + ",\"layers\":[" + lay + "]}" +write_text of [p + "/model.json", model] +child is ["define nt(x) as:", " try:", " return num_type of x", " catch e:", " return e.kind", "define say(v) as:", " print of (\"R16|\" + (str of v))", "p is env_get of \"EIGS_PROBE_TMP\"", "try:", " s is eigen_model_load of (p + \"/model.json\")", "catch e:", " say of [\"capability\", e.kind, e.message]", " exit of 0", "g is eigen_generate of [[1, 2, 3], 0, 4]", "say of [len of g, nt of g[0]]", "try:", " say of [\"fraction-answer\", len of (eigen_generate of [[1.5, 2], 0, 4])]", "catch e:", " say of [\"token-fraction\", e.kind]", "try:", " say of [\"fraction-answer\", len of (eigen_generate of [[1, 2], 0, 2.5])]", "catch e:", " say of [\"max_tokens-fraction\", e.kind]", "h is eigen_generate of [[1.0, 2.0], 0, 2.0]", "say of [len of h, nt of h[0]]"] +write_text of [p + "/child.eigs", join of [child, "\n"]] +r is exec_capture of [exe_path of null, p + "/child.eigs"] +for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) +print of ["child-rc", r[0]] diff --git a/tests/phase2/builtins/builtins/eigen_generate.pre b/tests/phase2/builtins/builtins/eigen_generate.pre new file mode 100644 index 000000000..a8f7516ce --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_generate.pre @@ -0,0 +1,6 @@ +[4, "float"] +["fraction-answer", 4] +["fraction-answer", 2] +[2, "float"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_generate.tag b/tests/phase2/builtins/builtins/eigen_generate.tag new file mode 100644 index 000000000..77a3dd5be --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_generate.tag @@ -0,0 +1,6 @@ +[4, "undefined_name"] +["fraction-answer", 4] +["fraction-answer", 2] +[2, "undefined_name"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_model_load.branch b/tests/phase2/builtins/builtins/eigen_model_load.branch new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_model_load.branch @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_model_load.eigs b/tests/phase2/builtins/builtins/eigen_model_load.eigs new file mode 100644 index 000000000..a772979c8 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_model_load.eigs @@ -0,0 +1,33 @@ +# R1.6 behaviour-table probe: eigen_model_load (other; parses model JSON numbers with strtod; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define mk_mat(r, c) as: + local m is [] + for i in range of r: + local row is [] + for j in range of c: + append of [row, 0.5] + append of [m, row] + return m +define mk_vec(k, v) as: + local out is [] + for i in range of k: + append of [out, v] + return out +p is env_get of "EIGS_PROBE_TMP" +lay is "{\"w_q\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_k\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_v\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_o\":" + (json_encode of (mk_mat of [4, 4])) +lay is lay + ",\"w_ff1\":" + (json_encode of (mk_mat of [4, 8])) + ",\"w_ff2\":" + (json_encode of (mk_mat of [8, 4])) +lay is lay + ",\"ln1_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln1_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + ",\"ln2_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln2_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + "}" +cfg is "{\"vocab_size\":8,\"d_model\":4,\"n_layers\":1,\"d_ff\":8,\"max_seq_len\":16}" +model is "{\"format_version\":2,\"weight_format\":\"ternary_weight_only\",\"config\":" + cfg + ",\"token_embeddings\":" + (json_encode of (mk_mat of [8, 4])) + ",\"output_proj\":" + (json_encode of (mk_mat of [4, 8])) + ",\"layers\":[" + lay + "]}" +write_text of [p + "/model.json", model] +child is ["define nt(x) as:", " try:", " return num_type of x", " catch e:", " return e.kind", "define say(v) as:", " print of (\"R16|\" + (str of v))", "p is env_get of \"EIGS_PROBE_TMP\"", "try:", " s is eigen_model_load of (p + \"/model.json\")", "catch e:", " say of [\"capability\", e.kind, e.message]", " exit of 0", "d is json_decode of s", "say of [d[\"status\"], d[\"vocab_size\"], d[\"d_model\"], d[\"n_layers\"], d[\"d_ff\"]]", "say of (eigen_model_loaded of null)"] +write_text of [p + "/child.eigs", join of [child, "\n"]] +r is exec_capture of [exe_path of null, p + "/child.eigs"] +for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) +print of ["child-rc", r[0]] diff --git a/tests/phase2/builtins/builtins/eigen_model_load.pre b/tests/phase2/builtins/builtins/eigen_model_load.pre new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_model_load.pre @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/eigen_model_load.tag b/tests/phase2/builtins/builtins/eigen_model_load.tag new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/eigen_model_load.tag @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/exec_capture.branch b/tests/phase2/builtins/builtins/exec_capture.branch new file mode 100644 index 000000000..1df06cd10 --- /dev/null +++ b/tests/phase2/builtins/builtins/exec_capture.branch @@ -0,0 +1,4 @@ +[0, "int", "hi"] +["timeout-magnitude", "value"] +[0, "int", "hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/exec_capture.eigs b/tests/phase2/builtins/builtins/exec_capture.eigs new file mode 100644 index 000000000..b0ae1bad6 --- /dev/null +++ b/tests/phase2/builtins/builtins/exec_capture.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: exec_capture (other, Q7 magnitude; the timeout seconds; returns exit status ints); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is exec_capture of ["/bin/echo", "hi"] +print of [r[0], nt of r[0], trim of r[1]] +try: + print of ["huge-timeout-answer", type of (exec_capture of [["/bin/echo", "hi"], 1e300])] +catch e: + print of ["timeout-magnitude", e.kind] +t is exec_capture of [["/bin/echo", "hi"], 5] +print of [t[0], nt of t[0], trim of t[1]] diff --git a/tests/phase2/builtins/builtins/exec_capture.pre b/tests/phase2/builtins/builtins/exec_capture.pre new file mode 100644 index 000000000..7c4c68d73 --- /dev/null +++ b/tests/phase2/builtins/builtins/exec_capture.pre @@ -0,0 +1,4 @@ +[0, "float", "hi"] +["huge-timeout-answer", "list"] +[0, "float", "hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/exec_capture.tag b/tests/phase2/builtins/builtins/exec_capture.tag new file mode 100644 index 000000000..33ea5763c --- /dev/null +++ b/tests/phase2/builtins/builtins/exec_capture.tag @@ -0,0 +1,4 @@ +[0, "undefined_name", "hi"] +["huge-timeout-answer", "list"] +[0, "undefined_name", "hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/exit.branch b/tests/phase2/builtins/builtins/exit.branch new file mode 100644 index 000000000..86fcba496 --- /dev/null +++ b/tests/phase2/builtins/builtins/exit.branch @@ -0,0 +1,4 @@ +start +["code-fraction", "value"] +["code-past-int", "value"] +rc=3 diff --git a/tests/phase2/builtins/builtins/exit.eigs b/tests/phase2/builtins/builtins/exit.eigs new file mode 100644 index 000000000..135008503 --- /dev/null +++ b/tests/phase2/builtins/builtins/exit.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: exit (int-requiring; exit code: N5, then the C int range (Q1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"exit-code":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of "start" +try: + exit of 2.5 +catch e: + print of ["code-fraction", e.kind] +try: + exit of 4294967296 +catch e: + print of ["code-past-int", e.kind] +exit of 3.0 diff --git a/tests/phase2/builtins/builtins/exit.pre b/tests/phase2/builtins/builtins/exit.pre new file mode 100644 index 000000000..7c9227695 --- /dev/null +++ b/tests/phase2/builtins/builtins/exit.pre @@ -0,0 +1,2 @@ +start +rc=2 diff --git a/tests/phase2/builtins/builtins/exit.tag b/tests/phase2/builtins/builtins/exit.tag new file mode 100644 index 000000000..7c9227695 --- /dev/null +++ b/tests/phase2/builtins/builtins/exit.tag @@ -0,0 +1,2 @@ +start +rc=2 diff --git a/tests/phase2/builtins/builtins/exp.branch b/tests/phase2/builtins/builtins/exp.branch new file mode 100644 index 000000000..a2f48d449 --- /dev/null +++ b/tests/phase2/builtins/builtins/exp.branch @@ -0,0 +1,3 @@ +[1.0, "float"] +[2.718281828459045, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/exp.eigs b/tests/phase2/builtins/builtins/exp.eigs new file mode 100644 index 000000000..6a6037485 --- /dev/null +++ b/tests/phase2/builtins/builtins/exp.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: exp (float-producing; Lua math.exp) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is exp of 0 +print of [a, nt of a] +b is exp of 1 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/exp.pre b/tests/phase2/builtins/builtins/exp.pre new file mode 100644 index 000000000..1c199dbc0 --- /dev/null +++ b/tests/phase2/builtins/builtins/exp.pre @@ -0,0 +1,3 @@ +[1, "float"] +[2.718281828459045, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/exp.tag b/tests/phase2/builtins/builtins/exp.tag new file mode 100644 index 000000000..d819b75c4 --- /dev/null +++ b/tests/phase2/builtins/builtins/exp.tag @@ -0,0 +1,3 @@ +[1, "undefined_name"] +[2.718281828459045, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_from_bytes.branch b/tests/phase2/builtins/builtins/f64_from_bytes.branch new file mode 100644 index 000000000..3b4027a6c --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_from_bytes.branch @@ -0,0 +1,5 @@ +[1.0, "float"] +["byte-fraction", "value"] +["byte-319", "value"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_from_bytes.eigs b/tests/phase2/builtins/builtins/f64_from_bytes.eigs new file mode 100644 index 000000000..dcde5fd33 --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_from_bytes.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: f64_from_bytes (int-requiring; 8 byte values: N5, then 0..255 (Q1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +v is f64_from_bytes of [63, 240, 0, 0, 0, 0, 0, 0] +print of [v, nt of v] +try: + print of ["fraction-answer", f64_from_bytes of [63.5, 240, 0, 0, 0, 0, 0, 0]] +catch e: + print of ["byte-fraction", e.kind] +try: + print of ["319-answer", f64_from_bytes of [319, 240, 0, 0, 0, 0, 0, 0]] +catch e: + print of ["byte-319", e.kind] +w is f64_from_bytes of [64.0, 4.0, 0, 0, 0, 0, 0, 0] +print of [w, nt of w] diff --git a/tests/phase2/builtins/builtins/f64_from_bytes.pre b/tests/phase2/builtins/builtins/f64_from_bytes.pre new file mode 100644 index 000000000..af5575e49 --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_from_bytes.pre @@ -0,0 +1,5 @@ +[1, "float"] +["fraction-answer", 1] +["319-answer", 1] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_from_bytes.tag b/tests/phase2/builtins/builtins/f64_from_bytes.tag new file mode 100644 index 000000000..1529a01af --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_from_bytes.tag @@ -0,0 +1,5 @@ +[1, "undefined_name"] +["fraction-answer", 1] +["319-answer", 1] +[2.5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_to_bytes.branch b/tests/phase2/builtins/builtins/f64_to_bytes.branch new file mode 100644 index 000000000..84e596f8c --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_to_bytes.branch @@ -0,0 +1,4 @@ +[[63, 240, 0, 0, 0, 0, 0, 0], "int", "int"] +[64, 4, 0, 0, 0, 0, 0, 0] +[67, 64, 0, 0, 0, 0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_to_bytes.eigs b/tests/phase2/builtins/builtins/f64_to_bytes.eigs new file mode 100644 index 000000000..c88581e94 --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_to_bytes.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: f64_to_bytes (other; float to 8 byte ints) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is f64_to_bytes of 1 +print of [a, nt of a[0], nt of a[1]] +b is f64_to_bytes of 2.5 +print of b +print of (f64_to_bytes of 9007199254740993) diff --git a/tests/phase2/builtins/builtins/f64_to_bytes.pre b/tests/phase2/builtins/builtins/f64_to_bytes.pre new file mode 100644 index 000000000..3032353d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_to_bytes.pre @@ -0,0 +1,4 @@ +[[63, 240, 0, 0, 0, 0, 0, 0], "float", "float"] +[64, 4, 0, 0, 0, 0, 0, 0] +[67, 64, 0, 0, 0, 0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/f64_to_bytes.tag b/tests/phase2/builtins/builtins/f64_to_bytes.tag new file mode 100644 index 000000000..0517492af --- /dev/null +++ b/tests/phase2/builtins/builtins/f64_to_bytes.tag @@ -0,0 +1,4 @@ +[[63, 240, 0, 0, 0, 0, 0, 0], "undefined_name", "undefined_name"] +[64, 4, 0, 0, 0, 0, 0, 0] +[67, 64, 0, 0, 0, 0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/fill.branch b/tests/phase2/builtins/builtins/fill.branch new file mode 100644 index 000000000..feebd07eb --- /dev/null +++ b/tests/phase2/builtins/builtins/fill.branch @@ -0,0 +1,4 @@ +[0, 0, 0] +["count-fraction", "value"] +[1, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/fill.eigs b/tests/phase2/builtins/builtins/fill.eigs new file mode 100644 index 000000000..2e66f29c8 --- /dev/null +++ b/tests/phase2/builtins/builtins/fill.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: fill (int-requiring; count); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (fill of [3, 0]) +try: + print of ["fraction-answer", fill of [2.5, 0]] +catch e: + print of ["count-fraction", e.kind] +print of (fill of [2.0, 1]) diff --git a/tests/phase2/builtins/builtins/fill.pre b/tests/phase2/builtins/builtins/fill.pre new file mode 100644 index 000000000..25b7e5fdb --- /dev/null +++ b/tests/phase2/builtins/builtins/fill.pre @@ -0,0 +1,4 @@ +[0, 0, 0] +["fraction-answer", [0, 0]] +[1, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/fill.tag b/tests/phase2/builtins/builtins/fill.tag new file mode 100644 index 000000000..25b7e5fdb --- /dev/null +++ b/tests/phase2/builtins/builtins/fill.tag @@ -0,0 +1,4 @@ +[0, 0, 0] +["fraction-answer", [0, 0]] +[1, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/floor.branch b/tests/phase2/builtins/builtins/floor.branch new file mode 100644 index 000000000..c4e148f20 --- /dev/null +++ b/tests/phase2/builtins/builtins/floor.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[-3, "int"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/floor.eigs b/tests/phase2/builtins/builtins/floor.eigs new file mode 100644 index 000000000..3d1d4d1da --- /dev/null +++ b/tests/phase2/builtins/builtins/floor.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: floor (int-preserving, LOSS; Lua math.floor: an int when the result fits int64, else a float) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is floor of 9007199254740993 +print of [a, nt of a] +b is floor of -2.5 +print of [b, nt of b] +c is floor of 1e300 +print of [c, nt of c] diff --git a/tests/phase2/builtins/builtins/floor.pre b/tests/phase2/builtins/builtins/floor.pre new file mode 100644 index 000000000..c065dba72 --- /dev/null +++ b/tests/phase2/builtins/builtins/floor.pre @@ -0,0 +1,4 @@ +[9007199254740992, "float"] +[-3, "float"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/floor.tag b/tests/phase2/builtins/builtins/floor.tag new file mode 100644 index 000000000..b4d80faa1 --- /dev/null +++ b/tests/phase2/builtins/builtins/floor.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +[-3, "undefined_name"] +[1e+300, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/fround.branch b/tests/phase2/builtins/builtins/fround.branch new file mode 100644 index 000000000..3bf8a39ed --- /dev/null +++ b/tests/phase2/builtins/builtins/fround.branch @@ -0,0 +1,8 @@ +[0.10000000149011612, "float"] +[1e+308, "float"] +[-1e+308, "float"] +[16777216.0, "float"] +[9007199254740992.0, "float"] +["non-number", "type_mismatch"] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/fround.eigs b/tests/phase2/builtins/builtins/fround.eigs new file mode 100644 index 000000000..26a8872bd --- /dev/null +++ b/tests/phase2/builtins/builtins/fround.eigs @@ -0,0 +1,32 @@ +# phase2-exercises: {"q7": []} +try: + x is fround of 0.1 + print of [x, num_type of x] +catch e: + print of ["float", e.kind, (index_of of [e.message, "fround"]) >= 0] +try: + x is fround of 1e39 + print of [x, num_type of x] +catch e: + print of ["float", e.kind, (index_of of [e.message, "fround"]) >= 0] +try: + x is fround of -1e39 + print of [x, num_type of x] +catch e: + print of ["float", e.kind, (index_of of [e.message, "fround"]) >= 0] +try: + x is fround of 16777217 + print of [x, num_type of x] +catch e: + print of ["float", e.kind, (index_of of [e.message, "fround"]) >= 0] +try: + x is fround of 9007199254740993 + print of [x, num_type of x] +catch e: + print of ["float", e.kind, (index_of of [e.message, "fround"]) >= 0] +try: + x is fround of "bad" + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +print of (math_flags of null).overflow diff --git a/tests/phase2/builtins/builtins/fround.pre b/tests/phase2/builtins/builtins/fround.pre new file mode 100644 index 000000000..a47476079 --- /dev/null +++ b/tests/phase2/builtins/builtins/fround.pre @@ -0,0 +1,8 @@ +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["non-number", "undefined_name"] +false +rc=0 diff --git a/tests/phase2/builtins/builtins/fround.tag b/tests/phase2/builtins/builtins/fround.tag new file mode 100644 index 000000000..a47476079 --- /dev/null +++ b/tests/phase2/builtins/builtins/fround.tag @@ -0,0 +1,8 @@ +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["float", "undefined_name", true] +["non-number", "undefined_name"] +false +rc=0 diff --git a/tests/phase2/builtins/builtins/gather.branch b/tests/phase2/builtins/builtins/gather.branch new file mode 100644 index 000000000..8427716d2 --- /dev/null +++ b/tests/phase2/builtins/builtins/gather.branch @@ -0,0 +1,5 @@ +["index-fraction", "value"] +[11.0, 22.0, "float"] +[[2, 3], "int"] +["list-index-fraction", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/gather.eigs b/tests/phase2/builtins/builtins/gather.eigs new file mode 100644 index 000000000..83c4da245 --- /dev/null +++ b/tests/phase2/builtins/builtins/gather.eigs @@ -0,0 +1,25 @@ +# R1.6 behaviour-table probe: gather (int-requiring; each index is integer-valued (N5); returns elements unchanged); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +t is reshape of [buf_from_list of [10, 11, 12, 20, 21, 22], 2, 3] +ib is buffer of 2 +ib[0] is 1.5 +ib[1] is 2.0 +try: + g is gather of [t, ib] + print of ["fraction-answer", g[0], g[1]] +catch e: + print of ["index-fraction", e.kind] +ib[0] is 1.0 +h is gather of [t, ib] +print of [h[0], h[1], nt of h[0]] +xs is [[1, 2], [3, 4]] +k is gather of [xs, [1, 0]] +print of [k, nt of k[0]] +try: + print of ["fraction-answer", gather of [xs, [0.5, 0]]] +catch e: + print of ["list-index-fraction", e.kind] diff --git a/tests/phase2/builtins/builtins/gather.pre b/tests/phase2/builtins/builtins/gather.pre new file mode 100644 index 000000000..727359eb0 --- /dev/null +++ b/tests/phase2/builtins/builtins/gather.pre @@ -0,0 +1,5 @@ +["fraction-answer", 11, 22] +[11, 22, "float"] +[[2, 3], "float"] +["fraction-answer", [1, 3]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gather.tag b/tests/phase2/builtins/builtins/gather.tag new file mode 100644 index 000000000..215ecd504 --- /dev/null +++ b/tests/phase2/builtins/builtins/gather.tag @@ -0,0 +1,5 @@ +["fraction-answer", 11, 22] +[11, 22, "undefined_name"] +[[2, 3], "undefined_name"] +["fraction-answer", [1, 3]] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_at.branch b/tests/phase2/builtins/builtins/get_at.branch new file mode 100644 index 000000000..94f2416b7 --- /dev/null +++ b/tests/phase2/builtins/builtins/get_at.branch @@ -0,0 +1,7 @@ +20 +["index-fraction", "value"] +20 +[5.0, "float"] +3 +["col-fraction", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_at.eigs b/tests/phase2/builtins/builtins/get_at.eigs new file mode 100644 index 000000000..5ca0cab84 --- /dev/null +++ b/tests/phase2/builtins/builtins/get_at.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: get_at (int-requiring; index, row and col; returns the element); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [10, 20, 30] +print of (get_at of [xs, 1]) +try: + print of ["fraction-answer", get_at of [xs, 1.5]] +catch e: + print of ["index-fraction", e.kind] +print of (get_at of [xs, 1.0]) +b is buf_from_list of [4, 5] +g is get_at of [b, 1] +print of [g, nt of g] +m is [[1, 2], [3, 4]] +print of (get_at of [m, 1, 0]) +try: + print of ["fraction-answer", get_at of [m, 1, 0.5]] +catch e: + print of ["col-fraction", e.kind] diff --git a/tests/phase2/builtins/builtins/get_at.pre b/tests/phase2/builtins/builtins/get_at.pre new file mode 100644 index 000000000..d0759f5e2 --- /dev/null +++ b/tests/phase2/builtins/builtins/get_at.pre @@ -0,0 +1,7 @@ +20 +["fraction-answer", 20] +20 +[5, "float"] +3 +["fraction-answer", 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_at.tag b/tests/phase2/builtins/builtins/get_at.tag new file mode 100644 index 000000000..5c30eba5d --- /dev/null +++ b/tests/phase2/builtins/builtins/get_at.tag @@ -0,0 +1,7 @@ +20 +["fraction-answer", 20] +20 +[5, "undefined_name"] +3 +["fraction-answer", 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_observer_thresholds.branch b/tests/phase2/builtins/builtins/get_observer_thresholds.branch new file mode 100644 index 000000000..b693e7a8b --- /dev/null +++ b/tests/phase2/builtins/builtins/get_observer_thresholds.branch @@ -0,0 +1,3 @@ +[0.001, 0.01, 0.1] +["float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_observer_thresholds.eigs b/tests/phase2/builtins/builtins/get_observer_thresholds.eigs new file mode 100644 index 000000000..40b79a57b --- /dev/null +++ b/tests/phase2/builtins/builtins/get_observer_thresholds.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: get_observer_thresholds (float-producing; observer thresholds) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +t is get_observer_thresholds of null +print of t +print of [nt of t[0], nt of t[1], nt of t[2]] diff --git a/tests/phase2/builtins/builtins/get_observer_thresholds.pre b/tests/phase2/builtins/builtins/get_observer_thresholds.pre new file mode 100644 index 000000000..b693e7a8b --- /dev/null +++ b/tests/phase2/builtins/builtins/get_observer_thresholds.pre @@ -0,0 +1,3 @@ +[0.001, 0.01, 0.1] +["float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/get_observer_thresholds.tag b/tests/phase2/builtins/builtins/get_observer_thresholds.tag new file mode 100644 index 000000000..4f87dd7b4 --- /dev/null +++ b/tests/phase2/builtins/builtins/get_observer_thresholds.tag @@ -0,0 +1,3 @@ +[0.001, 0.01, 0.1] +["undefined_name", "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_circle.branch b/tests/phase2/builtins/builtins/gfx_circle.branch new file mode 100644 index 000000000..4c184e269 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_circle.branch @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0]] +[255, 0, 128] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_circle.eigs b/tests/phase2/builtins/builtins/gfx_circle.eigs new file mode 100644 index 000000000..09a24d968 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_circle.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: gfx_circle (other, Q7 geometry,bounded; the circle is clipped in float space, then rounded); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_circle of [32, 24, 3e9, 0, 255, 0] +print of [gfx_read of [0, 0], gfx_read of [63, 47], gfx_read of [32, 24]] +gfx_clear of [0, 0, 0] +gfx_circle of [10, 10, 2, 300, -20, 127.6] +print of (gfx_read of [10, 10]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_circle.pre b/tests/phase2/builtins/builtins/gfx_circle.pre new file mode 100644 index 000000000..421b6949c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_circle.pre @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_circle.tag b/tests/phase2/builtins/builtins/gfx_circle.tag new file mode 100644 index 000000000..421b6949c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_circle.tag @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clear.branch b/tests/phase2/builtins/builtins/gfx_clear.branch new file mode 100644 index 000000000..5d3bf97c7 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clear.branch @@ -0,0 +1,4 @@ +["open", true] +[[255, 0, 128], [255, 0, 128]] +[0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clear.eigs b/tests/phase2/builtins/builtins/gfx_clear.eigs new file mode 100644 index 000000000..2ad82468a --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clear.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: gfx_clear (other, Q7 bounded; colour channels clamp to 0..255, then round); phase2-exercises: {"q7":["bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_clear of [300, -20, 127.6] +print of [gfx_read of [0, 0], gfx_read of [63, 47]] +gfx_clear of null +print of (gfx_read of [5, 5]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_clear.pre b/tests/phase2/builtins/builtins/gfx_clear.pre new file mode 100644 index 000000000..e9dd702d1 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clear.pre @@ -0,0 +1,4 @@ +["open", true] +[[44, 236, 127], [44, 236, 127]] +[0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clear.tag b/tests/phase2/builtins/builtins/gfx_clear.tag new file mode 100644 index 000000000..e9dd702d1 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clear.tag @@ -0,0 +1,4 @@ +["open", true] +[[44, 236, 127], [44, 236, 127]] +[0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clip.branch b/tests/phase2/builtins/builtins/gfx_clip.branch new file mode 100644 index 000000000..e2d3080d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clip.branch @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 0, 0]] +[0, 0, 255] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clip.eigs b/tests/phase2/builtins/builtins/gfx_clip.eigs new file mode 100644 index 000000000..4a4b7f326 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clip.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_clip (other, Q7 geometry; the clip rect is intersected with the drawable area in float space, then rounded); phase2-exercises: {"q7":["geometry"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_clip of [-3e9, 0, 6e9, 4] +gfx_rect of [0, 0, 64, 48, 0, 255, 0] +print of [gfx_read of [0, 1], gfx_read of [63, 3], gfx_read of [32, 10]] +gfx_clip of null +gfx_rect of [0, 0, 64, 48, 0, 0, 255] +print of (gfx_read of [32, 10]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_clip.pre b/tests/phase2/builtins/builtins/gfx_clip.pre new file mode 100644 index 000000000..7a5494655 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clip.pre @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0]] +[0, 0, 255] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_clip.tag b/tests/phase2/builtins/builtins/gfx_clip.tag new file mode 100644 index 000000000..7a5494655 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_clip.tag @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0]] +[0, 0, 255] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_delay.branch b/tests/phase2/builtins/builtins/gfx_delay.branch new file mode 100644 index 000000000..2315755ca --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_delay.branch @@ -0,0 +1,4 @@ +["open", true] +null +["delay-magnitude", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_delay.eigs b/tests/phase2/builtins/builtins/gfx_delay.eigs new file mode 100644 index 000000000..9c32e512f --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_delay.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: gfx_delay (other, Q7 magnitude; the milliseconds round, a delay Uint32 cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +print of (gfx_delay of 1.6) +try: + print of ["huge-delay-answer", gfx_delay of 1e300] +catch e: + print of ["delay-magnitude", e.kind] +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_delay.pre b/tests/phase2/builtins/builtins/gfx_delay.pre new file mode 100644 index 000000000..a611ac455 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_delay.pre @@ -0,0 +1,4 @@ +["open", true] +null +["huge-delay-answer", null] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_delay.tag b/tests/phase2/builtins/builtins/gfx_delay.tag new file mode 100644 index 000000000..a611ac455 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_delay.tag @@ -0,0 +1,4 @@ +["open", true] +null +["huge-delay-answer", null] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_fb.branch b/tests/phase2/builtins/builtins/gfx_fb.branch new file mode 100644 index 000000000..3bbd036ea --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_fb.branch @@ -0,0 +1,8 @@ +["open", true] +[[255, 255, 255], [170, 170, 170], [85, 85, 85], [0, 0, 0], [255, 0, 0]] +["w-fraction", "value"] +[[0, 0, 0], [255, 0, 0]] +["byte-300", "value"] +["byte-fraction", "value"] +[[0, 0, 0], [0, 0, 0], [0, 0, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_fb.eigs b/tests/phase2/builtins/builtins/gfx_fb.eigs new file mode 100644 index 000000000..fc1a7165c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_fb.eigs @@ -0,0 +1,35 @@ +# R1.6 behaviour-table probe: gfx_fb (int-requiring, Q7 geometry; w and h by N5, palette bytes by the Q1 byte reader, the destination rect clipped in float space); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"q7":["geometry"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_clear of [255, 0, 0] +fb is buf_from_list of [0, 1, 2, 3] +gfx_fb of [fb, 2, 2, 0, 0, 1] +print of [gfx_read of [0, 0], gfx_read of [1, 0], gfx_read of [0, 1], gfx_read of [1, 1], gfx_read of [2, 0]] +try: + gfx_fb of [fb, 2.5, 2, 0, 0, 1] + print of "w fraction accepted" +catch e: + print of ["w-fraction", e.kind] +gfx_clear of [255, 0, 0] +gfx_fb of [fb, 2.0, 2.0, 0, 0, 1] +print of [gfx_read of [1, 1], gfx_read of [2, 0]] +try: + gfx_fb of [buf_from_list of [300, 0, 0, 0], 2, 2, 0, 0, 1] + print of "byte 300 accepted" +catch e: + print of ["byte-300", e.kind] +try: + gfx_fb of [buf_from_list of [1.5, 0, 0, 0], 2, 2, 0, 0, 1] + print of "byte fraction accepted" +catch e: + print of ["byte-fraction", e.kind] +gfx_clear of [255, 0, 0] +gfx_fb of [buf_from_list of ([3]), 1, 1, -3e9, -3e9, 6e9] +print of [gfx_read of [0, 0], gfx_read of [32, 24], gfx_read of [63, 47]] +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_fb.pre b/tests/phase2/builtins/builtins/gfx_fb.pre new file mode 100644 index 000000000..5e18ce9a4 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_fb.pre @@ -0,0 +1,8 @@ +["open", true] +[[255, 255, 255], [170, 170, 170], [85, 85, 85], [0, 0, 0], [255, 0, 0]] +w fraction accepted +[[0, 0, 0], [255, 0, 0]] +byte 300 accepted +byte fraction accepted +[[255, 0, 0], [255, 0, 0], [255, 0, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_fb.tag b/tests/phase2/builtins/builtins/gfx_fb.tag new file mode 100644 index 000000000..5e18ce9a4 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_fb.tag @@ -0,0 +1,8 @@ +["open", true] +[[255, 255, 255], [170, 170, 170], [85, 85, 85], [0, 0, 0], [255, 0, 0]] +w fraction accepted +[[0, 0, 0], [255, 0, 0]] +byte 300 accepted +byte fraction accepted +[[255, 0, 0], [255, 0, 0], [255, 0, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_line.branch b/tests/phase2/builtins/builtins/gfx_line.branch new file mode 100644 index 000000000..eff4bfd1d --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_line.branch @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 0, 0]] +[255, 0, 128] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_line.eigs b/tests/phase2/builtins/builtins/gfx_line.eigs new file mode 100644 index 000000000..904bcbed9 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_line.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: gfx_line (other, Q7 geometry,bounded; the segment is clipped in float space (Liang-Barsky), then rounded); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_line of [-3e9, 5, 3e9, 5, 0, 255, 0] +print of [gfx_read of [0, 5], gfx_read of [32, 5], gfx_read of [63, 5], gfx_read of [32, 6]] +gfx_line of [10, 20, 14, 20, 300, -20, 127.6] +print of (gfx_read of [12, 20]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_line.pre b/tests/phase2/builtins/builtins/gfx_line.pre new file mode 100644 index 000000000..aef7b403b --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_line.pre @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_line.tag b/tests/phase2/builtins/builtins/gfx_line.tag new file mode 100644 index 000000000..aef7b403b --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_line.tag @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_open.branch b/tests/phase2/builtins/builtins/gfx_open.branch new file mode 100644 index 000000000..06f026f84 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_open.branch @@ -0,0 +1,4 @@ +["size-magnitude", "value"] +["open", true] +[[0, 255, 0], [0, 255, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_open.eigs b/tests/phase2/builtins/builtins/gfx_open.eigs new file mode 100644 index 000000000..03c5c602f --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_open.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_open (other, Q7 magnitude; window sizes round, a size the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of ["huge-size-answer", gfx_open of [3e9, 48, "r16"]] +catch e: + print of ["size-magnitude", e.kind] +ok is gfx_open of [64.4, 47.6, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_rect of [0, 0, 64, 48, 0, 255, 0] +print of [gfx_read of [63, 47], gfx_read of [0, 0]] +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_open.pre b/tests/phase2/builtins/builtins/gfx_open.pre new file mode 100644 index 000000000..85d907b1d --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_open.pre @@ -0,0 +1,4 @@ +["huge-size-answer", true] +["open", true] +[[0, 0, 0], [0, 255, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_open.tag b/tests/phase2/builtins/builtins/gfx_open.tag new file mode 100644 index 000000000..85d907b1d --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_open.tag @@ -0,0 +1,4 @@ +["huge-size-answer", true] +["open", true] +[[0, 0, 0], [0, 255, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_point.branch b/tests/phase2/builtins/builtins/gfx_point.branch new file mode 100644 index 000000000..12c48f024 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_point.branch @@ -0,0 +1,5 @@ +["open", true] +[0, 0, 0] +[[0, 0, 0], [0, 0, 255]] +[255, 0, 128] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_point.eigs b/tests/phase2/builtins/builtins/gfx_point.eigs new file mode 100644 index 000000000..9d547ff28 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_point.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_point (other, Q7 geometry,bounded; a point outside the drawable area draws nothing); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_point of [-0.6, 5, 0, 255, 0] +print of (gfx_read of [0, 5]) +gfx_point of [10.6, 7, 0, 0, 255] +print of [gfx_read of [10, 7], gfx_read of [11, 7]] +gfx_point of [20, 20, 300, -20, 127.6] +print of (gfx_read of [20, 20]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_point.pre b/tests/phase2/builtins/builtins/gfx_point.pre new file mode 100644 index 000000000..0e0625538 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_point.pre @@ -0,0 +1,5 @@ +["open", true] +[0, 255, 0] +[[0, 0, 255], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_point.tag b/tests/phase2/builtins/builtins/gfx_point.tag new file mode 100644 index 000000000..0e0625538 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_point.tag @@ -0,0 +1,5 @@ +["open", true] +[0, 255, 0] +[[0, 0, 255], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.branch b/tests/phase2/builtins/builtins/gfx_poll.branch new file mode 100644 index 000000000..426de0a68 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.branch @@ -0,0 +1,5 @@ +[17, "int", -9, "int"] +[4, "int"] +[1, "int", 23, "int", 42, "int"] +hook-loaded events +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.eigs b/tests/phase2/builtins/builtins/gfx_poll.eigs new file mode 100644 index 000000000..a017a97f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_poll (int-preserving; event fields x, y, scancode, button are ints; HOOK events: SDL_PollEvent delivers motion, key and button events (ruling B3)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +log is env_get of "R16_HOOK_LOG" +ok is gfx_open of [64, 48, "r16"] +m is gfx_poll of null +print of [m["x"], nt of m["x"], m["y"], nt of m["y"]] +k is gfx_poll of null +print of [k["scancode"], nt of k["scancode"]] +b is gfx_poll of null +print of [b["button"], nt of b["button"], b["x"], nt of b["x"], b["y"], nt of b["y"]] +gfx_close of null +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/gfx_poll.empty.branch b/tests/phase2/builtins/builtins/gfx_poll.empty.branch new file mode 100644 index 000000000..5bfce60c6 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.empty.branch @@ -0,0 +1,3 @@ +["open", true] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.empty.eigs b/tests/phase2/builtins/builtins/gfx_poll.empty.eigs new file mode 100644 index 000000000..f95dab655 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.empty.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: gfx_poll, case empty (int-preserving; an empty event queue answers null; no hook) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +print of (gfx_poll of null) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_poll.empty.pre b/tests/phase2/builtins/builtins/gfx_poll.empty.pre new file mode 100644 index 000000000..5bfce60c6 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.empty.pre @@ -0,0 +1,3 @@ +["open", true] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.empty.tag b/tests/phase2/builtins/builtins/gfx_poll.empty.tag new file mode 100644 index 000000000..5bfce60c6 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.empty.tag @@ -0,0 +1,3 @@ +["open", true] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.pre b/tests/phase2/builtins/builtins/gfx_poll.pre new file mode 100644 index 000000000..a700870f0 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.pre @@ -0,0 +1,5 @@ +[17, "float", -9, "float"] +[4, "float"] +[1, "float", 23, "float", 42, "float"] +hook-loaded events +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_poll.tag b/tests/phase2/builtins/builtins/gfx_poll.tag new file mode 100644 index 000000000..74d3ca0a9 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_poll.tag @@ -0,0 +1,5 @@ +[17, "undefined_name", -9, "undefined_name"] +[4, "undefined_name"] +[1, "undefined_name", 23, "undefined_name", 42, "undefined_name"] +hook-loaded events +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_read.branch b/tests/phase2/builtins/builtins/gfx_read.branch new file mode 100644 index 000000000..d05cefd2e --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_read.branch @@ -0,0 +1,6 @@ +["open", true] +[[255, 0, 0], "int"] +[255, 0, 0] +null +null +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_read.eigs b/tests/phase2/builtins/builtins/gfx_read.eigs new file mode 100644 index 000000000..e6eaa4c4e --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_read.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_read (other, Q7 geometry; the origin rounds, a point outside the drawable area reads nothing; channel values are ints); phase2-exercises: {"q7":["geometry"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_rect of [10, 10, 1, 1, 255, 0, 0] +r is gfx_read of [10, 10] +print of [r, nt of r[0]] +print of (gfx_read of [9.6, 10.4]) +print of (gfx_read of [3e9, 0]) +print of (gfx_read of [-0.6, 0]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_read.pre b/tests/phase2/builtins/builtins/gfx_read.pre new file mode 100644 index 000000000..7131da664 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_read.pre @@ -0,0 +1,6 @@ +["open", true] +[[255, 0, 0], "float"] +[0, 0, 0] +[0, 0, 0] +[0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_read.tag b/tests/phase2/builtins/builtins/gfx_read.tag new file mode 100644 index 000000000..80df0b993 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_read.tag @@ -0,0 +1,6 @@ +["open", true] +[[255, 0, 0], "undefined_name"] +[0, 0, 0] +[0, 0, 0] +[0, 0, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rect.branch b/tests/phase2/builtins/builtins/gfx_rect.branch new file mode 100644 index 000000000..ae0cec607 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rect.branch @@ -0,0 +1,5 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 0, 0]] +[255, 0, 128] +[[0, 0, 0], [0, 0, 255], [0, 0, 255]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rect.eigs b/tests/phase2/builtins/builtins/gfx_rect.eigs new file mode 100644 index 000000000..fb107d4e9 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rect.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: gfx_rect (other, Q7 geometry,bounded; the rect is intersected with the drawable area in float space, then rounded); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_rect of [-3e9, 0, 6e9, 4, 0, 255, 0] +print of [gfx_read of [0, 1], gfx_read of [32, 1], gfx_read of [63, 3], gfx_read of [32, 5]] +gfx_rect of [10, 10, 4, 4, 300, -20, 127.6] +print of (gfx_read of [11, 11]) +gfx_rect of [20.5, 20, 2, 2, 0, 0, 255] +print of [gfx_read of [20, 20], gfx_read of [21, 20], gfx_read of [22, 20]] +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_rect.pre b/tests/phase2/builtins/builtins/gfx_rect.pre new file mode 100644 index 000000000..20ac573e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rect.pre @@ -0,0 +1,5 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +[[0, 0, 255], [0, 0, 255], [0, 0, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rect.tag b/tests/phase2/builtins/builtins/gfx_rect.tag new file mode 100644 index 000000000..20ac573e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rect.tag @@ -0,0 +1,5 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +[[0, 0, 255], [0, 0, 255], [0, 0, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rrect.branch b/tests/phase2/builtins/builtins/gfx_rrect.branch new file mode 100644 index 000000000..4c184e269 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rrect.branch @@ -0,0 +1,4 @@ +["open", true] +[[0, 255, 0], [0, 255, 0], [0, 255, 0]] +[255, 0, 128] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rrect.eigs b/tests/phase2/builtins/builtins/gfx_rrect.eigs new file mode 100644 index 000000000..a37b2b49a --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rrect.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: gfx_rrect (other, Q7 geometry,bounded; the rect and radius are clipped in float space, then rounded); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_rrect of [-3e9, -3e9, 6e9, 6e9, 10, 0, 255, 0] +print of [gfx_read of [0, 0], gfx_read of [63, 47], gfx_read of [32, 24]] +gfx_clear of [0, 0, 0] +gfx_rrect of [8, 8, 6, 6, 0, 300, -20, 127.6] +print of (gfx_read of [10, 10]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_rrect.pre b/tests/phase2/builtins/builtins/gfx_rrect.pre new file mode 100644 index 000000000..421b6949c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rrect.pre @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_rrect.tag b/tests/phase2/builtins/builtins/gfx_rrect.tag new file mode 100644 index 000000000..421b6949c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_rrect.tag @@ -0,0 +1,4 @@ +["open", true] +[[0, 0, 0], [0, 0, 0], [0, 0, 0]] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text.branch b/tests/phase2/builtins/builtins/gfx_text.branch new file mode 100644 index 000000000..0712a6420 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text.branch @@ -0,0 +1,5 @@ +["open", true] +[[0, 0, 0], [0, 255, 0], [0, 255, 0]] +[0, 255, 0] +[255, 0, 128] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text.eigs b/tests/phase2/builtins/builtins/gfx_text.eigs new file mode 100644 index 000000000..c530b7872 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: gfx_text (other, Q7 geometry,bounded; each glyph cell is clipped in float space, then rounded (bitmap font: EIGS_GFX_FONT is a nonexistent path)); phase2-exercises: {"q7":["geometry","bounded"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ok is gfx_open of [64, 48, "r16"] +print of ["open", ok] +gfx_clear of [0, 0, 0] +gfx_text of [0.6, 0, "T", 0, 255, 0] +print of [gfx_read of [0, 0], gfx_read of [5, 0], gfx_read of [3, 3]] +gfx_clear of [0, 0, 0] +gfx_text of [0, 0, "T", 0, 255, 0, 3e9] +print of (gfx_read of [32, 24]) +gfx_clear of [0, 0, 0] +gfx_text of [10, 10, "T", 300, -20, 127.6] +print of (gfx_read of [12, 12]) +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_text.pre b/tests/phase2/builtins/builtins/gfx_text.pre new file mode 100644 index 000000000..284aeaee3 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text.pre @@ -0,0 +1,5 @@ +["open", true] +[[0, 255, 0], [0, 0, 0], [0, 0, 0]] +[0, 0, 0] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text.tag b/tests/phase2/builtins/builtins/gfx_text.tag new file mode 100644 index 000000000..284aeaee3 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text.tag @@ -0,0 +1,5 @@ +["open", true] +[[0, 255, 0], [0, 0, 0], [0, 0, 0]] +[0, 0, 0] +[44, 236, 127] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_height.branch b/tests/phase2/builtins/builtins/gfx_text_height.branch new file mode 100644 index 000000000..5da0e7fff --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_height.branch @@ -0,0 +1,5 @@ +[14, "int"] +14 +["scale-magnitude", "value"] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_height.eigs b/tests/phase2/builtins/builtins/gfx_text_height.eigs new file mode 100644 index 000000000..0e823b427 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_height.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: gfx_text_height (int-preserving, Q7 magnitude; pixel height; the scale rounds, a scale the C int cannot hold raises (bitmap font)); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +h is gfx_text_height of 2 +print of [h, nt of h] +print of (gfx_text_height of 1.6) +try: + print of ["huge-scale-answer", gfx_text_height of 3e9] +catch e: + print of ["scale-magnitude", e.kind] +print of (gfx_text_height of null) diff --git a/tests/phase2/builtins/builtins/gfx_text_height.pre b/tests/phase2/builtins/builtins/gfx_text_height.pre new file mode 100644 index 000000000..40decc3f4 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_height.pre @@ -0,0 +1,5 @@ +[14, "float"] +7 +["huge-scale-answer", 7] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_height.tag b/tests/phase2/builtins/builtins/gfx_text_height.tag new file mode 100644 index 000000000..4d3b387c3 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_height.tag @@ -0,0 +1,5 @@ +[14, "undefined_name"] +7 +["huge-scale-answer", 7] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_width.branch b/tests/phase2/builtins/builtins/gfx_text_width.branch new file mode 100644 index 000000000..d40391bc9 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_width.branch @@ -0,0 +1,5 @@ +[36, "int"] +36 +["scale-magnitude", "value"] +24 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_width.eigs b/tests/phase2/builtins/builtins/gfx_text_width.eigs new file mode 100644 index 000000000..f1ddf04bf --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_width.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: gfx_text_width (int-preserving, Q7 magnitude; pixel width; the scale rounds, a scale the C int cannot hold raises (bitmap font)); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is gfx_text_width of ["abc", 2] +print of [a, nt of a] +print of (gfx_text_width of ["abc", 1.6]) +try: + print of ["huge-scale-answer", gfx_text_width of ["abc", 3e9]] +catch e: + print of ["scale-magnitude", e.kind] +print of (gfx_text_width of "abcd") diff --git a/tests/phase2/builtins/builtins/gfx_text_width.pre b/tests/phase2/builtins/builtins/gfx_text_width.pre new file mode 100644 index 000000000..6597cb48c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_width.pre @@ -0,0 +1,5 @@ +[36, "float"] +18 +["huge-scale-answer", 18] +24 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_text_width.tag b/tests/phase2/builtins/builtins/gfx_text_width.tag new file mode 100644 index 000000000..148462ed1 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_text_width.tag @@ -0,0 +1,5 @@ +[36, "undefined_name"] +18 +["huge-scale-answer", 18] +24 +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_ticks.branch b/tests/phase2/builtins/builtins/gfx_ticks.branch new file mode 100644 index 000000000..87571883c --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_ticks.branch @@ -0,0 +1,3 @@ +[0, "int"] +["int", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_ticks.eigs b/tests/phase2/builtins/builtins/gfx_ticks.eigs new file mode 100644 index 000000000..323cac915 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_ticks.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: gfx_ticks (int-preserving; SDL ticks (Uint32 ms); prints a kind and an ordering only) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is gfx_ticks of null +print of [a, nt of a] +ok is gfx_open of [64, 48, "r16"] +b is gfx_ticks of null +c is gfx_ticks of null +print of [nt of b, c >= b] +gfx_close of null diff --git a/tests/phase2/builtins/builtins/gfx_ticks.pre b/tests/phase2/builtins/builtins/gfx_ticks.pre new file mode 100644 index 000000000..6e989ce4f --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_ticks.pre @@ -0,0 +1,3 @@ +[0, "float"] +["float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/gfx_ticks.tag b/tests/phase2/builtins/builtins/gfx_ticks.tag new file mode 100644 index 000000000..f5d4f8d50 --- /dev/null +++ b/tests/phase2/builtins/builtins/gfx_ticks.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +["undefined_name", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/hex.branch b/tests/phase2/builtins/builtins/hex.branch new file mode 100644 index 000000000..74f4008f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/hex.branch @@ -0,0 +1,7 @@ +20000000000001 +00FF +["nibbles-fraction", "value"] +00FF +["fraction", "value"] +["negative", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/hex.eigs b/tests/phase2/builtins/builtins/hex.eigs new file mode 100644 index 000000000..0b18a5c9f --- /dev/null +++ b/tests/phase2/builtins/builtins/hex.eigs @@ -0,0 +1,21 @@ +# R1.6 behaviour-table probe: hex (int-requiring, LOSS; the 2^53 refusal is a double compare; nibbles is a count); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (hex of 9007199254740993) +print of (hex of [255, 4]) +try: + print of (hex of [255, 4.5]) +catch e: + print of ["nibbles-fraction", e.kind] +print of (hex of [255.0, 4.0]) +try: + print of (hex of 2.5) +catch e: + print of ["fraction", e.kind] +try: + print of ["negative-answer", hex of -1] +catch e: + print of ["negative", e.kind] diff --git a/tests/phase2/builtins/builtins/hex.pre b/tests/phase2/builtins/builtins/hex.pre new file mode 100644 index 000000000..26f473bf8 --- /dev/null +++ b/tests/phase2/builtins/builtins/hex.pre @@ -0,0 +1,7 @@ +20000000000000 +00FF +00FF +00FF +["fraction", "value"] +["negative", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/hex.tag b/tests/phase2/builtins/builtins/hex.tag new file mode 100644 index 000000000..26f473bf8 --- /dev/null +++ b/tests/phase2/builtins/builtins/hex.tag @@ -0,0 +1,7 @@ +20000000000000 +00FF +00FF +00FF +["fraction", "value"] +["negative", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_early_bind.branch b/tests/phase2/builtins/builtins/http_early_bind.branch new file mode 100644 index 000000000..afde0776f --- /dev/null +++ b/tests/phase2/builtins/builtins/http_early_bind.branch @@ -0,0 +1,5 @@ +["port-fraction", "value", false] +["port-65536", "value", false] +["integral-float", "bound"] +["early-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_early_bind.eigs b/tests/phase2/builtins/builtins/http_early_bind.eigs new file mode 100644 index 000000000..bb0eac503 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_early_bind.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: http_early_bind (int-requiring; port; PROFILE server (make server); run in a child (it prints its cwd on stdout)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define run_child(name, lines, secs) as: + local f is p + "/" + name + ".eigs" + write_text of [f, join of [lines, "\n"]] + local r is exec_capture of [[exe_path of null, f], secs] + for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) + print of [name + "-rc", r[0]] +p is env_get of "EIGS_PROBE_TMP" +run_child of ["early", ["define say(v) as:", " print of (\"R16|\" + (str of v))", "try:", " say of [\"fraction-answer\", http_early_bind of 0.5]", "catch e:", " say of [\"port-fraction\", e.kind, contains of [e.message, \"capability\"]]", "try:", " say of [\"65536-answer\", http_early_bind of 65536]", "catch e:", " say of [\"port-65536\", e.kind, contains of [e.message, \"capability\"]]", "try:", " say of [\"integral-float\", http_early_bind of 0.0]", "catch e:", " say of [\"integral-float-error\", e.kind, contains of [e.message, \"capability\"]]"], 5] diff --git a/tests/phase2/builtins/builtins/http_early_bind.pre b/tests/phase2/builtins/builtins/http_early_bind.pre new file mode 100644 index 000000000..275aab368 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_early_bind.pre @@ -0,0 +1,5 @@ +["fraction-answer", "bound"] +["port-65536", "value", false] +["integral-float-error", "value", false] +["early-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_early_bind.tag b/tests/phase2/builtins/builtins/http_early_bind.tag new file mode 100644 index 000000000..275aab368 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_early_bind.tag @@ -0,0 +1,5 @@ +["fraction-answer", "bound"] +["port-65536", "value", false] +["integral-float-error", "value", false] +["early-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_post.branch b/tests/phase2/builtins/builtins/http_post.branch new file mode 100644 index 000000000..cb43c7995 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_post.branch @@ -0,0 +1,2 @@ +["9007199254740993", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_post.eigs b/tests/phase2/builtins/builtins/http_post.eigs new file mode 100644 index 000000000..afb70dd76 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_post.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: http_post (other; header values via value_to_string; PROFILE server (make server); FIXTURE R16_HTTP_CAPTURE_URL: R1.6a's loopback capture server answers with the received X-Number header (ruling B6)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + url is env_get of "R16_HTTP_CAPTURE_URL" + r is http_post of [url, "{\"X-Number\": 9007199254740993}", "ping"] + print of [r, type of r] +catch e: + print of ["capability", e.kind, e.message] diff --git a/tests/phase2/builtins/builtins/http_post.pre b/tests/phase2/builtins/builtins/http_post.pre new file mode 100644 index 000000000..4be7a13f7 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_post.pre @@ -0,0 +1,2 @@ +["9007199254740992", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_post.tag b/tests/phase2/builtins/builtins/http_post.tag new file mode 100644 index 000000000..4be7a13f7 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_post.tag @@ -0,0 +1,2 @@ +["9007199254740992", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route.branch b/tests/phase2/builtins/builtins/http_route.branch new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route.branch @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route.eigs b/tests/phase2/builtins/builtins/http_route.eigs new file mode 100644 index 000000000..d61d06127 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: http_route (other; formats via value_to_string; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of (http_route of ["GET", "/n", 9007199254740993]) +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +print of (http_route of ["GET", "/f", 2.5]) diff --git a/tests/phase2/builtins/builtins/http_route.pre b/tests/phase2/builtins/builtins/http_route.pre new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route.pre @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route.tag b/tests/phase2/builtins/builtins/http_route.tag new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route.tag @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route_authed.branch b/tests/phase2/builtins/builtins/http_route_authed.branch new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route_authed.branch @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route_authed.eigs b/tests/phase2/builtins/builtins/http_route_authed.eigs new file mode 100644 index 000000000..6f36988f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route_authed.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: http_route_authed (other; formats via value_to_string; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + print of (http_route_authed of ["GET", "/n", 9007199254740993]) +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +print of (http_route_authed of ["GET", "/f", 2.5]) diff --git a/tests/phase2/builtins/builtins/http_route_authed.pre b/tests/phase2/builtins/builtins/http_route_authed.pre new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route_authed.pre @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_route_authed.tag b/tests/phase2/builtins/builtins/http_route_authed.tag new file mode 100644 index 000000000..1636e27d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_route_authed.tag @@ -0,0 +1,3 @@ +route registered +route registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_serve.branch b/tests/phase2/builtins/builtins/http_serve.branch new file mode 100644 index 000000000..1ca7cddaf --- /dev/null +++ b/tests/phase2/builtins/builtins/http_serve.branch @@ -0,0 +1,6 @@ +["port-fraction", "value", false] +["serve_a-rc", 0] +["port-65536", "value", false] +["serve_c-rc", 0] +["serve_b-rc", -2] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_serve.eigs b/tests/phase2/builtins/builtins/http_serve.eigs new file mode 100644 index 000000000..3b8fde375 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_serve.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: http_serve (int-requiring; port; PROFILE server (make server); each call in a child under a timeout (a valid call serves forever)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define run_child(name, lines, secs) as: + local f is p + "/" + name + ".eigs" + write_text of [f, join of [lines, "\n"]] + local r is exec_capture of [[exe_path of null, f], secs] + for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) + print of [name + "-rc", r[0]] +p is env_get of "EIGS_PROBE_TMP" +run_child of ["serve_a", ["define say(v) as:", " print of (\"R16|\" + (str of v))", "try:", " http_serve of 0.5", " say of \"http_serve returned\"", "catch e:", " say of [\"port-fraction\", e.kind, contains of [e.message, \"capability\"]]"], 5] +run_child of ["serve_c", ["define say(v) as:", " print of (\"R16|\" + (str of v))", "try:", " http_serve of 65536", " say of \"http_serve returned\"", "catch e:", " say of [\"port-65536\", e.kind, contains of [e.message, \"capability\"]]"], 5] +run_child of ["serve_b", ["define say(v) as:", " print of (\"R16|\" + (str of v))", "try:", " http_serve of 0.0", " say of \"http_serve returned\"", "catch e:", " say of [\"integral-float-error\", e.kind, contains of [e.message, \"capability\"]]"], 3] diff --git a/tests/phase2/builtins/builtins/http_serve.pre b/tests/phase2/builtins/builtins/http_serve.pre new file mode 100644 index 000000000..e66ecc41b --- /dev/null +++ b/tests/phase2/builtins/builtins/http_serve.pre @@ -0,0 +1,4 @@ +["serve_a-rc", -2] +["serve_c-rc", -2] +["serve_b-rc", -2] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_serve.tag b/tests/phase2/builtins/builtins/http_serve.tag new file mode 100644 index 000000000..e66ecc41b --- /dev/null +++ b/tests/phase2/builtins/builtins/http_serve.tag @@ -0,0 +1,4 @@ +["serve_a-rc", -2] +["serve_c-rc", -2] +["serve_b-rc", -2] +rc=0 diff --git a/tests/phase2/builtins/builtins/http_static.branch b/tests/phase2/builtins/builtins/http_static.branch new file mode 100644 index 000000000..660d9684c --- /dev/null +++ b/tests/phase2/builtins/builtins/http_static.branch @@ -0,0 +1,2 @@ +static registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_static.eigs b/tests/phase2/builtins/builtins/http_static.eigs new file mode 100644 index 000000000..e8df94d38 --- /dev/null +++ b/tests/phase2/builtins/builtins/http_static.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: http_static (other; formats via value_to_string; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +try: + print of (http_static of ["/s", p]) +catch e: + print of ["capability", e.kind, e.message] + exit of 0 diff --git a/tests/phase2/builtins/builtins/http_static.pre b/tests/phase2/builtins/builtins/http_static.pre new file mode 100644 index 000000000..660d9684c --- /dev/null +++ b/tests/phase2/builtins/builtins/http_static.pre @@ -0,0 +1,2 @@ +static registered +rc=0 diff --git a/tests/phase2/builtins/builtins/http_static.tag b/tests/phase2/builtins/builtins/http_static.tag new file mode 100644 index 000000000..660d9684c --- /dev/null +++ b/tests/phase2/builtins/builtins/http_static.tag @@ -0,0 +1,2 @@ +static registered +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_add.branch b/tests/phase2/builtins/builtins/i16_checked_add.branch new file mode 100644 index 000000000..264ae5b80 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_add.eigs b/tests/phase2/builtins/builtins/i16_checked_add.eigs new file mode 100644 index 000000000..4b25baead --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_checked_add"]) >= 0] +try: + x is i16_checked_add of [32767, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_checked_add"]) >= 0] +try: + x is i16_checked_add of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_checked_add"]) >= 0] +try: + x is i16_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_checked_add of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_checked_add of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_checked_add.pre b/tests/phase2/builtins/builtins/i16_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_add.tag b/tests/phase2/builtins/builtins/i16_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_mul.branch b/tests/phase2/builtins/builtins/i16_checked_mul.branch new file mode 100644 index 000000000..49e008fb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_mul.eigs b/tests/phase2/builtins/builtins/i16_checked_mul.eigs new file mode 100644 index 000000000..5294e7d0b --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_checked_mul"]) >= 0] +try: + x is i16_checked_mul of [32767, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_checked_mul"]) >= 0] +try: + x is i16_checked_mul of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_checked_mul"]) >= 0] +try: + x is i16_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_checked_mul of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_checked_mul of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_checked_mul.pre b/tests/phase2/builtins/builtins/i16_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_mul.tag b/tests/phase2/builtins/builtins/i16_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_sub.branch b/tests/phase2/builtins/builtins/i16_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_sub.eigs b/tests/phase2/builtins/builtins/i16_checked_sub.eigs new file mode 100644 index 000000000..9588a46ad --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_checked_sub"]) >= 0] +try: + x is i16_checked_sub of [-32768, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_checked_sub"]) >= 0] +try: + x is i16_checked_sub of [32767, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_checked_sub"]) >= 0] +try: + x is i16_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_checked_sub of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_checked_sub of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_checked_sub.pre b/tests/phase2/builtins/builtins/i16_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_checked_sub.tag b/tests/phase2/builtins/builtins/i16_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_add.branch b/tests/phase2/builtins/builtins/i16_saturating_add.branch new file mode 100644 index 000000000..06aa59c30 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[32767, "int"] +[-32768, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_add.eigs b/tests/phase2/builtins/builtins/i16_saturating_add.eigs new file mode 100644 index 000000000..3cdf1b8ed --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_saturating_add"]) >= 0] +try: + x is i16_saturating_add of [32767, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_saturating_add"]) >= 0] +try: + x is i16_saturating_add of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_saturating_add"]) >= 0] +try: + x is i16_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_saturating_add of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_saturating_add of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_saturating_add.pre b/tests/phase2/builtins/builtins/i16_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_add.tag b/tests/phase2/builtins/builtins/i16_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_mul.branch b/tests/phase2/builtins/builtins/i16_saturating_mul.branch new file mode 100644 index 000000000..56f5a26f8 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[32767, "int"] +[32767, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_mul.eigs b/tests/phase2/builtins/builtins/i16_saturating_mul.eigs new file mode 100644 index 000000000..b940c84d4 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_saturating_mul"]) >= 0] +try: + x is i16_saturating_mul of [32767, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_saturating_mul"]) >= 0] +try: + x is i16_saturating_mul of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_saturating_mul"]) >= 0] +try: + x is i16_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_saturating_mul of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_saturating_mul of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_saturating_mul.pre b/tests/phase2/builtins/builtins/i16_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_mul.tag b/tests/phase2/builtins/builtins/i16_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_sub.branch b/tests/phase2/builtins/builtins/i16_saturating_sub.branch new file mode 100644 index 000000000..36a8beb17 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[-32768, "int"] +[32767, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_sub.eigs b/tests/phase2/builtins/builtins/i16_saturating_sub.eigs new file mode 100644 index 000000000..cc99766ed --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_saturating_sub"]) >= 0] +try: + x is i16_saturating_sub of [-32768, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_saturating_sub"]) >= 0] +try: + x is i16_saturating_sub of [32767, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_saturating_sub"]) >= 0] +try: + x is i16_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_saturating_sub of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_saturating_sub of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_saturating_sub.pre b/tests/phase2/builtins/builtins/i16_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_saturating_sub.tag b/tests/phase2/builtins/builtins/i16_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_add.branch b/tests/phase2/builtins/builtins/i16_wrapping_add.branch new file mode 100644 index 000000000..7ed1f6bc3 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[-32768, "int"] +[32767, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_add.eigs b/tests/phase2/builtins/builtins/i16_wrapping_add.eigs new file mode 100644 index 000000000..67e3cf2f5 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_wrapping_add"]) >= 0] +try: + x is i16_wrapping_add of [32767, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_wrapping_add"]) >= 0] +try: + x is i16_wrapping_add of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_wrapping_add"]) >= 0] +try: + x is i16_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_wrapping_add of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_wrapping_add of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_wrapping_add.pre b/tests/phase2/builtins/builtins/i16_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_add.tag b/tests/phase2/builtins/builtins/i16_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_mul.branch b/tests/phase2/builtins/builtins/i16_wrapping_mul.branch new file mode 100644 index 000000000..67cd9a8f6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[-2, "int"] +[-32768, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_mul.eigs b/tests/phase2/builtins/builtins/i16_wrapping_mul.eigs new file mode 100644 index 000000000..a7878f025 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_wrapping_mul"]) >= 0] +try: + x is i16_wrapping_mul of [32767, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_wrapping_mul"]) >= 0] +try: + x is i16_wrapping_mul of [-32768, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_wrapping_mul"]) >= 0] +try: + x is i16_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_wrapping_mul of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_wrapping_mul of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_wrapping_mul.pre b/tests/phase2/builtins/builtins/i16_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_mul.tag b/tests/phase2/builtins/builtins/i16_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_sub.branch b/tests/phase2/builtins/builtins/i16_wrapping_sub.branch new file mode 100644 index 000000000..8409b31bc --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[32767, "int"] +[-32768, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_sub.eigs b/tests/phase2/builtins/builtins/i16_wrapping_sub.eigs new file mode 100644 index 000000000..46644e308 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i16_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i16_wrapping_sub"]) >= 0] +try: + x is i16_wrapping_sub of [-32768, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i16_wrapping_sub"]) >= 0] +try: + x is i16_wrapping_sub of [32767, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i16_wrapping_sub"]) >= 0] +try: + x is i16_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i16_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i16_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i16_wrapping_sub of [-32769, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i16_wrapping_sub of [1, 32768] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i16_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i16_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i16_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i16_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i16_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i16_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i16_wrapping_sub.pre b/tests/phase2/builtins/builtins/i16_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i16_wrapping_sub.tag b/tests/phase2/builtins/builtins/i16_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i16_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_add.branch b/tests/phase2/builtins/builtins/i32_checked_add.branch new file mode 100644 index 000000000..264ae5b80 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_add.eigs b/tests/phase2/builtins/builtins/i32_checked_add.eigs new file mode 100644 index 000000000..44b8b858f --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_checked_add"]) >= 0] +try: + x is i32_checked_add of [2147483647, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_checked_add"]) >= 0] +try: + x is i32_checked_add of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_checked_add"]) >= 0] +try: + x is i32_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_checked_add of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_checked_add of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_checked_add.pre b/tests/phase2/builtins/builtins/i32_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_add.tag b/tests/phase2/builtins/builtins/i32_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_mul.branch b/tests/phase2/builtins/builtins/i32_checked_mul.branch new file mode 100644 index 000000000..49e008fb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_mul.eigs b/tests/phase2/builtins/builtins/i32_checked_mul.eigs new file mode 100644 index 000000000..28d9a492c --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_checked_mul"]) >= 0] +try: + x is i32_checked_mul of [2147483647, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_checked_mul"]) >= 0] +try: + x is i32_checked_mul of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_checked_mul"]) >= 0] +try: + x is i32_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_checked_mul of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_checked_mul of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_checked_mul.pre b/tests/phase2/builtins/builtins/i32_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_mul.tag b/tests/phase2/builtins/builtins/i32_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_sub.branch b/tests/phase2/builtins/builtins/i32_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_sub.eigs b/tests/phase2/builtins/builtins/i32_checked_sub.eigs new file mode 100644 index 000000000..1b3e8b12e --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_checked_sub"]) >= 0] +try: + x is i32_checked_sub of [-2147483648, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_checked_sub"]) >= 0] +try: + x is i32_checked_sub of [2147483647, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_checked_sub"]) >= 0] +try: + x is i32_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_checked_sub of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_checked_sub of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_checked_sub.pre b/tests/phase2/builtins/builtins/i32_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_checked_sub.tag b/tests/phase2/builtins/builtins/i32_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_add.branch b/tests/phase2/builtins/builtins/i32_saturating_add.branch new file mode 100644 index 000000000..8b9140fba --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[2147483647, "int"] +[-2147483648, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_add.eigs b/tests/phase2/builtins/builtins/i32_saturating_add.eigs new file mode 100644 index 000000000..eb13c1eef --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_saturating_add"]) >= 0] +try: + x is i32_saturating_add of [2147483647, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_saturating_add"]) >= 0] +try: + x is i32_saturating_add of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_saturating_add"]) >= 0] +try: + x is i32_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_saturating_add of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_saturating_add of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_saturating_add.pre b/tests/phase2/builtins/builtins/i32_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_add.tag b/tests/phase2/builtins/builtins/i32_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_mul.branch b/tests/phase2/builtins/builtins/i32_saturating_mul.branch new file mode 100644 index 000000000..d393c9973 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[2147483647, "int"] +[2147483647, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_mul.eigs b/tests/phase2/builtins/builtins/i32_saturating_mul.eigs new file mode 100644 index 000000000..430b39967 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_saturating_mul"]) >= 0] +try: + x is i32_saturating_mul of [2147483647, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_saturating_mul"]) >= 0] +try: + x is i32_saturating_mul of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_saturating_mul"]) >= 0] +try: + x is i32_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_saturating_mul of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_saturating_mul of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_saturating_mul.pre b/tests/phase2/builtins/builtins/i32_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_mul.tag b/tests/phase2/builtins/builtins/i32_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_sub.branch b/tests/phase2/builtins/builtins/i32_saturating_sub.branch new file mode 100644 index 000000000..c239f1ad2 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[-2147483648, "int"] +[2147483647, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_sub.eigs b/tests/phase2/builtins/builtins/i32_saturating_sub.eigs new file mode 100644 index 000000000..3fd326a4c --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_saturating_sub"]) >= 0] +try: + x is i32_saturating_sub of [-2147483648, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_saturating_sub"]) >= 0] +try: + x is i32_saturating_sub of [2147483647, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_saturating_sub"]) >= 0] +try: + x is i32_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_saturating_sub of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_saturating_sub of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_saturating_sub.pre b/tests/phase2/builtins/builtins/i32_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_saturating_sub.tag b/tests/phase2/builtins/builtins/i32_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_add.branch b/tests/phase2/builtins/builtins/i32_wrapping_add.branch new file mode 100644 index 000000000..ba8e713fd --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[-2147483648, "int"] +[2147483647, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_add.eigs b/tests/phase2/builtins/builtins/i32_wrapping_add.eigs new file mode 100644 index 000000000..1599c7f3d --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_wrapping_add"]) >= 0] +try: + x is i32_wrapping_add of [2147483647, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_wrapping_add"]) >= 0] +try: + x is i32_wrapping_add of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_wrapping_add"]) >= 0] +try: + x is i32_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_wrapping_add of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_wrapping_add of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_wrapping_add.pre b/tests/phase2/builtins/builtins/i32_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_add.tag b/tests/phase2/builtins/builtins/i32_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_mul.branch b/tests/phase2/builtins/builtins/i32_wrapping_mul.branch new file mode 100644 index 000000000..aed1b2958 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[-2, "int"] +[-2147483648, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_mul.eigs b/tests/phase2/builtins/builtins/i32_wrapping_mul.eigs new file mode 100644 index 000000000..19e9c4902 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_wrapping_mul"]) >= 0] +try: + x is i32_wrapping_mul of [2147483647, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_wrapping_mul"]) >= 0] +try: + x is i32_wrapping_mul of [-2147483648, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_wrapping_mul"]) >= 0] +try: + x is i32_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_wrapping_mul of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_wrapping_mul of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_wrapping_mul.pre b/tests/phase2/builtins/builtins/i32_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_mul.tag b/tests/phase2/builtins/builtins/i32_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_sub.branch b/tests/phase2/builtins/builtins/i32_wrapping_sub.branch new file mode 100644 index 000000000..f2a8981a7 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[2147483647, "int"] +[-2147483648, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_sub.eigs b/tests/phase2/builtins/builtins/i32_wrapping_sub.eigs new file mode 100644 index 000000000..da459872d --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i32_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i32_wrapping_sub"]) >= 0] +try: + x is i32_wrapping_sub of [-2147483648, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i32_wrapping_sub"]) >= 0] +try: + x is i32_wrapping_sub of [2147483647, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i32_wrapping_sub"]) >= 0] +try: + x is i32_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i32_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i32_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i32_wrapping_sub of [-2147483649, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i32_wrapping_sub of [1, 2147483648] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i32_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i32_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i32_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i32_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i32_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i32_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i32_wrapping_sub.pre b/tests/phase2/builtins/builtins/i32_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i32_wrapping_sub.tag b/tests/phase2/builtins/builtins/i32_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i32_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_add.branch b/tests/phase2/builtins/builtins/i64_checked_add.branch new file mode 100644 index 000000000..aa885ee01 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_add.eigs b/tests/phase2/builtins/builtins/i64_checked_add.eigs new file mode 100644 index 000000000..50aca6b0d --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_checked_add"]) >= 0] +try: + x is i64_checked_add of [9223372036854775807, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_checked_add"]) >= 0] +try: + x is i64_checked_add of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_checked_add"]) >= 0] +try: + x is i64_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_checked_add.pre b/tests/phase2/builtins/builtins/i64_checked_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_add.tag b/tests/phase2/builtins/builtins/i64_checked_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_mul.branch b/tests/phase2/builtins/builtins/i64_checked_mul.branch new file mode 100644 index 000000000..8e8962a78 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_mul.eigs b/tests/phase2/builtins/builtins/i64_checked_mul.eigs new file mode 100644 index 000000000..ec5d2ee98 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_checked_mul"]) >= 0] +try: + x is i64_checked_mul of [9223372036854775807, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_checked_mul"]) >= 0] +try: + x is i64_checked_mul of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_checked_mul"]) >= 0] +try: + x is i64_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_checked_mul.pre b/tests/phase2/builtins/builtins/i64_checked_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_mul.tag b/tests/phase2/builtins/builtins/i64_checked_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_sub.branch b/tests/phase2/builtins/builtins/i64_checked_sub.branch new file mode 100644 index 000000000..ed342b1b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_sub.eigs b/tests/phase2/builtins/builtins/i64_checked_sub.eigs new file mode 100644 index 000000000..67896ddd3 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_checked_sub"]) >= 0] +try: + x is i64_checked_sub of [-9223372036854775808, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_checked_sub"]) >= 0] +try: + x is i64_checked_sub of [9223372036854775807, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_checked_sub"]) >= 0] +try: + x is i64_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_checked_sub.pre b/tests/phase2/builtins/builtins/i64_checked_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_checked_sub.tag b/tests/phase2/builtins/builtins/i64_checked_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_checked_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_add.branch b/tests/phase2/builtins/builtins/i64_saturating_add.branch new file mode 100644 index 000000000..c919ac5d5 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +[9223372036854775807, "int"] +[-9223372036854775808, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_add.eigs b/tests/phase2/builtins/builtins/i64_saturating_add.eigs new file mode 100644 index 000000000..a77d7d8e9 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_saturating_add"]) >= 0] +try: + x is i64_saturating_add of [9223372036854775807, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_saturating_add"]) >= 0] +try: + x is i64_saturating_add of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_saturating_add"]) >= 0] +try: + x is i64_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_saturating_add.pre b/tests/phase2/builtins/builtins/i64_saturating_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_add.tag b/tests/phase2/builtins/builtins/i64_saturating_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_mul.branch b/tests/phase2/builtins/builtins/i64_saturating_mul.branch new file mode 100644 index 000000000..f4134fd0c --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +[9223372036854775807, "int"] +[9223372036854775807, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_mul.eigs b/tests/phase2/builtins/builtins/i64_saturating_mul.eigs new file mode 100644 index 000000000..ca0e101a8 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_saturating_mul"]) >= 0] +try: + x is i64_saturating_mul of [9223372036854775807, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_saturating_mul"]) >= 0] +try: + x is i64_saturating_mul of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_saturating_mul"]) >= 0] +try: + x is i64_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_saturating_mul.pre b/tests/phase2/builtins/builtins/i64_saturating_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_mul.tag b/tests/phase2/builtins/builtins/i64_saturating_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_sub.branch b/tests/phase2/builtins/builtins/i64_saturating_sub.branch new file mode 100644 index 000000000..04d679712 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +[-9223372036854775808, "int"] +[9223372036854775807, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_sub.eigs b/tests/phase2/builtins/builtins/i64_saturating_sub.eigs new file mode 100644 index 000000000..3aac551fd --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_saturating_sub"]) >= 0] +try: + x is i64_saturating_sub of [-9223372036854775808, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_saturating_sub"]) >= 0] +try: + x is i64_saturating_sub of [9223372036854775807, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_saturating_sub"]) >= 0] +try: + x is i64_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_saturating_sub.pre b/tests/phase2/builtins/builtins/i64_saturating_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_saturating_sub.tag b/tests/phase2/builtins/builtins/i64_saturating_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_saturating_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_add.branch b/tests/phase2/builtins/builtins/i64_wrapping_add.branch new file mode 100644 index 000000000..b6a12dec8 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +[-9223372036854775808, "int"] +[9223372036854775807, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_add.eigs b/tests/phase2/builtins/builtins/i64_wrapping_add.eigs new file mode 100644 index 000000000..718b96893 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_wrapping_add"]) >= 0] +try: + x is i64_wrapping_add of [9223372036854775807, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_wrapping_add"]) >= 0] +try: + x is i64_wrapping_add of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_wrapping_add"]) >= 0] +try: + x is i64_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_wrapping_add.pre b/tests/phase2/builtins/builtins/i64_wrapping_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_add.tag b/tests/phase2/builtins/builtins/i64_wrapping_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_mul.branch b/tests/phase2/builtins/builtins/i64_wrapping_mul.branch new file mode 100644 index 000000000..859bd48de --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +[-2, "int"] +[-9223372036854775808, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_mul.eigs b/tests/phase2/builtins/builtins/i64_wrapping_mul.eigs new file mode 100644 index 000000000..e348bf1de --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_wrapping_mul"]) >= 0] +try: + x is i64_wrapping_mul of [9223372036854775807, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_wrapping_mul"]) >= 0] +try: + x is i64_wrapping_mul of [-9223372036854775808, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_wrapping_mul"]) >= 0] +try: + x is i64_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_wrapping_mul.pre b/tests/phase2/builtins/builtins/i64_wrapping_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_mul.tag b/tests/phase2/builtins/builtins/i64_wrapping_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_sub.branch b/tests/phase2/builtins/builtins/i64_wrapping_sub.branch new file mode 100644 index 000000000..5b8815a1f --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +[9223372036854775807, "int"] +[-9223372036854775808, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_sub.eigs b/tests/phase2/builtins/builtins/i64_wrapping_sub.eigs new file mode 100644 index 000000000..2b54f045f --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i64_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i64_wrapping_sub"]) >= 0] +try: + x is i64_wrapping_sub of [-9223372036854775808, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i64_wrapping_sub"]) >= 0] +try: + x is i64_wrapping_sub of [9223372036854775807, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i64_wrapping_sub"]) >= 0] +try: + x is i64_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i64_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i64_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i64_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i64_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i64_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i64_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i64_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i64_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i64_wrapping_sub.pre b/tests/phase2/builtins/builtins/i64_wrapping_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i64_wrapping_sub.tag b/tests/phase2/builtins/builtins/i64_wrapping_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/i64_wrapping_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_add.branch b/tests/phase2/builtins/builtins/i8_checked_add.branch new file mode 100644 index 000000000..264ae5b80 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_add.eigs b/tests/phase2/builtins/builtins/i8_checked_add.eigs new file mode 100644 index 000000000..3eb2c18af --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_checked_add"]) >= 0] +try: + x is i8_checked_add of [127, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_checked_add"]) >= 0] +try: + x is i8_checked_add of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_checked_add"]) >= 0] +try: + x is i8_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_checked_add of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_checked_add of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_checked_add.pre b/tests/phase2/builtins/builtins/i8_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_add.tag b/tests/phase2/builtins/builtins/i8_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_mul.branch b/tests/phase2/builtins/builtins/i8_checked_mul.branch new file mode 100644 index 000000000..49e008fb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_mul.eigs b/tests/phase2/builtins/builtins/i8_checked_mul.eigs new file mode 100644 index 000000000..29dfedbab --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_checked_mul"]) >= 0] +try: + x is i8_checked_mul of [127, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_checked_mul"]) >= 0] +try: + x is i8_checked_mul of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_checked_mul"]) >= 0] +try: + x is i8_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_checked_mul of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_checked_mul of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_checked_mul.pre b/tests/phase2/builtins/builtins/i8_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_mul.tag b/tests/phase2/builtins/builtins/i8_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_sub.branch b/tests/phase2/builtins/builtins/i8_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_sub.eigs b/tests/phase2/builtins/builtins/i8_checked_sub.eigs new file mode 100644 index 000000000..e7cf99771 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_checked_sub"]) >= 0] +try: + x is i8_checked_sub of [-128, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_checked_sub"]) >= 0] +try: + x is i8_checked_sub of [127, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_checked_sub"]) >= 0] +try: + x is i8_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_checked_sub of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_checked_sub of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_checked_sub.pre b/tests/phase2/builtins/builtins/i8_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_checked_sub.tag b/tests/phase2/builtins/builtins/i8_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_add.branch b/tests/phase2/builtins/builtins/i8_saturating_add.branch new file mode 100644 index 000000000..538c64f1b --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[127, "int"] +[-128, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_add.eigs b/tests/phase2/builtins/builtins/i8_saturating_add.eigs new file mode 100644 index 000000000..37597c27f --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_saturating_add"]) >= 0] +try: + x is i8_saturating_add of [127, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_saturating_add"]) >= 0] +try: + x is i8_saturating_add of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_saturating_add"]) >= 0] +try: + x is i8_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_saturating_add of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_saturating_add of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_saturating_add.pre b/tests/phase2/builtins/builtins/i8_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_add.tag b/tests/phase2/builtins/builtins/i8_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_mul.branch b/tests/phase2/builtins/builtins/i8_saturating_mul.branch new file mode 100644 index 000000000..da05cd898 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[127, "int"] +[127, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_mul.eigs b/tests/phase2/builtins/builtins/i8_saturating_mul.eigs new file mode 100644 index 000000000..370725d9d --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_saturating_mul"]) >= 0] +try: + x is i8_saturating_mul of [127, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_saturating_mul"]) >= 0] +try: + x is i8_saturating_mul of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_saturating_mul"]) >= 0] +try: + x is i8_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_saturating_mul of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_saturating_mul of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_saturating_mul.pre b/tests/phase2/builtins/builtins/i8_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_mul.tag b/tests/phase2/builtins/builtins/i8_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_sub.branch b/tests/phase2/builtins/builtins/i8_saturating_sub.branch new file mode 100644 index 000000000..cee635873 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[-128, "int"] +[127, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_sub.eigs b/tests/phase2/builtins/builtins/i8_saturating_sub.eigs new file mode 100644 index 000000000..80da6f0fb --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_saturating_sub"]) >= 0] +try: + x is i8_saturating_sub of [-128, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_saturating_sub"]) >= 0] +try: + x is i8_saturating_sub of [127, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_saturating_sub"]) >= 0] +try: + x is i8_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_saturating_sub of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_saturating_sub of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_saturating_sub.pre b/tests/phase2/builtins/builtins/i8_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_saturating_sub.tag b/tests/phase2/builtins/builtins/i8_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_add.branch b/tests/phase2/builtins/builtins/i8_wrapping_add.branch new file mode 100644 index 000000000..0f2d3e860 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[-128, "int"] +[127, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_add.eigs b/tests/phase2/builtins/builtins/i8_wrapping_add.eigs new file mode 100644 index 000000000..b9b7dc203 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_wrapping_add"]) >= 0] +try: + x is i8_wrapping_add of [127, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_wrapping_add"]) >= 0] +try: + x is i8_wrapping_add of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_wrapping_add"]) >= 0] +try: + x is i8_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_wrapping_add of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_wrapping_add of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_wrapping_add.pre b/tests/phase2/builtins/builtins/i8_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_add.tag b/tests/phase2/builtins/builtins/i8_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_mul.branch b/tests/phase2/builtins/builtins/i8_wrapping_mul.branch new file mode 100644 index 000000000..ac274f25b --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[-2, "int"] +[-128, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_mul.eigs b/tests/phase2/builtins/builtins/i8_wrapping_mul.eigs new file mode 100644 index 000000000..aab8bf27f --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_wrapping_mul"]) >= 0] +try: + x is i8_wrapping_mul of [127, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_wrapping_mul"]) >= 0] +try: + x is i8_wrapping_mul of [-128, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_wrapping_mul"]) >= 0] +try: + x is i8_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_wrapping_mul of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_wrapping_mul of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_wrapping_mul.pre b/tests/phase2/builtins/builtins/i8_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_mul.tag b/tests/phase2/builtins/builtins/i8_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_sub.branch b/tests/phase2/builtins/builtins/i8_wrapping_sub.branch new file mode 100644 index 000000000..e66c7e6a6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[127, "int"] +[-128, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_sub.eigs b/tests/phase2/builtins/builtins/i8_wrapping_sub.eigs new file mode 100644 index 000000000..98487f12b --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is i8_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "i8_wrapping_sub"]) >= 0] +try: + x is i8_wrapping_sub of [-128, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "i8_wrapping_sub"]) >= 0] +try: + x is i8_wrapping_sub of [127, -1] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "i8_wrapping_sub"]) >= 0] +try: + x is i8_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is i8_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is i8_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is i8_wrapping_sub of [-129, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is i8_wrapping_sub of [1, 128] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is i8_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is i8_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is i8_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is i8_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is i8_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is i8_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/i8_wrapping_sub.pre b/tests/phase2/builtins/builtins/i8_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/i8_wrapping_sub.tag b/tests/phase2/builtins/builtins/i8_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/i8_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/index_of.branch b/tests/phase2/builtins/builtins/index_of.branch new file mode 100644 index 000000000..5d78be20e --- /dev/null +++ b/tests/phase2/builtins/builtins/index_of.branch @@ -0,0 +1,3 @@ +[2, "int"] +[-1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/index_of.eigs b/tests/phase2/builtins/builtins/index_of.eigs new file mode 100644 index 000000000..e2a847354 --- /dev/null +++ b/tests/phase2/builtins/builtins/index_of.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: index_of (int-preserving; byte index or -1) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is index_of of ["hello", "l"] +print of [a, nt of a] +b is index_of of ["hello", "z"] +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/index_of.pre b/tests/phase2/builtins/builtins/index_of.pre new file mode 100644 index 000000000..d30989a04 --- /dev/null +++ b/tests/phase2/builtins/builtins/index_of.pre @@ -0,0 +1,3 @@ +[2, "float"] +[-1, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/index_of.tag b/tests/phase2/builtins/builtins/index_of.tag new file mode 100644 index 000000000..4ea99d809 --- /dev/null +++ b/tests/phase2/builtins/builtins/index_of.tag @@ -0,0 +1,3 @@ +[2, "undefined_name"] +[-1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/inflate.branch b/tests/phase2/builtins/builtins/inflate.branch new file mode 100644 index 000000000..2a46c5008 --- /dev/null +++ b/tests/phase2/builtins/builtins/inflate.branch @@ -0,0 +1,6 @@ +hi +[[104, 105], "int"] +["byte-fraction", "value"] +["byte-459", "value"] +hi +rc=0 diff --git a/tests/phase2/builtins/builtins/inflate.eigs b/tests/phase2/builtins/builtins/inflate.eigs new file mode 100644 index 000000000..2ae4f198f --- /dev/null +++ b/tests/phase2/builtins/builtins/inflate.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: inflate (int-requiring; byte values: N5, then 0..255 (Q1); PROFILE zlib (make zlib)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + r is inflate of [203, 200, 4, 0] +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +print of (str_from_bytes of r) +print of [r, nt of r[0]] +try: + print of ["fraction-answer", inflate of [203.5, 200, 4, 0]] +catch e: + print of ["byte-fraction", e.kind] +try: + print of ["459-answer", inflate of [459, 200, 4, 0]] +catch e: + print of ["byte-459", e.kind] +print of (str_from_bytes of (inflate of [203.0, 200.0, 4.0, 0.0])) diff --git a/tests/phase2/builtins/builtins/inflate.pre b/tests/phase2/builtins/builtins/inflate.pre new file mode 100644 index 000000000..56a1c5abf --- /dev/null +++ b/tests/phase2/builtins/builtins/inflate.pre @@ -0,0 +1,6 @@ +hi +[[104, 105], "float"] +["fraction-answer", [104, 105]] +["459-answer", [104, 105]] +hi +rc=0 diff --git a/tests/phase2/builtins/builtins/inflate.tag b/tests/phase2/builtins/builtins/inflate.tag new file mode 100644 index 000000000..e6f056747 --- /dev/null +++ b/tests/phase2/builtins/builtins/inflate.tag @@ -0,0 +1,6 @@ +hi +[[104, 105], "undefined_name"] +["fraction-answer", [104, 105]] +["459-answer", [104, 105]] +hi +rc=0 diff --git a/tests/phase2/builtins/builtins/join.branch b/tests/phase2/builtins/builtins/join.branch new file mode 100644 index 000000000..d68d28cb6 --- /dev/null +++ b/tests/phase2/builtins/builtins/join.branch @@ -0,0 +1,2 @@ +1,9007199254740993,2.5,3.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/join.eigs b/tests/phase2/builtins/builtins/join.eigs new file mode 100644 index 000000000..d750ff330 --- /dev/null +++ b/tests/phase2/builtins/builtins/join.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: join (other; elements via value_to_string (N6)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (join of [[1, 9007199254740993, 2.5, 3.0], ","]) diff --git a/tests/phase2/builtins/builtins/join.pre b/tests/phase2/builtins/builtins/join.pre new file mode 100644 index 000000000..ba816510e --- /dev/null +++ b/tests/phase2/builtins/builtins/join.pre @@ -0,0 +1,2 @@ +1,9007199254740993,2.5,3 +rc=0 diff --git a/tests/phase2/builtins/builtins/join.tag b/tests/phase2/builtins/builtins/join.tag new file mode 100644 index 000000000..b416e3a27 --- /dev/null +++ b/tests/phase2/builtins/builtins/join.tag @@ -0,0 +1,2 @@ +1,9007199254740992,2.5,3 +rc=0 diff --git a/tests/phase2/builtins/builtins/json_build.branch b/tests/phase2/builtins/builtins/json_build.branch new file mode 100644 index 000000000..08a558edb --- /dev/null +++ b/tests/phase2/builtins/builtins/json_build.branch @@ -0,0 +1,2 @@ +{"a": 9007199254740993, "b": 2.5, "c": -9223372036854775808, "d": 3.0} +rc=0 diff --git a/tests/phase2/builtins/builtins/json_build.eigs b/tests/phase2/builtins/builtins/json_build.eigs new file mode 100644 index 000000000..180fe9402 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_build.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: json_build (other, LOSS; exact int text arm written in R1.2, live from R1.3) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (json_build of ["a", 9007199254740993, "b", 2.5, "c", -9223372036854775807 - 1, "d", 3.0]) diff --git a/tests/phase2/builtins/builtins/json_build.pre b/tests/phase2/builtins/builtins/json_build.pre new file mode 100644 index 000000000..ece4d1c4c --- /dev/null +++ b/tests/phase2/builtins/builtins/json_build.pre @@ -0,0 +1,2 @@ +{"a": 9007199254740993, "b": 2.5, "c": -9223372036854775808, "d": 3} +rc=0 diff --git a/tests/phase2/builtins/builtins/json_build.tag b/tests/phase2/builtins/builtins/json_build.tag new file mode 100644 index 000000000..2d228ca95 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_build.tag @@ -0,0 +1,2 @@ +{"a": 9007199254740992, "b": 2.5, "c": -9.223372036854776e+18, "d": 3} +rc=0 diff --git a/tests/phase2/builtins/builtins/json_decode.branch b/tests/phase2/builtins/builtins/json_decode.branch new file mode 100644 index 000000000..37d980a65 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_decode.branch @@ -0,0 +1,3 @@ +["int", "float", 9223372036854775807, "float", 9007199254740993, "int", "float"] +[3, 3.0, 9223372036854775807, 1.8446744073709552e+19, 9007199254740993, -7, 100.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_decode.eigs b/tests/phase2/builtins/builtins/json_decode.eigs new file mode 100644 index 000000000..b576c2dc0 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_decode.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: json_decode (other, LOSS; N14 decoder: integer text gives an int when it fits int64) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +v is json_decode of "[3, 3.0, 9223372036854775807, 18446744073709551615, 9007199254740993, -7, 1e2]" +print of [nt of v[0], nt of v[1], v[2], nt of v[3], v[4], nt of v[5], nt of v[6]] +print of v diff --git a/tests/phase2/builtins/builtins/json_decode.pre b/tests/phase2/builtins/builtins/json_decode.pre new file mode 100644 index 000000000..8fd6ece70 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_decode.pre @@ -0,0 +1,3 @@ +["float", "float", 9.223372036854776e+18, "float", 9007199254740992, "float", "float"] +[3, 3, 9.223372036854776e+18, 1.8446744073709552e+19, 9007199254740992, -7, 100] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_decode.tag b/tests/phase2/builtins/builtins/json_decode.tag new file mode 100644 index 000000000..6a0e783ab --- /dev/null +++ b/tests/phase2/builtins/builtins/json_decode.tag @@ -0,0 +1,3 @@ +["undefined_name", "undefined_name", 9.223372036854776e+18, "undefined_name", 9007199254740992, "undefined_name", "undefined_name"] +[3, 3, 9.223372036854776e+18, 1.8446744073709552e+19, 9007199254740992, -7, 100] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_encode.branch b/tests/phase2/builtins/builtins/json_encode.branch new file mode 100644 index 000000000..f76a895d9 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_encode.branch @@ -0,0 +1,2 @@ +[3,9007199254740993,9223372036854775807,-9223372036854775808,2.5,3.0,{"k":9007199254740993}] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_encode.eigs b/tests/phase2/builtins/builtins/json_encode.eigs new file mode 100644 index 000000000..c4256e580 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_encode.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: json_encode (other, LOSS; N13/N14 encoder; int arm exact since R1.2a; float text R1.7) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (json_encode of [3, 9007199254740993, 9223372036854775807, -9223372036854775807 - 1, 2.5, 3.0, {"k": 9007199254740993}]) diff --git a/tests/phase2/builtins/builtins/json_encode.pre b/tests/phase2/builtins/builtins/json_encode.pre new file mode 100644 index 000000000..fb7cdf479 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_encode.pre @@ -0,0 +1,2 @@ +[3,9007199254740993,9223372036854775807,-9223372036854775808,2.5,3,{"k":9007199254740993}] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_encode.tag b/tests/phase2/builtins/builtins/json_encode.tag new file mode 100644 index 000000000..dd83ac4eb --- /dev/null +++ b/tests/phase2/builtins/builtins/json_encode.tag @@ -0,0 +1,2 @@ +[3,9007199254740992,9.223372036854776e+18,-9.223372036854776e+18,2.5,3,{"k":9007199254740992}] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_path.branch b/tests/phase2/builtins/builtins/json_path.branch new file mode 100644 index 000000000..3fbb22a92 --- /dev/null +++ b/tests/phase2/builtins/builtins/json_path.branch @@ -0,0 +1,5 @@ +9223372036854775807 +9007199254740993 +["2.5", "str"] +[9007199254740993,2] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_path.eigs b/tests/phase2/builtins/builtins/json_path.eigs new file mode 100644 index 000000000..b7f593abf --- /dev/null +++ b/tests/phase2/builtins/builtins/json_path.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: json_path (other, LOSS; exact int text reader (R1.2); decoder int production R1.6) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is json_path of ["{\"n\":9223372036854775807}", "n"] +print of a +doc is "{\"n\": 9007199254740993, \"f\": 2.5, \"l\": [9007199254740993, 2]}" +b is json_path of [doc, "n"] +print of b +c is json_path of [doc, "f"] +print of [c, type of c] +d is json_path of [doc, "l"] +print of d diff --git a/tests/phase2/builtins/builtins/json_path.pre b/tests/phase2/builtins/builtins/json_path.pre new file mode 100644 index 000000000..323f83b2a --- /dev/null +++ b/tests/phase2/builtins/builtins/json_path.pre @@ -0,0 +1,5 @@ +9.223372036854776e+18 +9007199254740992 +["2.5", "str"] +[9007199254740992,2] +rc=0 diff --git a/tests/phase2/builtins/builtins/json_path.tag b/tests/phase2/builtins/builtins/json_path.tag new file mode 100644 index 000000000..323f83b2a --- /dev/null +++ b/tests/phase2/builtins/builtins/json_path.tag @@ -0,0 +1,5 @@ +9.223372036854776e+18 +9007199254740992 +["2.5", "str"] +[9007199254740992,2] +rc=0 diff --git a/tests/phase2/builtins/builtins/leaky_relu.branch b/tests/phase2/builtins/builtins/leaky_relu.branch new file mode 100644 index 000000000..d1b461ccb --- /dev/null +++ b/tests/phase2/builtins/builtins/leaky_relu.branch @@ -0,0 +1,2 @@ +[[-0.01, 2.0], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/leaky_relu.eigs b/tests/phase2/builtins/builtins/leaky_relu.eigs new file mode 100644 index 000000000..c98120ad2 --- /dev/null +++ b/tests/phase2/builtins/builtins/leaky_relu.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: leaky_relu (float-producing; ML activation (float tensors; no Lua counterpart)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is leaky_relu of [-1, 2] +print of [r, nt of r[0], nt of r[1]] diff --git a/tests/phase2/builtins/builtins/leaky_relu.pre b/tests/phase2/builtins/builtins/leaky_relu.pre new file mode 100644 index 000000000..557cbf0a1 --- /dev/null +++ b/tests/phase2/builtins/builtins/leaky_relu.pre @@ -0,0 +1,2 @@ +[[-0.01, 2], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/leaky_relu.tag b/tests/phase2/builtins/builtins/leaky_relu.tag new file mode 100644 index 000000000..b4c1e587f --- /dev/null +++ b/tests/phase2/builtins/builtins/leaky_relu.tag @@ -0,0 +1,2 @@ +[[-0.01, 2], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/len.branch b/tests/phase2/builtins/builtins/len.branch new file mode 100644 index 000000000..83117e755 --- /dev/null +++ b/tests/phase2/builtins/builtins/len.branch @@ -0,0 +1,5 @@ +[2, "int"] +[5, "int"] +[3, "int"] +[4, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/len.eigs b/tests/phase2/builtins/builtins/len.eigs new file mode 100644 index 000000000..bc35a0483 --- /dev/null +++ b/tests/phase2/builtins/builtins/len.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: len (int-preserving; count (Lua # is an integer)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is len of [1, 2] +print of [a, nt of a] +b is len of "hello" +print of [b, nt of b] +c is len of {"k": 1, "j": 2, "i": 3} +print of [c, nt of c] +d is len of (buffer of 4) +print of [d, nt of d] diff --git a/tests/phase2/builtins/builtins/len.pre b/tests/phase2/builtins/builtins/len.pre new file mode 100644 index 000000000..77b7f2c0b --- /dev/null +++ b/tests/phase2/builtins/builtins/len.pre @@ -0,0 +1,5 @@ +[2, "float"] +[5, "float"] +[3, "float"] +[4, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/len.tag b/tests/phase2/builtins/builtins/len.tag new file mode 100644 index 000000000..22ccbaaf3 --- /dev/null +++ b/tests/phase2/builtins/builtins/len.tag @@ -0,0 +1,5 @@ +[2, "undefined_name"] +[5, "undefined_name"] +[3, "undefined_name"] +[4, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_contains.branch b/tests/phase2/builtins/builtins/list_contains.branch new file mode 100644 index 000000000..ab54adcd9 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_contains.branch @@ -0,0 +1,2 @@ +[false, true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_contains.eigs b/tests/phase2/builtins/builtins/list_contains.eigs new file mode 100644 index 000000000..45ae95229 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_contains.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: list_contains (other; equality via values_equal (N2)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of [list_contains of [[9007199254740992], 9007199254740993], list_contains of [[2.0], 2], list_contains of [[9007199254740993], 9007199254740993]] diff --git a/tests/phase2/builtins/builtins/list_contains.pre b/tests/phase2/builtins/builtins/list_contains.pre new file mode 100644 index 000000000..ab54adcd9 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_contains.pre @@ -0,0 +1,2 @@ +[false, true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_contains.tag b/tests/phase2/builtins/builtins/list_contains.tag new file mode 100644 index 000000000..1af09241e --- /dev/null +++ b/tests/phase2/builtins/builtins/list_contains.tag @@ -0,0 +1,2 @@ +[true, true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_index_of.branch b/tests/phase2/builtins/builtins/list_index_of.branch new file mode 100644 index 000000000..9aafa0306 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_index_of.branch @@ -0,0 +1,4 @@ +[1, "int"] +[1, "int"] +[-1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_index_of.eigs b/tests/phase2/builtins/builtins/list_index_of.eigs new file mode 100644 index 000000000..f1e9e685e --- /dev/null +++ b/tests/phase2/builtins/builtins/list_index_of.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: list_index_of (int-preserving; returns an index; equality via values_equal (N2)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is list_index_of of [[9007199254740992, 9007199254740993], 9007199254740993] +print of [a, nt of a] +b is list_index_of of [[1, 2.0, 3], 2] +print of [b, nt of b] +c is list_index_of of [[1, 2], 5] +print of [c, nt of c] diff --git a/tests/phase2/builtins/builtins/list_index_of.pre b/tests/phase2/builtins/builtins/list_index_of.pre new file mode 100644 index 000000000..836274d5e --- /dev/null +++ b/tests/phase2/builtins/builtins/list_index_of.pre @@ -0,0 +1,4 @@ +[1, "float"] +[1, "float"] +[-1, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_index_of.tag b/tests/phase2/builtins/builtins/list_index_of.tag new file mode 100644 index 000000000..437df64ce --- /dev/null +++ b/tests/phase2/builtins/builtins/list_index_of.tag @@ -0,0 +1,4 @@ +[0, "undefined_name"] +[1, "undefined_name"] +[-1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_insert_at.branch b/tests/phase2/builtins/builtins/list_insert_at.branch new file mode 100644 index 000000000..f2308d115 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_insert_at.branch @@ -0,0 +1,4 @@ +[1, 9, 2, 3] +["index-fraction", "value"] +[7, 1, 9, 2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_insert_at.eigs b/tests/phase2/builtins/builtins/list_insert_at.eigs new file mode 100644 index 000000000..5e1a9cf3a --- /dev/null +++ b/tests/phase2/builtins/builtins/list_insert_at.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: list_insert_at (int-requiring; position (Lua table.insert)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [1, 2, 3] +list_insert_at of [xs, 1, 9] +print of xs +try: + list_insert_at of [xs, 1.5, 8] + print of ["fraction accepted", xs] +catch e: + print of ["index-fraction", e.kind] +list_insert_at of [xs, 0.0, 7] +print of xs diff --git a/tests/phase2/builtins/builtins/list_insert_at.pre b/tests/phase2/builtins/builtins/list_insert_at.pre new file mode 100644 index 000000000..7591d5402 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_insert_at.pre @@ -0,0 +1,4 @@ +[1, 9, 2, 3] +["fraction accepted", [1, 8, 9, 2, 3]] +[7, 1, 8, 9, 2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_insert_at.tag b/tests/phase2/builtins/builtins/list_insert_at.tag new file mode 100644 index 000000000..7591d5402 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_insert_at.tag @@ -0,0 +1,4 @@ +[1, 9, 2, 3] +["fraction accepted", [1, 8, 9, 2, 3]] +[7, 1, 8, 9, 2, 3] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_remove_at.branch b/tests/phase2/builtins/builtins/list_remove_at.branch new file mode 100644 index 000000000..1f3f3e1be --- /dev/null +++ b/tests/phase2/builtins/builtins/list_remove_at.branch @@ -0,0 +1,4 @@ +[1, 3, 4] +["index-fraction", "value"] +[3, 4] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_remove_at.eigs b/tests/phase2/builtins/builtins/list_remove_at.eigs new file mode 100644 index 000000000..fe839dd61 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_remove_at.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: list_remove_at (int-requiring; position (Lua table.remove)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [1, 2, 3, 4] +list_remove_at of [xs, 1] +print of xs +try: + list_remove_at of [xs, 0.5] + print of ["fraction accepted", xs] +catch e: + print of ["index-fraction", e.kind] +list_remove_at of [xs, 0.0] +print of xs diff --git a/tests/phase2/builtins/builtins/list_remove_at.pre b/tests/phase2/builtins/builtins/list_remove_at.pre new file mode 100644 index 000000000..4f6b8ce72 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_remove_at.pre @@ -0,0 +1,4 @@ +[1, 3, 4] +["fraction accepted", [3, 4]] +[4] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_remove_at.tag b/tests/phase2/builtins/builtins/list_remove_at.tag new file mode 100644 index 000000000..4f6b8ce72 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_remove_at.tag @@ -0,0 +1,4 @@ +[1, 3, 4] +["fraction accepted", [3, 4]] +[4] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_slice.branch b/tests/phase2/builtins/builtins/list_slice.branch new file mode 100644 index 000000000..4d0ae2dd4 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_slice.branch @@ -0,0 +1,5 @@ +[2, 3] +["start-fraction", "value"] +["end-fraction", "value"] +[2] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_slice.eigs b/tests/phase2/builtins/builtins/list_slice.eigs new file mode 100644 index 000000000..5dc3c71b2 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_slice.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: list_slice (int-requiring; start and end indices); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (list_slice of [[1, 2, 3], 1, 3]) +try: + print of ["fraction-answer", list_slice of [[1, 2, 3], 0.5, 2]] +catch e: + print of ["start-fraction", e.kind] +try: + print of ["fraction-answer", list_slice of [[1, 2, 3], 0, 2.5]] +catch e: + print of ["end-fraction", e.kind] +print of (list_slice of [[1, 2, 3], 1.0, 2.0]) diff --git a/tests/phase2/builtins/builtins/list_slice.pre b/tests/phase2/builtins/builtins/list_slice.pre new file mode 100644 index 000000000..cd1a205be --- /dev/null +++ b/tests/phase2/builtins/builtins/list_slice.pre @@ -0,0 +1,5 @@ +[2, 3] +["fraction-answer", [1, 2]] +["fraction-answer", [1, 2]] +[2] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_slice.tag b/tests/phase2/builtins/builtins/list_slice.tag new file mode 100644 index 000000000..cd1a205be --- /dev/null +++ b/tests/phase2/builtins/builtins/list_slice.tag @@ -0,0 +1,5 @@ +[2, 3] +["fraction-answer", [1, 2]] +["fraction-answer", [1, 2]] +[2] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_truncate.branch b/tests/phase2/builtins/builtins/list_truncate.branch new file mode 100644 index 000000000..58b297861 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_truncate.branch @@ -0,0 +1,4 @@ +[1, 2] +["length-fraction", "value"] +[1] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_truncate.eigs b/tests/phase2/builtins/builtins/list_truncate.eigs new file mode 100644 index 000000000..b38fb79e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_truncate.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: list_truncate (int-requiring; new length); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [1, 2, 3, 4] +list_truncate of [xs, 2] +print of xs +try: + list_truncate of [xs, 1.5] + print of ["fraction accepted", xs] +catch e: + print of ["length-fraction", e.kind] +list_truncate of [xs, 1.0] +print of xs diff --git a/tests/phase2/builtins/builtins/list_truncate.pre b/tests/phase2/builtins/builtins/list_truncate.pre new file mode 100644 index 000000000..628bbc5e9 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_truncate.pre @@ -0,0 +1,4 @@ +[1, 2] +["fraction accepted", [1]] +[1] +rc=0 diff --git a/tests/phase2/builtins/builtins/list_truncate.tag b/tests/phase2/builtins/builtins/list_truncate.tag new file mode 100644 index 000000000..628bbc5e9 --- /dev/null +++ b/tests/phase2/builtins/builtins/list_truncate.tag @@ -0,0 +1,4 @@ +[1, 2] +["fraction accepted", [1]] +[1] +rc=0 diff --git a/tests/phase2/builtins/builtins/log.branch b/tests/phase2/builtins/builtins/log.branch new file mode 100644 index 000000000..09aefb679 --- /dev/null +++ b/tests/phase2/builtins/builtins/log.branch @@ -0,0 +1,3 @@ +[0.0, "float"] +[2.0794415416798357, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/log.eigs b/tests/phase2/builtins/builtins/log.eigs new file mode 100644 index 000000000..0dc395d9b --- /dev/null +++ b/tests/phase2/builtins/builtins/log.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: log (float-producing; Lua math.log) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is log of 1 +print of [a, nt of a] +b is log of 8 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/log.pre b/tests/phase2/builtins/builtins/log.pre new file mode 100644 index 000000000..09e1e8cf0 --- /dev/null +++ b/tests/phase2/builtins/builtins/log.pre @@ -0,0 +1,3 @@ +[0, "float"] +[2.0794415416798357, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/log.tag b/tests/phase2/builtins/builtins/log.tag new file mode 100644 index 000000000..5c04009b7 --- /dev/null +++ b/tests/phase2/builtins/builtins/log.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[2.0794415416798357, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/log_softmax.branch b/tests/phase2/builtins/builtins/log_softmax.branch new file mode 100644 index 000000000..c3c78e6f9 --- /dev/null +++ b/tests/phase2/builtins/builtins/log_softmax.branch @@ -0,0 +1,2 @@ +[[-0.6931471805599453, -0.6931471805599453], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/log_softmax.eigs b/tests/phase2/builtins/builtins/log_softmax.eigs new file mode 100644 index 000000000..c55ead580 --- /dev/null +++ b/tests/phase2/builtins/builtins/log_softmax.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: log_softmax (float-producing; ML (float)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is log_softmax of [0, 0] +print of [r, nt of r[0]] diff --git a/tests/phase2/builtins/builtins/log_softmax.pre b/tests/phase2/builtins/builtins/log_softmax.pre new file mode 100644 index 000000000..c3c78e6f9 --- /dev/null +++ b/tests/phase2/builtins/builtins/log_softmax.pre @@ -0,0 +1,2 @@ +[[-0.6931471805599453, -0.6931471805599453], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/log_softmax.tag b/tests/phase2/builtins/builtins/log_softmax.tag new file mode 100644 index 000000000..39ee4f4e1 --- /dev/null +++ b/tests/phase2/builtins/builtins/log_softmax.tag @@ -0,0 +1,2 @@ +[[-0.6931471805599453, -0.6931471805599453], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul.branch b/tests/phase2/builtins/builtins/matmul.branch new file mode 100644 index 000000000..e1678b977 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul.branch @@ -0,0 +1,2 @@ +[[[19.0, 22.0], [43.0, 50.0]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul.eigs b/tests/phase2/builtins/builtins/matmul.eigs new file mode 100644 index 000000000..8f09459ec --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: matmul (float-producing; float tensors) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is matmul of [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +print of [r, nt of r[0][0]] diff --git a/tests/phase2/builtins/builtins/matmul.pre b/tests/phase2/builtins/builtins/matmul.pre new file mode 100644 index 000000000..0a55f5d57 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul.pre @@ -0,0 +1,2 @@ +[[[19, 22], [43, 50]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul.tag b/tests/phase2/builtins/builtins/matmul.tag new file mode 100644 index 000000000..d99aaa457 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul.tag @@ -0,0 +1,2 @@ +[[[19, 22], [43, 50]], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_at.branch b/tests/phase2/builtins/builtins/matmul_at.branch new file mode 100644 index 000000000..208da68e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_at.branch @@ -0,0 +1,2 @@ +[[[26.0, 30.0], [38.0, 44.0]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_at.eigs b/tests/phase2/builtins/builtins/matmul_at.eigs new file mode 100644 index 000000000..06f43538b --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_at.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: matmul_at (float-producing; float tensors) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is matmul_at of [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +print of [r, nt of r[0][0]] diff --git a/tests/phase2/builtins/builtins/matmul_at.pre b/tests/phase2/builtins/builtins/matmul_at.pre new file mode 100644 index 000000000..2b3f1acd0 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_at.pre @@ -0,0 +1,2 @@ +[[[26, 30], [38, 44]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_at.tag b/tests/phase2/builtins/builtins/matmul_at.tag new file mode 100644 index 000000000..d15c8f63d --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_at.tag @@ -0,0 +1,2 @@ +[[[26, 30], [38, 44]], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_bt.branch b/tests/phase2/builtins/builtins/matmul_bt.branch new file mode 100644 index 000000000..669c119ad --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_bt.branch @@ -0,0 +1,2 @@ +[[[17.0, 23.0], [39.0, 53.0]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_bt.eigs b/tests/phase2/builtins/builtins/matmul_bt.eigs new file mode 100644 index 000000000..6a9ab35b0 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_bt.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: matmul_bt (float-producing; float tensors) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is matmul_bt of [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +print of [r, nt of r[0][0]] diff --git a/tests/phase2/builtins/builtins/matmul_bt.pre b/tests/phase2/builtins/builtins/matmul_bt.pre new file mode 100644 index 000000000..7c0fbc983 --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_bt.pre @@ -0,0 +1,2 @@ +[[[17, 23], [39, 53]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/matmul_bt.tag b/tests/phase2/builtins/builtins/matmul_bt.tag new file mode 100644 index 000000000..33ff76dde --- /dev/null +++ b/tests/phase2/builtins/builtins/matmul_bt.tag @@ -0,0 +1,2 @@ +[[[17, 23], [39, 53]], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/max.branch b/tests/phase2/builtins/builtins/max.branch new file mode 100644 index 000000000..3e667187d --- /dev/null +++ b/tests/phase2/builtins/builtins/max.branch @@ -0,0 +1,8 @@ +[9007199254740993, "int"] +[3, "int"] +[2.5, "float"] +[7, "int"] +[3, "int", 3.0, "float"] +["empty", "value"] +["empty-list", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/max.eigs b/tests/phase2/builtins/builtins/max.eigs new file mode 100644 index 000000000..662ef8b3f --- /dev/null +++ b/tests/phase2/builtins/builtins/max.eigs @@ -0,0 +1,23 @@ +# R1.6 behaviour-table probe: max (int-preserving, LOSS; Lua math.max returns the winning argument) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is max of [9007199254740993, 9007199254740992] +print of [a, nt of a] +b is max of [3, 2.5] +print of [b, nt of b] +c is max of [2, 2.5] +print of [c, nt of c] +d is max of 7 +print of [d, nt of d] +print of [max of [3, 3.0], nt of (max of [3, 3.0]), max of [3.0, 3], nt of (max of [3.0, 3])] +try: + print of ["empty-answer", max of []] +catch e: + print of ["empty", e.kind] +try: + print of ["empty-list-answer", max of ([])] +catch e: + print of ["empty-list", e.kind] diff --git a/tests/phase2/builtins/builtins/max.pre b/tests/phase2/builtins/builtins/max.pre new file mode 100644 index 000000000..653ce781c --- /dev/null +++ b/tests/phase2/builtins/builtins/max.pre @@ -0,0 +1,8 @@ +[9007199254740992, "float"] +[3, "float"] +[2.5, "float"] +[7, "float"] +[3, "float", 3, "float"] +["empty-answer", 0] +["empty-list-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/max.tag b/tests/phase2/builtins/builtins/max.tag new file mode 100644 index 000000000..a28e30291 --- /dev/null +++ b/tests/phase2/builtins/builtins/max.tag @@ -0,0 +1,8 @@ +[9007199254740992, "undefined_name"] +[3, "undefined_name"] +[2.5, "undefined_name"] +[7, "undefined_name"] +[3, "undefined_name", 3, "undefined_name"] +["empty-answer", 0] +["empty-list-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/mean.branch b/tests/phase2/builtins/builtins/mean.branch new file mode 100644 index 000000000..9d1e9d386 --- /dev/null +++ b/tests/phase2/builtins/builtins/mean.branch @@ -0,0 +1,3 @@ +[1.5, "float"] +[3.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/mean.eigs b/tests/phase2/builtins/builtins/mean.eigs new file mode 100644 index 000000000..2db443ca0 --- /dev/null +++ b/tests/phase2/builtins/builtins/mean.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: mean (float-producing; arithmetic mean) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is mean of [1, 2] +print of [a, nt of a] +b is mean of [2, 4] +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/mean.pre b/tests/phase2/builtins/builtins/mean.pre new file mode 100644 index 000000000..4abaddb8e --- /dev/null +++ b/tests/phase2/builtins/builtins/mean.pre @@ -0,0 +1,3 @@ +[1.5, "float"] +[3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/mean.tag b/tests/phase2/builtins/builtins/mean.tag new file mode 100644 index 000000000..d8aaedd73 --- /dev/null +++ b/tests/phase2/builtins/builtins/mean.tag @@ -0,0 +1,3 @@ +[1.5, "undefined_name"] +[3, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/min.branch b/tests/phase2/builtins/builtins/min.branch new file mode 100644 index 000000000..e8f606712 --- /dev/null +++ b/tests/phase2/builtins/builtins/min.branch @@ -0,0 +1,8 @@ +[9007199254740993, "int"] +[3, "int"] +[3.5, "float"] +[7, "int"] +[3, "int", 3.0, "float"] +["empty", "value"] +["empty-list", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/min.eigs b/tests/phase2/builtins/builtins/min.eigs new file mode 100644 index 000000000..d55b8ef20 --- /dev/null +++ b/tests/phase2/builtins/builtins/min.eigs @@ -0,0 +1,23 @@ +# R1.6 behaviour-table probe: min (int-preserving, LOSS; Lua math.min returns the winning argument) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is min of [9007199254740993, 9007199254740994] +print of [a, nt of a] +b is min of [3, 3.5] +print of [b, nt of b] +c is min of [4, 3.5] +print of [c, nt of c] +d is min of 7 +print of [d, nt of d] +print of [min of [3, 3.0], nt of (min of [3, 3.0]), min of [3.0, 3], nt of (min of [3.0, 3])] +try: + print of ["empty-answer", min of []] +catch e: + print of ["empty", e.kind] +try: + print of ["empty-list-answer", min of ([])] +catch e: + print of ["empty-list", e.kind] diff --git a/tests/phase2/builtins/builtins/min.pre b/tests/phase2/builtins/builtins/min.pre new file mode 100644 index 000000000..849911e68 --- /dev/null +++ b/tests/phase2/builtins/builtins/min.pre @@ -0,0 +1,8 @@ +[9007199254740992, "float"] +[3, "float"] +[3.5, "float"] +[7, "float"] +[3, "float", 3, "float"] +["empty-answer", 0] +["empty-list-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/min.tag b/tests/phase2/builtins/builtins/min.tag new file mode 100644 index 000000000..99e2bb6ee --- /dev/null +++ b/tests/phase2/builtins/builtins/min.tag @@ -0,0 +1,8 @@ +[9007199254740992, "undefined_name"] +[3, "undefined_name"] +[3.5, "undefined_name"] +[7, "undefined_name"] +[3, "undefined_name", 3, "undefined_name"] +["empty-answer", 0] +["empty-list-answer", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/model_load_weights.branch b/tests/phase2/builtins/builtins/model_load_weights.branch new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/model_load_weights.branch @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/model_load_weights.eigs b/tests/phase2/builtins/builtins/model_load_weights.eigs new file mode 100644 index 000000000..489ec0f76 --- /dev/null +++ b/tests/phase2/builtins/builtins/model_load_weights.eigs @@ -0,0 +1,33 @@ +# R1.6 behaviour-table probe: model_load_weights (other; alias of eigen_model_load; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define mk_mat(r, c) as: + local m is [] + for i in range of r: + local row is [] + for j in range of c: + append of [row, 0.5] + append of [m, row] + return m +define mk_vec(k, v) as: + local out is [] + for i in range of k: + append of [out, v] + return out +p is env_get of "EIGS_PROBE_TMP" +lay is "{\"w_q\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_k\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_v\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_o\":" + (json_encode of (mk_mat of [4, 4])) +lay is lay + ",\"w_ff1\":" + (json_encode of (mk_mat of [4, 8])) + ",\"w_ff2\":" + (json_encode of (mk_mat of [8, 4])) +lay is lay + ",\"ln1_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln1_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + ",\"ln2_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln2_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + "}" +cfg is "{\"vocab_size\":8,\"d_model\":4,\"n_layers\":1,\"d_ff\":8,\"max_seq_len\":16}" +model is "{\"format_version\":2,\"weight_format\":\"ternary_weight_only\",\"config\":" + cfg + ",\"token_embeddings\":" + (json_encode of (mk_mat of [8, 4])) + ",\"output_proj\":" + (json_encode of (mk_mat of [4, 8])) + ",\"layers\":[" + lay + "]}" +write_text of [p + "/model.json", model] +child is ["define nt(x) as:", " try:", " return num_type of x", " catch e:", " return e.kind", "define say(v) as:", " print of (\"R16|\" + (str of v))", "p is env_get of \"EIGS_PROBE_TMP\"", "try:", " s is model_load_weights of (p + \"/model.json\")", "catch e:", " say of [\"capability\", e.kind, e.message]", " exit of 0", "d is json_decode of s", "say of [d[\"status\"], d[\"vocab_size\"], d[\"d_model\"], d[\"n_layers\"], d[\"d_ff\"]]", "say of (eigen_model_loaded of null)"] +write_text of [p + "/child.eigs", join of [child, "\n"]] +r is exec_capture of [exe_path of null, p + "/child.eigs"] +for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) +print of ["child-rc", r[0]] diff --git a/tests/phase2/builtins/builtins/model_load_weights.pre b/tests/phase2/builtins/builtins/model_load_weights.pre new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/model_load_weights.pre @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/model_load_weights.tag b/tests/phase2/builtins/builtins/model_load_weights.tag new file mode 100644 index 000000000..e15c55ad5 --- /dev/null +++ b/tests/phase2/builtins/builtins/model_load_weights.tag @@ -0,0 +1,4 @@ +["loaded", 8, 4, 1, 8] +true +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ms.branch b/tests/phase2/builtins/builtins/monotonic_ms.branch new file mode 100644 index 000000000..36397c45d --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ms.branch @@ -0,0 +1,2 @@ +["float", "float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ms.eigs b/tests/phase2/builtins/builtins/monotonic_ms.eigs new file mode 100644 index 000000000..05b99b231 --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ms.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: monotonic_ms (float-producing; fractional milliseconds) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is monotonic_ms of null +b is monotonic_ms of null +print of [nt of a, nt of b, b >= a] diff --git a/tests/phase2/builtins/builtins/monotonic_ms.pre b/tests/phase2/builtins/builtins/monotonic_ms.pre new file mode 100644 index 000000000..36397c45d --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ms.pre @@ -0,0 +1,2 @@ +["float", "float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ms.tag b/tests/phase2/builtins/builtins/monotonic_ms.tag new file mode 100644 index 000000000..55e46f57a --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ms.tag @@ -0,0 +1,2 @@ +["undefined_name", "undefined_name", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ns.branch b/tests/phase2/builtins/builtins/monotonic_ns.branch new file mode 100644 index 000000000..472383445 --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ns.branch @@ -0,0 +1,3 @@ +[9007199254740993, "int", true] +hook-loaded clock +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ns.eigs b/tests/phase2/builtins/builtins/monotonic_ns.eigs new file mode 100644 index 000000000..ed5e66142 --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ns.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: monotonic_ns (int-preserving, LOSS; exact below 2^53 ns, an exact int under N1; HOOK clock: CLOCK_MONOTONIC answers 2^53 + 1 ns (ruling B2)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +log is env_get of "R16_HOOK_LOG" +n is monotonic_ns of null +print of [n, nt of n, n == 9007199254740993] +write of (read_text of log) diff --git a/tests/phase2/builtins/builtins/monotonic_ns.pre b/tests/phase2/builtins/builtins/monotonic_ns.pre new file mode 100644 index 000000000..0a526b451 --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ns.pre @@ -0,0 +1,3 @@ +[9007199254740992, "float", false] +hook-loaded clock +rc=0 diff --git a/tests/phase2/builtins/builtins/monotonic_ns.tag b/tests/phase2/builtins/builtins/monotonic_ns.tag new file mode 100644 index 000000000..ab5d40bfe --- /dev/null +++ b/tests/phase2/builtins/builtins/monotonic_ns.tag @@ -0,0 +1,3 @@ +[9007199254740992, "undefined_name", true] +hook-loaded clock +rc=0 diff --git a/tests/phase2/builtins/builtins/multiply.branch b/tests/phase2/builtins/builtins/multiply.branch new file mode 100644 index 000000000..19b7a041d --- /dev/null +++ b/tests/phase2/builtins/builtins/multiply.branch @@ -0,0 +1,4 @@ +[9223372030926249001, "int"] +["overflow", "value"] +[[6, 9223372030926249001], "int", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/multiply.eigs b/tests/phase2/builtins/builtins/multiply.eigs new file mode 100644 index 000000000..8a1c1bd80 --- /dev/null +++ b/tests/phase2/builtins/builtins/multiply.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: multiply (int-preserving, LOSS; scalar arm mirrors *, (A)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is multiply of [3037000499, 3037000499] +print of [r, nt of r] +try: + print of (multiply of [4611686018427387904, 2]) +catch e: + print of ["overflow", e.kind] +l is multiply of [[3, 3037000499], [2, 3037000499]] +print of [l, nt of l[0], nt of l[1]] diff --git a/tests/phase2/builtins/builtins/multiply.pre b/tests/phase2/builtins/builtins/multiply.pre new file mode 100644 index 000000000..5c79e096d --- /dev/null +++ b/tests/phase2/builtins/builtins/multiply.pre @@ -0,0 +1,4 @@ +[9.223372030926249e+18, "float"] +9.223372036854776e+18 +[[6, 9.223372030926249e+18], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/multiply.tag b/tests/phase2/builtins/builtins/multiply.tag new file mode 100644 index 000000000..4e232fb9d --- /dev/null +++ b/tests/phase2/builtins/builtins/multiply.tag @@ -0,0 +1,4 @@ +[9.223372030926249e+18, "undefined_name"] +9.223372036854776e+18 +[[6, 9.223372030926249e+18], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/native_train_step_builtin.branch b/tests/phase2/builtins/builtins/native_train_step_builtin.branch new file mode 100644 index 000000000..83fb49d59 --- /dev/null +++ b/tests/phase2/builtins/builtins/native_train_step_builtin.branch @@ -0,0 +1,7 @@ +["status", "trained"] +["input-fraction", "value"] +["output-fraction", "value"] +["status", "trained"] +["string-lr", "trained"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/native_train_step_builtin.eigs b/tests/phase2/builtins/builtins/native_train_step_builtin.eigs new file mode 100644 index 000000000..8a75a5de0 --- /dev/null +++ b/tests/phase2/builtins/builtins/native_train_step_builtin.eigs @@ -0,0 +1,33 @@ +# R1.6 behaviour-table probe: native_train_step_builtin (int-requiring; token ids are integer-valued (N5, Q1); lr a float or a numeric string; PROFILE server (make server)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define mk_mat(r, c) as: + local m is [] + for i in range of r: + local row is [] + for j in range of c: + append of [row, 0.5] + append of [m, row] + return m +define mk_vec(k, v) as: + local out is [] + for i in range of k: + append of [out, v] + return out +p is env_get of "EIGS_PROBE_TMP" +lay is "{\"w_q\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_k\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_v\":" + (json_encode of (mk_mat of [4, 4])) + ",\"w_o\":" + (json_encode of (mk_mat of [4, 4])) +lay is lay + ",\"w_ff1\":" + (json_encode of (mk_mat of [4, 8])) + ",\"w_ff2\":" + (json_encode of (mk_mat of [8, 4])) +lay is lay + ",\"ln1_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln1_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + ",\"ln2_gamma\":" + (json_encode of (mk_vec of [4, 1.0])) + ",\"ln2_beta\":" + (json_encode of (mk_vec of [4, 0.0])) + "}" +cfg is "{\"vocab_size\":8,\"d_model\":4,\"n_layers\":1,\"d_ff\":8,\"max_seq_len\":16}" +model is "{\"format_version\":2,\"weight_format\":\"ternary_weight_only\",\"config\":" + cfg + ",\"token_embeddings\":" + (json_encode of (mk_mat of [8, 4])) + ",\"output_proj\":" + (json_encode of (mk_mat of [4, 8])) + ",\"layers\":[" + lay + "]}" +write_text of [p + "/model.json", model] +child is ["define nt(x) as:", " try:", " return num_type of x", " catch e:", " return e.kind", "define say(v) as:", " print of (\"R16|\" + (str of v))", "p is env_get of \"EIGS_PROBE_TMP\"", "try:", " s is eigen_model_load of (p + \"/model.json\")", "catch e:", " say of [\"capability\", e.kind, e.message]", " exit of 0", "a is json_decode of (native_train_step_builtin of [[1, 2, 3], [4], 0.01])", "say of [\"status\", a[\"status\"]]", "try:", " say of [\"fraction-answer\", native_train_step_builtin of [[1.5, 2], [3], 0.01]]", "catch e:", " say of [\"input-fraction\", e.kind]", "try:", " say of [\"fraction-answer\", native_train_step_builtin of [[1, 2], [3.5], 0.01]]", "catch e:", " say of [\"output-fraction\", e.kind]", "b is json_decode of (native_train_step_builtin of [[1.0, 2.0], [3.0], 0.01])", "say of [\"status\", b[\"status\"]]", "c is json_decode of (native_train_step_builtin of [[1, 2], [3], \"0.01\"])", "say of [\"string-lr\", c[\"status\"]]"] +write_text of [p + "/child.eigs", join of [child, "\n"]] +r is exec_capture of [exe_path of null, p + "/child.eigs"] +for line in split of [r[1], "\n"]: + if starts_with of [line, "R16|"]: + print of (substr of [line, 4, 100000]) +print of ["child-rc", r[0]] diff --git a/tests/phase2/builtins/builtins/native_train_step_builtin.pre b/tests/phase2/builtins/builtins/native_train_step_builtin.pre new file mode 100644 index 000000000..70d215a0d --- /dev/null +++ b/tests/phase2/builtins/builtins/native_train_step_builtin.pre @@ -0,0 +1,7 @@ +["status", "trained"] +["fraction-answer", "{\"status\": \"trained\", \"loss\": 2.045051, \"tokens_trained\": 2, \"model_age\": 5, \"training_samples\": 2, \"effective_lr\": 0.004893, \"engine\": \"eigenscript\"}"] +["fraction-answer", "{\"status\": \"trained\", \"loss\": 2.022891, \"tokens_trained\": 2, \"model_age\": 7, \"training_samples\": 3, \"effective_lr\": 0.004398, \"engine\": \"eigenscript\"}"] +["status", "trained"] +["string-lr", "trained"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/native_train_step_builtin.tag b/tests/phase2/builtins/builtins/native_train_step_builtin.tag new file mode 100644 index 000000000..70d215a0d --- /dev/null +++ b/tests/phase2/builtins/builtins/native_train_step_builtin.tag @@ -0,0 +1,7 @@ +["status", "trained"] +["fraction-answer", "{\"status\": \"trained\", \"loss\": 2.045051, \"tokens_trained\": 2, \"model_age\": 5, \"training_samples\": 2, \"effective_lr\": 0.004893, \"engine\": \"eigenscript\"}"] +["fraction-answer", "{\"status\": \"trained\", \"loss\": 2.022891, \"tokens_trained\": 2, \"model_age\": 7, \"training_samples\": 3, \"effective_lr\": 0.004398, \"engine\": \"eigenscript\"}"] +["status", "trained"] +["string-lr", "trained"] +["child-rc", 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range.branch b/tests/phase2/builtins/builtins/nearest_in_range.branch new file mode 100644 index 000000000..0798970c3 --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range.branch @@ -0,0 +1,2 @@ +[0, "int", 1.4142135623730951, "float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range.eigs b/tests/phase2/builtins/builtins/nearest_in_range.eigs new file mode 100644 index 000000000..7200ece94 --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: nearest_in_range (float-producing; geometry floats, the index field is an int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ents is [{"px": 1, "py": 1, "active": true}, {"px": 5, "py": 5, "active": true}] +r is nearest_in_range of [ents, 0, 0, 3, 100, 100] +print of [r["index"], nt of r["index"], r["dist"], nt of r["dist"], nt of r["dx"], nt of r["dy"]] diff --git a/tests/phase2/builtins/builtins/nearest_in_range.pre b/tests/phase2/builtins/builtins/nearest_in_range.pre new file mode 100644 index 000000000..342d591d7 --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range.pre @@ -0,0 +1,2 @@ +[0, "float", 1.4142135623730951, "float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range.tag b/tests/phase2/builtins/builtins/nearest_in_range.tag new file mode 100644 index 000000000..0c398c7ba --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range.tag @@ -0,0 +1,2 @@ +[0, "undefined_name", 1.4142135623730951, "undefined_name", "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range_all.branch b/tests/phase2/builtins/builtins/nearest_in_range_all.branch new file mode 100644 index 000000000..ef7487500 --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range_all.branch @@ -0,0 +1,2 @@ +[0, "int", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range_all.eigs b/tests/phase2/builtins/builtins/nearest_in_range_all.eigs new file mode 100644 index 000000000..546a69d0b --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range_all.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: nearest_in_range_all (float-producing; geometry floats, the index field is an int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +ents is [{"px": 1, "py": 1, "active": true}, {"px": 5, "py": 5, "active": true}] +r is nearest_in_range_all of [ents, 10, 100, 100] +print of [r[1]["index"], nt of r[1]["index"], nt of r[1]["dist"], nt of r[1]["dx"]] diff --git a/tests/phase2/builtins/builtins/nearest_in_range_all.pre b/tests/phase2/builtins/builtins/nearest_in_range_all.pre new file mode 100644 index 000000000..1d0fe58f4 --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range_all.pre @@ -0,0 +1,2 @@ +[1, "float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/nearest_in_range_all.tag b/tests/phase2/builtins/builtins/nearest_in_range_all.tag new file mode 100644 index 000000000..09afa161e --- /dev/null +++ b/tests/phase2/builtins/builtins/nearest_in_range_all.tag @@ -0,0 +1,2 @@ +[1, "undefined_name", "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/negative.branch b/tests/phase2/builtins/builtins/negative.branch new file mode 100644 index 000000000..4fe8d05b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/negative.branch @@ -0,0 +1,4 @@ +[-9007199254740993, "int"] +[-2.5, "float"] +["int64-min", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/negative.eigs b/tests/phase2/builtins/builtins/negative.eigs new file mode 100644 index 000000000..d924be23b --- /dev/null +++ b/tests/phase2/builtins/builtins/negative.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: negative (int-preserving, LOSS; mirrors unary -, N4b) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is negative of 9007199254740993 +print of [r, nt of r] +f is negative of 2.5 +print of [f, nt of f] +try: + print of (negative of (-9223372036854775807 - 1)) +catch e: + print of ["int64-min", e.kind] diff --git a/tests/phase2/builtins/builtins/negative.pre b/tests/phase2/builtins/builtins/negative.pre new file mode 100644 index 000000000..3bae05936 --- /dev/null +++ b/tests/phase2/builtins/builtins/negative.pre @@ -0,0 +1,4 @@ +[-9007199254740992, "float"] +[-2.5, "float"] +9.223372036854776e+18 +rc=0 diff --git a/tests/phase2/builtins/builtins/negative.tag b/tests/phase2/builtins/builtins/negative.tag new file mode 100644 index 000000000..cd9284821 --- /dev/null +++ b/tests/phase2/builtins/builtins/negative.tag @@ -0,0 +1,4 @@ +[-9007199254740992, "undefined_name"] +[-2.5, "undefined_name"] +9.223372036854776e+18 +rc=0 diff --git a/tests/phase2/builtins/builtins/net_accept.branch b/tests/phase2/builtins/builtins/net_accept.branch new file mode 100644 index 000000000..77b0d62fe --- /dev/null +++ b/tests/phase2/builtins/builtins/net_accept.branch @@ -0,0 +1,3 @@ +["handle-fraction", "value"] +["int", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_accept.eigs b/tests/phase2/builtins/builtins/net_accept.eigs new file mode 100644 index 000000000..3c6c0e652 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_accept.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: net_accept (int-requiring; socket handle; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +c is net_dial of ["127.0.0.1", pt] +try: + print of ["fraction-answer", (net_accept of [l + 0.5, 2000]) != null] +catch e: + print of ["handle-fraction", e.kind] +a is net_accept of [l + 0.0, 2000] +print of [nt of a, a != null] diff --git a/tests/phase2/builtins/builtins/net_accept.pre b/tests/phase2/builtins/builtins/net_accept.pre new file mode 100644 index 000000000..cad402d6c --- /dev/null +++ b/tests/phase2/builtins/builtins/net_accept.pre @@ -0,0 +1,3 @@ +["fraction-answer", true] +[null, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_accept.tag b/tests/phase2/builtins/builtins/net_accept.tag new file mode 100644 index 000000000..28703bf53 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_accept.tag @@ -0,0 +1,3 @@ +["fraction-answer", true] +["undefined_name", false] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_close.branch b/tests/phase2/builtins/builtins/net_close.branch new file mode 100644 index 000000000..7eafbe2da --- /dev/null +++ b/tests/phase2/builtins/builtins/net_close.branch @@ -0,0 +1,3 @@ +["handle-fraction", "value"] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/net_close.eigs b/tests/phase2/builtins/builtins/net_close.eigs new file mode 100644 index 000000000..9c86cfe0b --- /dev/null +++ b/tests/phase2/builtins/builtins/net_close.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: net_close (int-requiring; socket handle; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +try: + print of ["fraction-answer", net_close of (l + 0.5)] +catch e: + print of ["handle-fraction", e.kind] +print of (net_close of (l + 0.0)) diff --git a/tests/phase2/builtins/builtins/net_close.pre b/tests/phase2/builtins/builtins/net_close.pre new file mode 100644 index 000000000..e13142420 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_close.pre @@ -0,0 +1,3 @@ +["fraction-answer", null] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/net_close.tag b/tests/phase2/builtins/builtins/net_close.tag new file mode 100644 index 000000000..e13142420 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_close.tag @@ -0,0 +1,3 @@ +["fraction-answer", null] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/net_dial.branch b/tests/phase2/builtins/builtins/net_dial.branch new file mode 100644 index 000000000..4ac8e5014 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_dial.branch @@ -0,0 +1,5 @@ +["int", true] +["port-fraction", "value"] +["port-65536", "value"] +["int", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_dial.eigs b/tests/phase2/builtins/builtins/net_dial.eigs new file mode 100644 index 000000000..db06c035f --- /dev/null +++ b/tests/phase2/builtins/builtins/net_dial.eigs @@ -0,0 +1,24 @@ +# R1.6 behaviour-table probe: net_dial (int-requiring; port; returns a handle; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +c is net_dial of ["127.0.0.1", pt] +print of [nt of c, c > 0] +try: + print of ["fraction-answer", (net_dial of ["127.0.0.1", pt + 0.5]) > 0] +catch e: + print of ["port-fraction", e.kind] +try: + print of ["65536-answer", (net_dial of ["127.0.0.1", 65536]) > 0] +catch e: + print of ["port-65536", e.kind] +d is net_dial of ["127.0.0.1", pt + 0.0] +print of [nt of d, d > 0] diff --git a/tests/phase2/builtins/builtins/net_dial.pre b/tests/phase2/builtins/builtins/net_dial.pre new file mode 100644 index 000000000..601b99dd6 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_dial.pre @@ -0,0 +1,5 @@ +["float", true] +["fraction-answer", true] +["port-65536", "type_mismatch"] +["float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_dial.tag b/tests/phase2/builtins/builtins/net_dial.tag new file mode 100644 index 000000000..202ae085d --- /dev/null +++ b/tests/phase2/builtins/builtins/net_dial.tag @@ -0,0 +1,5 @@ +["undefined_name", true] +["fraction-answer", true] +["port-65536", "type_mismatch"] +["undefined_name", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_listen.branch b/tests/phase2/builtins/builtins/net_listen.branch new file mode 100644 index 000000000..4ac8e5014 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_listen.branch @@ -0,0 +1,5 @@ +["int", true] +["port-fraction", "value"] +["port-65536", "value"] +["int", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_listen.eigs b/tests/phase2/builtins/builtins/net_listen.eigs new file mode 100644 index 000000000..8ad05323f --- /dev/null +++ b/tests/phase2/builtins/builtins/net_listen.eigs @@ -0,0 +1,23 @@ +# R1.6 behaviour-table probe: net_listen (int-requiring; port; returns a handle; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +print of [nt of l, l > 0] +try: + print of ["fraction-answer", (net_listen of 0.5) > 0] +catch e: + print of ["port-fraction", e.kind] +try: + print of ["65536-answer", (net_listen of 65536) > 0] +catch e: + print of ["port-65536", e.kind] +m is net_listen of 0.0 +print of [nt of m, m > 0] diff --git a/tests/phase2/builtins/builtins/net_listen.pre b/tests/phase2/builtins/builtins/net_listen.pre new file mode 100644 index 000000000..601b99dd6 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_listen.pre @@ -0,0 +1,5 @@ +["float", true] +["fraction-answer", true] +["port-65536", "type_mismatch"] +["float", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_listen.tag b/tests/phase2/builtins/builtins/net_listen.tag new file mode 100644 index 000000000..202ae085d --- /dev/null +++ b/tests/phase2/builtins/builtins/net_listen.tag @@ -0,0 +1,5 @@ +["undefined_name", true] +["fraction-answer", true] +["port-65536", "type_mismatch"] +["undefined_name", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_port.branch b/tests/phase2/builtins/builtins/net_port.branch new file mode 100644 index 000000000..47a354fa4 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_port.branch @@ -0,0 +1,4 @@ +["int", true] +["handle-fraction", "value"] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/net_port.eigs b/tests/phase2/builtins/builtins/net_port.eigs new file mode 100644 index 000000000..bee3f12a7 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_port.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: net_port (int-requiring; socket handle; returns a port; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +print of [nt of pt, pt > 0] +try: + print of ["fraction-answer", (net_port of (l + 0.5)) == pt] +catch e: + print of ["handle-fraction", e.kind] +print of ((net_port of (l + 0.0)) == pt) diff --git a/tests/phase2/builtins/builtins/net_port.pre b/tests/phase2/builtins/builtins/net_port.pre new file mode 100644 index 000000000..15829bc10 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_port.pre @@ -0,0 +1,4 @@ +["float", true] +["fraction-answer", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/net_port.tag b/tests/phase2/builtins/builtins/net_port.tag new file mode 100644 index 000000000..f69e78772 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_port.tag @@ -0,0 +1,4 @@ +["undefined_name", true] +["fraction-answer", true] +true +rc=0 diff --git a/tests/phase2/builtins/builtins/net_recv.branch b/tests/phase2/builtins/builtins/net_recv.branch new file mode 100644 index 000000000..d3dc1561f --- /dev/null +++ b/tests/phase2/builtins/builtins/net_recv.branch @@ -0,0 +1,4 @@ +["handle-fraction", "value"] +["max-fraction", "value"] +["hi", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/net_recv.eigs b/tests/phase2/builtins/builtins/net_recv.eigs new file mode 100644 index 000000000..9a9b02b66 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_recv.eigs @@ -0,0 +1,25 @@ +# R1.6 behaviour-table probe: net_recv (int-requiring; handle and max bytes; bytes stored as floats; PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +c is net_dial of ["127.0.0.1", pt] +a is net_accept of [l, 2000] +n is net_send of [c, "hi"] +try: + print of ["fraction-answer", net_recv of [a + 0.5, 16, 2000]] +catch e: + print of ["handle-fraction", e.kind] +try: + print of ["fraction-answer", net_recv of [a, 16.5, 2000]] +catch e: + print of ["max-fraction", e.kind] +b is net_recv of [a + 0.0, 16.0, 2000] +print of [str_from_bytes of b, nt of b[0]] diff --git a/tests/phase2/builtins/builtins/net_recv.pre b/tests/phase2/builtins/builtins/net_recv.pre new file mode 100644 index 000000000..0ad35e658 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_recv.pre @@ -0,0 +1,3 @@ +["fraction-answer", ] +["fraction-answer", null] +rc=1 diff --git a/tests/phase2/builtins/builtins/net_recv.tag b/tests/phase2/builtins/builtins/net_recv.tag new file mode 100644 index 000000000..0ad35e658 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_recv.tag @@ -0,0 +1,3 @@ +["fraction-answer", ] +["fraction-answer", null] +rc=1 diff --git a/tests/phase2/builtins/builtins/net_send.branch b/tests/phase2/builtins/builtins/net_send.branch new file mode 100644 index 000000000..2ee0d5319 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_send.branch @@ -0,0 +1,7 @@ +[2, "int"] +["handle-fraction", "value"] +["byte-300", "value"] +["byte-fraction", "value"] +[2, "int"] +hihi +rc=0 diff --git a/tests/phase2/builtins/builtins/net_send.eigs b/tests/phase2/builtins/builtins/net_send.eigs new file mode 100644 index 000000000..9d4d45bfc --- /dev/null +++ b/tests/phase2/builtins/builtins/net_send.eigs @@ -0,0 +1,32 @@ +# R1.6 behaviour-table probe: net_send (int-requiring; the handle and the byte values: N5, bytes then 0..255 (Q1); PROFILE server (make server), loopback only); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + l is net_listen of 0 +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +pt is net_port of l +c is net_dial of ["127.0.0.1", pt] +a is net_accept of [l, 2000] +n is net_send of [c, "hi"] +print of [n, nt of n] +try: + print of ["fraction-answer", net_send of [c + 0.5, "x"]] +catch e: + print of ["handle-fraction", e.kind] +try: + print of ["300-answer", net_send of [c, [65, 300]]] +catch e: + print of ["byte-300", e.kind] +try: + print of ["fraction-answer", net_send of [c, [65.5, 66]]] +catch e: + print of ["byte-fraction", e.kind] +m is net_send of [c + 0.0, [104.0, 105.0]] +print of [m, nt of m] +r is net_recv of [a, 16, 2000] +print of (str_from_bytes of r) diff --git a/tests/phase2/builtins/builtins/net_send.pre b/tests/phase2/builtins/builtins/net_send.pre new file mode 100644 index 000000000..f53ede3f0 --- /dev/null +++ b/tests/phase2/builtins/builtins/net_send.pre @@ -0,0 +1,7 @@ +[2, "float"] +["fraction-answer", 1] +["300-answer", 2] +["fraction-answer", 2] +[2, "float"] +hixA,ABhi +rc=0 diff --git a/tests/phase2/builtins/builtins/net_send.tag b/tests/phase2/builtins/builtins/net_send.tag new file mode 100644 index 000000000..be8d09dcf --- /dev/null +++ b/tests/phase2/builtins/builtins/net_send.tag @@ -0,0 +1,7 @@ +[2, "undefined_name"] +["fraction-answer", 1] +["300-answer", 2] +["fraction-answer", 2] +[2, "undefined_name"] +hixA,ABhi +rc=0 diff --git a/tests/phase2/builtins/builtins/norm.branch b/tests/phase2/builtins/builtins/norm.branch new file mode 100644 index 000000000..735ae1227 --- /dev/null +++ b/tests/phase2/builtins/builtins/norm.branch @@ -0,0 +1,2 @@ +[5.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/norm.eigs b/tests/phase2/builtins/builtins/norm.eigs new file mode 100644 index 000000000..38536c324 --- /dev/null +++ b/tests/phase2/builtins/builtins/norm.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: norm (float-producing; L2 norm) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is norm of [3, 4] +print of [a, nt of a] diff --git a/tests/phase2/builtins/builtins/norm.pre b/tests/phase2/builtins/builtins/norm.pre new file mode 100644 index 000000000..a7f117d08 --- /dev/null +++ b/tests/phase2/builtins/builtins/norm.pre @@ -0,0 +1,2 @@ +[5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/norm.tag b/tests/phase2/builtins/builtins/norm.tag new file mode 100644 index 000000000..ba2c22cb7 --- /dev/null +++ b/tests/phase2/builtins/builtins/norm.tag @@ -0,0 +1,2 @@ +[5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num.branch b/tests/phase2/builtins/builtins/num.branch new file mode 100644 index 000000000..55865497d --- /dev/null +++ b/tests/phase2/builtins/builtins/num.branch @@ -0,0 +1,28 @@ +["3", 3, "int"] +["3.0", 3.0, "float"] +["9007199254740993", 9007199254740993, "int"] +["9223372036854775808", 9.223372036854776e+18, "float"] +[" 12 ", 12, "int"] +["+5", 5, "int"] +["-0x10", -16, "int"] +["0x1F", 31, "int"] +["0X1f", 31, "int"] +["0xFFFFFFFFFFFFFFFF", -1, "int"] +[".5", 0.5, "float"] +["1.", 1.0, "float"] +["1e5", 100000.0, "float"] +["12abc", "value"] +["0b101", "value"] +["0o17", "value"] +["1_000", "value"] +["", "value"] +[" ", "value"] +["inf", "value"] +["nan", "value"] +["1.2.3", "value"] +["0x", "value"] +["0x1p4", "value"] +["-9223372036854775808", -9223372036854775808, "int"] +[7, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num.eigs b/tests/phase2/builtins/builtins/num.eigs new file mode 100644 index 000000000..c3c4fc8e3 --- /dev/null +++ b/tests/phase2/builtins/builtins/num.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: num (other, LOSS; N10 (#1758 comment 6304880619): exactly the lexer's numeric grammar, like Lua tonumber) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define p(s) as: + try: + local v is num of s + return [s, v, nt of v] + catch e: + return [s, e.kind] +for s in ["3", "3.0", "9007199254740993", "9223372036854775808", " 12 ", "+5", "-0x10", "0x1F", "0X1f", "0xFFFFFFFFFFFFFFFF", ".5", "1.", "1e5", "12abc", "0b101", "0o17", "1_000", "", " ", "inf", "nan", "1.2.3", "0x", "0x1p4", "-9223372036854775808"]: + print of (p of s) +n is num of 7 +print of [n, nt of n] +f is num of 2.5 +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/num.pre b/tests/phase2/builtins/builtins/num.pre new file mode 100644 index 000000000..e2c574548 --- /dev/null +++ b/tests/phase2/builtins/builtins/num.pre @@ -0,0 +1,28 @@ +["3", 3, "float"] +["3.0", 3, "float"] +["9007199254740993", 9007199254740992, "float"] +["9223372036854775808", 9.223372036854776e+18, "float"] +[" 12 ", 12, "float"] +["+5", 5, "float"] +["-0x10", -16, "float"] +["0x1F", 31, "float"] +["0X1f", 31, "float"] +["0xFFFFFFFFFFFFFFFF", 1.8446744073709552e+19, "float"] +[".5", 0.5, "float"] +["1.", 1, "float"] +["1e5", 100000, "float"] +["12abc", 12, "float"] +["0b101", 0, "float"] +["0o17", 0, "float"] +["1_000", 1, "float"] +["", 0, "float"] +[" ", 0, "float"] +["inf", 1e+308, "float"] +["nan", "value"] +["1.2.3", 1.2, "float"] +["0x", 0, "float"] +["0x1p4", 1, "float"] +["-9223372036854775808", -9.223372036854776e+18, "float"] +[7, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num.tag b/tests/phase2/builtins/builtins/num.tag new file mode 100644 index 000000000..f6ddb30b2 --- /dev/null +++ b/tests/phase2/builtins/builtins/num.tag @@ -0,0 +1,28 @@ +["3", 3, "undefined_name"] +["3.0", 3, "undefined_name"] +["9007199254740993", 9007199254740992, "undefined_name"] +["9223372036854775808", 9.223372036854776e+18, "undefined_name"] +[" 12 ", 12, "undefined_name"] +["+5", 5, "undefined_name"] +["-0x10", -16, "undefined_name"] +["0x1F", 31, "undefined_name"] +["0X1f", 31, "undefined_name"] +["0xFFFFFFFFFFFFFFFF", 1.8446744073709552e+19, "undefined_name"] +[".5", 0.5, "undefined_name"] +["1.", 1, "undefined_name"] +["1e5", 100000, "undefined_name"] +["12abc", 12, "undefined_name"] +["0b101", 0, "undefined_name"] +["0o17", 0, "undefined_name"] +["1_000", 1, "undefined_name"] +["", 0, "undefined_name"] +[" ", 0, "undefined_name"] +["inf", 1e+308, "undefined_name"] +["nan", "value"] +["1.2.3", 1.2, "undefined_name"] +["0x", 0, "undefined_name"] +["0x1p4", 1, "undefined_name"] +["-9223372036854775808", -9.223372036854776e+18, "undefined_name"] +[7, "undefined_name"] +[2.5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_copy.branch b/tests/phase2/builtins/builtins/num_copy.branch new file mode 100644 index 000000000..8e213e343 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_copy.branch @@ -0,0 +1,3 @@ +[9007199254740993, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_copy.eigs b/tests/phase2/builtins/builtins/num_copy.eigs new file mode 100644 index 000000000..8df44dabd --- /dev/null +++ b/tests/phase2/builtins/builtins/num_copy.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: num_copy (int-preserving, LOSS; copies a number and keeps its subtype) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is num_copy of 9007199254740993 +print of [a, nt of a] +b is num_copy of 2.5 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/num_copy.pre b/tests/phase2/builtins/builtins/num_copy.pre new file mode 100644 index 000000000..982dee2b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_copy.pre @@ -0,0 +1,3 @@ +[9007199254740992, "float"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_copy.tag b/tests/phase2/builtins/builtins/num_copy.tag new file mode 100644 index 000000000..63acdf9a7 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_copy.tag @@ -0,0 +1,3 @@ +[9007199254740992, "undefined_name"] +[2.5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_type.branch b/tests/phase2/builtins/builtins/num_type.branch new file mode 100644 index 000000000..b04dde087 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_type.branch @@ -0,0 +1,2 @@ +["int", "float", "int", null, null] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_type.eigs b/tests/phase2/builtins/builtins/num_type.eigs new file mode 100644 index 000000000..395270022 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_type.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: num_type (other; N7: int/float kind, null for a non-number) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of [num_type of 3, num_type of 3.0, num_type of 9007199254740993, num_type of "3", num_type of null] diff --git a/tests/phase2/builtins/builtins/num_type.pre b/tests/phase2/builtins/builtins/num_type.pre new file mode 100644 index 000000000..b04dde087 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_type.pre @@ -0,0 +1,2 @@ +["int", "float", "int", null, null] +rc=0 diff --git a/tests/phase2/builtins/builtins/num_type.tag b/tests/phase2/builtins/builtins/num_type.tag new file mode 100644 index 000000000..321433716 --- /dev/null +++ b/tests/phase2/builtins/builtins/num_type.tag @@ -0,0 +1 @@ +rc=1 diff --git a/tests/phase2/builtins/builtins/numerical_grad.branch b/tests/phase2/builtins/builtins/numerical_grad.branch new file mode 100644 index 000000000..0daf753da --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad.branch @@ -0,0 +1,2 @@ +[[[0.0, 0.0], [6.0, 0.0]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad.eigs b/tests/phase2/builtins/builtins/numerical_grad.eigs new file mode 100644 index 000000000..314331318 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: numerical_grad (float-producing; finite differences) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +q is [[1.0, 2.0], [3.0, 4.0]] +define loss(u) as: + return q[1][0] * q[1][0] +g is numerical_grad of [loss, q, 0.5] +print of [g, nt of g[1][0]] diff --git a/tests/phase2/builtins/builtins/numerical_grad.pre b/tests/phase2/builtins/builtins/numerical_grad.pre new file mode 100644 index 000000000..0980a1401 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad.pre @@ -0,0 +1,2 @@ +[[[0, 0], [6, 0]], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad.tag b/tests/phase2/builtins/builtins/numerical_grad.tag new file mode 100644 index 000000000..bcc0b1e1c --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad.tag @@ -0,0 +1,2 @@ +[[[0, 0], [6, 0]], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_cols.branch b/tests/phase2/builtins/builtins/numerical_grad_cols.branch new file mode 100644 index 000000000..212857f10 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_cols.branch @@ -0,0 +1,4 @@ +[[[0.0, 0.0], [6.0, 0.0]], "float"] +["col-fraction", "value"] +[[0.0, 0.0], [6.0, 0.0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_cols.eigs b/tests/phase2/builtins/builtins/numerical_grad_cols.eigs new file mode 100644 index 000000000..d6377b181 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_cols.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: numerical_grad_cols (float-producing; finite differences; column indices int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +q is [[1.0, 2.0], [3.0, 4.0]] +define loss(u) as: + return q[1][0] * q[1][0] +g is numerical_grad_cols of [loss, q, [0, 1], 0.5] +print of [g, nt of g[1][0]] +try: + print of ["fraction-answer", numerical_grad_cols of [loss, q, [0.5, 1], 0.5]] +catch e: + print of ["col-fraction", e.kind] +print of (numerical_grad_cols of [loss, q, [0.0, 1.0], 0.5]) diff --git a/tests/phase2/builtins/builtins/numerical_grad_cols.pre b/tests/phase2/builtins/builtins/numerical_grad_cols.pre new file mode 100644 index 000000000..b55026079 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_cols.pre @@ -0,0 +1,4 @@ +[[[0, 0], [6, 0]], "float"] +["fraction-answer", [[0, 0], [6, 0]]] +[[0, 0], [6, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_cols.tag b/tests/phase2/builtins/builtins/numerical_grad_cols.tag new file mode 100644 index 000000000..95d07d57c --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_cols.tag @@ -0,0 +1,4 @@ +[[[0, 0], [6, 0]], "undefined_name"] +["fraction-answer", [[0, 0], [6, 0]]] +[[0, 0], [6, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_rows.branch b/tests/phase2/builtins/builtins/numerical_grad_rows.branch new file mode 100644 index 000000000..45c62a37e --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_rows.branch @@ -0,0 +1,4 @@ +[[[0.0, 0.0], [6.0, 0.0]], "float"] +["row-fraction", "value"] +[[0.0, 0.0], [6.0, 0.0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_rows.eigs b/tests/phase2/builtins/builtins/numerical_grad_rows.eigs new file mode 100644 index 000000000..6a87f2030 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_rows.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: numerical_grad_rows (float-producing; finite differences; row indices int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +q is [[1.0, 2.0], [3.0, 4.0]] +define loss(u) as: + return q[1][0] * q[1][0] +g is numerical_grad_rows of [loss, q, [1, 0], 0.5] +print of [g, nt of g[1][0]] +try: + print of ["fraction-answer", numerical_grad_rows of [loss, q, [0.5, 1], 0.5]] +catch e: + print of ["row-fraction", e.kind] +print of (numerical_grad_rows of [loss, q, [1.0, 0.0], 0.5]) diff --git a/tests/phase2/builtins/builtins/numerical_grad_rows.pre b/tests/phase2/builtins/builtins/numerical_grad_rows.pre new file mode 100644 index 000000000..b55026079 --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_rows.pre @@ -0,0 +1,4 @@ +[[[0, 0], [6, 0]], "float"] +["fraction-answer", [[0, 0], [6, 0]]] +[[0, 0], [6, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/numerical_grad_rows.tag b/tests/phase2/builtins/builtins/numerical_grad_rows.tag new file mode 100644 index 000000000..95d07d57c --- /dev/null +++ b/tests/phase2/builtins/builtins/numerical_grad_rows.tag @@ -0,0 +1,4 @@ +[[[0, 0], [6, 0]], "undefined_name"] +["fraction-answer", [[0, 0], [6, 0]]] +[[0, 0], [6, 0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/observe.branch b/tests/phase2/builtins/builtins/observe.branch new file mode 100644 index 000000000..6c96c90ee --- /dev/null +++ b/tests/phase2/builtins/builtins/observe.branch @@ -0,0 +1,2 @@ +[["equilibrium", 0.0, 0.0, 0.0], "float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/observe.eigs b/tests/phase2/builtins/builtins/observe.eigs new file mode 100644 index 000000000..2e4041bc3 --- /dev/null +++ b/tests/phase2/builtins/builtins/observe.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: observe (other; observer readouts (O4)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +x is 5 +r is observe of (x + 0) +print of [r, nt of r[1], nt of r[2], nt of r[3]] diff --git a/tests/phase2/builtins/builtins/observe.pre b/tests/phase2/builtins/builtins/observe.pre new file mode 100644 index 000000000..830736f5c --- /dev/null +++ b/tests/phase2/builtins/builtins/observe.pre @@ -0,0 +1,2 @@ +[["equilibrium", 0, 0, 0], "float", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/observe.tag b/tests/phase2/builtins/builtins/observe.tag new file mode 100644 index 000000000..294e767d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/observe.tag @@ -0,0 +1,2 @@ +[["equilibrium", 0, 0, 0], "undefined_name", "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ord.branch b/tests/phase2/builtins/builtins/ord.branch new file mode 100644 index 000000000..3386f9785 --- /dev/null +++ b/tests/phase2/builtins/builtins/ord.branch @@ -0,0 +1,3 @@ +[65, "int"] +[-1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ord.eigs b/tests/phase2/builtins/builtins/ord.eigs new file mode 100644 index 000000000..ef6600ea3 --- /dev/null +++ b/tests/phase2/builtins/builtins/ord.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: ord (int-preserving; byte code (Lua string.byte)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is ord of "A" +print of [a, nt of a] +b is ord of "" +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/ord.pre b/tests/phase2/builtins/builtins/ord.pre new file mode 100644 index 000000000..eb4147717 --- /dev/null +++ b/tests/phase2/builtins/builtins/ord.pre @@ -0,0 +1,3 @@ +[65, "float"] +[-1, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ord.tag b/tests/phase2/builtins/builtins/ord.tag new file mode 100644 index 000000000..c18b62428 --- /dev/null +++ b/tests/phase2/builtins/builtins/ord.tag @@ -0,0 +1,3 @@ +[65, "undefined_name"] +[-1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pi.branch b/tests/phase2/builtins/builtins/pi.branch new file mode 100644 index 000000000..ff54e3088 --- /dev/null +++ b/tests/phase2/builtins/builtins/pi.branch @@ -0,0 +1,2 @@ +[3.141592653589793, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pi.eigs b/tests/phase2/builtins/builtins/pi.eigs new file mode 100644 index 000000000..24f5ef4e2 --- /dev/null +++ b/tests/phase2/builtins/builtins/pi.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: pi (float-producing; Lua math.pi) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is pi of null +print of [p, nt of p] diff --git a/tests/phase2/builtins/builtins/pi.pre b/tests/phase2/builtins/builtins/pi.pre new file mode 100644 index 000000000..ff54e3088 --- /dev/null +++ b/tests/phase2/builtins/builtins/pi.pre @@ -0,0 +1,2 @@ +[3.141592653589793, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pi.tag b/tests/phase2/builtins/builtins/pi.tag new file mode 100644 index 000000000..ca39196c0 --- /dev/null +++ b/tests/phase2/builtins/builtins/pi.tag @@ -0,0 +1,2 @@ +[3.141592653589793, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pow.branch b/tests/phase2/builtins/builtins/pow.branch new file mode 100644 index 000000000..7d2176202 --- /dev/null +++ b/tests/phase2/builtins/builtins/pow.branch @@ -0,0 +1,3 @@ +[1024.0, "float"] +[1.4142135623730951, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pow.eigs b/tests/phase2/builtins/builtins/pow.eigs new file mode 100644 index 000000000..e568e7263 --- /dev/null +++ b/tests/phase2/builtins/builtins/pow.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: pow (float-producing; mirrors ^ (Lua 3.4.1: exponentiation is always float)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is pow of [2, 10] +print of [a, nt of a] +b is pow of [2, 0.5] +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/pow.pre b/tests/phase2/builtins/builtins/pow.pre new file mode 100644 index 000000000..1d8fc62ec --- /dev/null +++ b/tests/phase2/builtins/builtins/pow.pre @@ -0,0 +1,3 @@ +[1024, "float"] +[1.4142135623730951, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/pow.tag b/tests/phase2/builtins/builtins/pow.tag new file mode 100644 index 000000000..8917f718b --- /dev/null +++ b/tests/phase2/builtins/builtins/pow.tag @@ -0,0 +1,3 @@ +[1024, "undefined_name"] +[1.4142135623730951, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ppu_render_frame.branch b/tests/phase2/builtins/builtins/ppu_render_frame.branch new file mode 100644 index 000000000..e2885adaa --- /dev/null +++ b/tests/phase2/builtins/builtins/ppu_render_frame.branch @@ -0,0 +1,5 @@ +["lcd-off", 0.0] +["lcdc-fraction", "value"] +["lcdc-401", "value"] +["bg-on", 3.0, 3.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ppu_render_frame.eigs b/tests/phase2/builtins/builtins/ppu_render_frame.eigs new file mode 100644 index 000000000..53453ac40 --- /dev/null +++ b/tests/phase2/builtins/builtins/ppu_render_frame.eigs @@ -0,0 +1,27 @@ +# R1.6 behaviour-table probe: ppu_render_frame (int-requiring; the memory buffer's bytes by N5, then 0..255 (Q1)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +mem is buffer of 65536 +fb is buffer of 23040 +fb[0] is 7 +ppu_render_frame of [mem, fb] +print of ["lcd-off", fb[0]] +mem[65351] is 255 +mem[65344] is 145.5 +try: + ppu_render_frame of [mem, fb] + print of ["fraction accepted", fb[0]] +catch e: + print of ["lcdc-fraction", e.kind] +mem[65344] is 401 +try: + ppu_render_frame of [mem, fb] + print of ["401 accepted", fb[0]] +catch e: + print of ["lcdc-401", e.kind] +mem[65344] is 145.0 +ppu_render_frame of [mem, fb] +print of ["bg-on", fb[0], fb[23039], nt of fb[0]] diff --git a/tests/phase2/builtins/builtins/ppu_render_frame.pre b/tests/phase2/builtins/builtins/ppu_render_frame.pre new file mode 100644 index 000000000..346be0d53 --- /dev/null +++ b/tests/phase2/builtins/builtins/ppu_render_frame.pre @@ -0,0 +1,5 @@ +["lcd-off", 0] +["fraction accepted", 3] +["401 accepted", 3] +["bg-on", 3, 3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/ppu_render_frame.tag b/tests/phase2/builtins/builtins/ppu_render_frame.tag new file mode 100644 index 000000000..ad930eb55 --- /dev/null +++ b/tests/phase2/builtins/builtins/ppu_render_frame.tag @@ -0,0 +1,5 @@ +["lcd-off", 0] +["fraction accepted", 3] +["401 accepted", 3] +["bg-on", 3, 3, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/print.branch b/tests/phase2/builtins/builtins/print.branch new file mode 100644 index 000000000..163bce63c --- /dev/null +++ b/tests/phase2/builtins/builtins/print.branch @@ -0,0 +1,5 @@ +9007199254740993 +2.5 +3.0 +[9007199254740993, 1e+300] +rc=0 diff --git a/tests/phase2/builtins/builtins/print.eigs b/tests/phase2/builtins/builtins/print.eigs new file mode 100644 index 000000000..385bbdfe8 --- /dev/null +++ b/tests/phase2/builtins/builtins/print.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: print (other; N6 printing via value_to_string) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of 9007199254740993 +print of 2.5 +print of 3.0 +print of [9007199254740993, 1e300] diff --git a/tests/phase2/builtins/builtins/print.pre b/tests/phase2/builtins/builtins/print.pre new file mode 100644 index 000000000..c6eff81fa --- /dev/null +++ b/tests/phase2/builtins/builtins/print.pre @@ -0,0 +1,5 @@ +9007199254740993 +2.5 +3 +[9007199254740993, 1e+300] +rc=0 diff --git a/tests/phase2/builtins/builtins/print.tag b/tests/phase2/builtins/builtins/print.tag new file mode 100644 index 000000000..456b3ad3a --- /dev/null +++ b/tests/phase2/builtins/builtins/print.tag @@ -0,0 +1,5 @@ +9007199254740992 +2.5 +3 +[9007199254740992, 1e+300] +rc=0 diff --git a/tests/phase2/builtins/builtins/random.branch b/tests/phase2/builtins/builtins/random.branch new file mode 100644 index 000000000..a188fded6 --- /dev/null +++ b/tests/phase2/builtins/builtins/random.branch @@ -0,0 +1,2 @@ +["float", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/random.eigs b/tests/phase2/builtins/builtins/random.eigs new file mode 100644 index 000000000..30ed2ba33 --- /dev/null +++ b/tests/phase2/builtins/builtins/random.eigs @@ -0,0 +1,9 @@ +# R1.6 behaviour-table probe: random (float-producing; Lua math.random() in [0,1)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +seed_random of 7 +r is random of null +print of [nt of r, r >= 0, r < 1] diff --git a/tests/phase2/builtins/builtins/random.pre b/tests/phase2/builtins/builtins/random.pre new file mode 100644 index 000000000..a188fded6 --- /dev/null +++ b/tests/phase2/builtins/builtins/random.pre @@ -0,0 +1,2 @@ +["float", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/random.tag b/tests/phase2/builtins/builtins/random.tag new file mode 100644 index 000000000..4bd2350b5 --- /dev/null +++ b/tests/phase2/builtins/builtins/random.tag @@ -0,0 +1,2 @@ +["undefined_name", true, true] +rc=0 diff --git a/tests/phase2/builtins/builtins/random_hex.branch b/tests/phase2/builtins/builtins/random_hex.branch new file mode 100644 index 000000000..8e80ac10d --- /dev/null +++ b/tests/phase2/builtins/builtins/random_hex.branch @@ -0,0 +1,4 @@ +4 +["count-fraction", "value"] +6 +rc=0 diff --git a/tests/phase2/builtins/builtins/random_hex.eigs b/tests/phase2/builtins/builtins/random_hex.eigs new file mode 100644 index 000000000..86861278a --- /dev/null +++ b/tests/phase2/builtins/builtins/random_hex.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: random_hex (int-requiring; digit count); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (len of (random_hex of 4)) +try: + print of ["fraction-answer", len of (random_hex of 4.5)] +catch e: + print of ["count-fraction", e.kind] +print of (len of (random_hex of 6.0)) diff --git a/tests/phase2/builtins/builtins/random_hex.pre b/tests/phase2/builtins/builtins/random_hex.pre new file mode 100644 index 000000000..afda150dd --- /dev/null +++ b/tests/phase2/builtins/builtins/random_hex.pre @@ -0,0 +1,4 @@ +4 +["fraction-answer", 4] +6 +rc=0 diff --git a/tests/phase2/builtins/builtins/random_hex.tag b/tests/phase2/builtins/builtins/random_hex.tag new file mode 100644 index 000000000..afda150dd --- /dev/null +++ b/tests/phase2/builtins/builtins/random_hex.tag @@ -0,0 +1,4 @@ +4 +["fraction-answer", 4] +6 +rc=0 diff --git a/tests/phase2/builtins/builtins/random_int.branch b/tests/phase2/builtins/builtins/random_int.branch new file mode 100644 index 000000000..20c7c7468 --- /dev/null +++ b/tests/phase2/builtins/builtins/random_int.branch @@ -0,0 +1,5 @@ +[5, "int"] +[9007199254740993, "int"] +["fraction", "value"] +[2, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/random_int.eigs b/tests/phase2/builtins/builtins/random_int.eigs new file mode 100644 index 000000000..ada0cab84 --- /dev/null +++ b/tests/phase2/builtins/builtins/random_int.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: random_int (int-requiring, LOSS; Lua math.random(m, n) is an integer); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +seed_random of 7 +a is random_int of [5, 5] +print of [a, nt of a] +big is random_int of [9007199254740993, 9007199254740993] +print of [big, nt of big] +try: + print of (random_int of [1.5, 3]) +catch e: + print of ["fraction", e.kind] +f is random_int of [2.0, 2.0] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/random_int.pre b/tests/phase2/builtins/builtins/random_int.pre new file mode 100644 index 000000000..f6d9f0765 --- /dev/null +++ b/tests/phase2/builtins/builtins/random_int.pre @@ -0,0 +1,5 @@ +[5, "float"] +[9007199254740992, "float"] +2 +[2, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/random_int.tag b/tests/phase2/builtins/builtins/random_int.tag new file mode 100644 index 000000000..b58593dcb --- /dev/null +++ b/tests/phase2/builtins/builtins/random_int.tag @@ -0,0 +1,5 @@ +[5, "undefined_name"] +[9007199254740992, "undefined_name"] +2 +[2, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/random_normal.branch b/tests/phase2/builtins/builtins/random_normal.branch new file mode 100644 index 000000000..f7baf04e9 --- /dev/null +++ b/tests/phase2/builtins/builtins/random_normal.branch @@ -0,0 +1,6 @@ +[2, 3, "float"] +["rows-fraction", "value"] +[4, "float"] +["len-fraction", "value"] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/random_normal.eigs b/tests/phase2/builtins/builtins/random_normal.eigs new file mode 100644 index 000000000..3aa66f4fd --- /dev/null +++ b/tests/phase2/builtins/builtins/random_normal.eigs @@ -0,0 +1,20 @@ +# R1.6 behaviour-table probe: random_normal (float-producing; dims int; samples float) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +seed_random of 7 +r is random_normal of [2, 3, 1.0] +print of [len of r, len of r[0], nt of r[0][0]] +try: + print of ["fraction-answer", len of (random_normal of [2.5, 3, 1.0])] +catch e: + print of ["rows-fraction", e.kind] +v is random_normal of [4, 1.0] +print of [len of v, nt of v[0]] +try: + print of ["fraction-answer", len of (random_normal of [4.5, 1.0])] +catch e: + print of ["len-fraction", e.kind] +print of (len of (random_normal of [3.0, 1.0])) diff --git a/tests/phase2/builtins/builtins/random_normal.pre b/tests/phase2/builtins/builtins/random_normal.pre new file mode 100644 index 000000000..7cf474c0a --- /dev/null +++ b/tests/phase2/builtins/builtins/random_normal.pre @@ -0,0 +1,6 @@ +[2, 3, "float"] +["fraction-answer", 2] +[4, "float"] +["fraction-answer", 4] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/random_normal.tag b/tests/phase2/builtins/builtins/random_normal.tag new file mode 100644 index 000000000..2ac5c2100 --- /dev/null +++ b/tests/phase2/builtins/builtins/random_normal.tag @@ -0,0 +1,6 @@ +[2, 3, "undefined_name"] +["fraction-answer", 2] +[4, "undefined_name"] +["fraction-answer", 4] +3 +rc=0 diff --git a/tests/phase2/builtins/builtins/range.branch b/tests/phase2/builtins/builtins/range.branch new file mode 100644 index 000000000..f493806ab --- /dev/null +++ b/tests/phase2/builtins/builtins/range.branch @@ -0,0 +1,4 @@ +[[0, 1, 2], "int"] +[[1, 2], "int"] +["fraction", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/range.eigs b/tests/phase2/builtins/builtins/range.eigs new file mode 100644 index 000000000..0c69d1bc6 --- /dev/null +++ b/tests/phase2/builtins/builtins/range.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: range (int-preserving; bounds by N5; the elements are ints) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is range of 3 +print of [r, nt of r[0]] +r2 is range of [1.0, 3.0] +print of [r2, nt of r2[0]] +try: + print of (range of 2.5) +catch e: + print of ["fraction", e.kind] diff --git a/tests/phase2/builtins/builtins/range.pre b/tests/phase2/builtins/builtins/range.pre new file mode 100644 index 000000000..8802b22b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/range.pre @@ -0,0 +1,4 @@ +[[0, 1, 2], "float"] +[[1, 2], "float"] +[0, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/range.tag b/tests/phase2/builtins/builtins/range.tag new file mode 100644 index 000000000..dcf369013 --- /dev/null +++ b/tests/phase2/builtins/builtins/range.tag @@ -0,0 +1,4 @@ +[[0, 1, 2], "undefined_name"] +[[1, 2], "undefined_name"] +[0, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes.branch b/tests/phase2/builtins/builtins/read_bytes.branch new file mode 100644 index 000000000..33829c187 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes.branch @@ -0,0 +1,2 @@ +[[0, 65, 255], "int", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes.eigs b/tests/phase2/builtins/builtins/read_bytes.eigs new file mode 100644 index 000000000..858a53024 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: read_bytes (int-preserving; byte values 0..255) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/b.bin" +write_bytes of [f, [0, 65, 255]] +r is read_bytes of f +print of [r, nt of r[0], nt of r[2]] diff --git a/tests/phase2/builtins/builtins/read_bytes.pre b/tests/phase2/builtins/builtins/read_bytes.pre new file mode 100644 index 000000000..13cf1003d --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes.pre @@ -0,0 +1,2 @@ +[[0, 65, 255], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes.tag b/tests/phase2/builtins/builtins/read_bytes.tag new file mode 100644 index 000000000..c36169ad3 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes.tag @@ -0,0 +1,2 @@ +[[0, 65, 255], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes_buf.branch b/tests/phase2/builtins/builtins/read_bytes_buf.branch new file mode 100644 index 000000000..e3f437123 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes_buf.branch @@ -0,0 +1,4 @@ +[3, 1.0, "float"] +["cap-fraction", "value"] +[3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes_buf.eigs b/tests/phase2/builtins/builtins/read_bytes_buf.eigs new file mode 100644 index 000000000..d8fa0a726 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes_buf.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: read_bytes_buf (int-requiring; cap and length; bytes stored as floats); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/b.bin" +write_bytes of [f, [1, 2, 3]] +b is read_bytes_buf of f +print of [buf_len of b, b[0], nt of b[0]] +try: + c is read_bytes_buf of [f, 8.5] + print of ["fraction accepted", buf_len of c] +catch e: + print of ["cap-fraction", e.kind] +d is read_bytes_buf of [f, 8.0] +print of [buf_len of d, nt of d[2]] diff --git a/tests/phase2/builtins/builtins/read_bytes_buf.pre b/tests/phase2/builtins/builtins/read_bytes_buf.pre new file mode 100644 index 000000000..a72d8c617 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes_buf.pre @@ -0,0 +1,4 @@ +[3, 1, "float"] +["fraction accepted", 3] +[3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_bytes_buf.tag b/tests/phase2/builtins/builtins/read_bytes_buf.tag new file mode 100644 index 000000000..92834e462 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_bytes_buf.tag @@ -0,0 +1,4 @@ +[3, 1, "undefined_name"] +["fraction accepted", 3] +[3, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_text.branch b/tests/phase2/builtins/builtins/read_text.branch new file mode 100644 index 000000000..604cf416f --- /dev/null +++ b/tests/phase2/builtins/builtins/read_text.branch @@ -0,0 +1,6 @@ +["missing", "io"] +["written", 10485761] +["over-cap", "io", true] +["over-cap-tail", " is 10485761 bytes, over the 10485760-byte cap — pass read_text of [path, max_bytes] (hard ceiling 536870912) to raise it"] +["hi", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_text.eigs b/tests/phase2/builtins/builtins/read_text.eigs new file mode 100644 index 000000000..513dc495e --- /dev/null +++ b/tests/phase2/builtins/builtins/read_text.eigs @@ -0,0 +1,30 @@ +# R1.6 behaviour-table probe: read_text (other; string result; numeric over-cap size is internal tape metadata (#1668), with no numeric argument or successful numeric result; ruling R1.6a-D1; over-cap REPLAY, #1890, is not exercised) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +try: + print of ["missing-answer", read_text of (p + "/missing.txt")] +catch e: + print of ["missing", e.kind] +s is "x" +i is 0 +loop while i < 20: + s is s + s + i is i + 1 +big is s + s + s + s + s + s + s + s + s + s + "x" +f is p + "/big.txt" +write_text of [f, big] +print of ["written", len of big] +try: + print of ["over-cap-answer", len of (read_text of f)] +catch e: + prefix_text is "read_text: '" + f + "'" + print of ["over-cap", e.kind, starts_with of [e.message, prefix_text]] + print of ["over-cap-tail", substr of [e.message, len of prefix_text, 1000]] +w is p + "/small.txt" +write_text of [w, "hi"] +t is read_text of w +print of [t, type of t] diff --git a/tests/phase2/builtins/builtins/read_text.pre b/tests/phase2/builtins/builtins/read_text.pre new file mode 100644 index 000000000..48264b006 --- /dev/null +++ b/tests/phase2/builtins/builtins/read_text.pre @@ -0,0 +1,6 @@ +["missing", "io"] +["written", 10485761] +["over-cap", "io", true] +["over-cap-tail", " is 10485761 bytes, over the 10485760-byte cap"] +["hi", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/read_text.tag b/tests/phase2/builtins/builtins/read_text.tag new file mode 100644 index 000000000..8fd361c5d --- /dev/null +++ b/tests/phase2/builtins/builtins/read_text.tag @@ -0,0 +1,5 @@ +["missing-answer", ""] +["written", 10485761] +["over-cap-answer", 0] +["hi", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/record_history.branch b/tests/phase2/builtins/builtins/record_history.branch new file mode 100644 index 000000000..122550c48 --- /dev/null +++ b/tests/phase2/builtins/builtins/record_history.branch @@ -0,0 +1,3 @@ +[0, "int", 1, "int"] +[0, "int", 1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/record_history.eigs b/tests/phase2/builtins/builtins/record_history.eigs new file mode 100644 index 000000000..105e8d55c --- /dev/null +++ b/tests/phase2/builtins/builtins/record_history.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: record_history (other; truthiness flag in, previous flag out) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is record_history of 1 +q is record_history of 0 +print of [p, nt of p, q, nt of q] +r is record_history of 0.5 +s is record_history of 0 +print of [r, nt of r, s, nt of s] diff --git a/tests/phase2/builtins/builtins/record_history.pre b/tests/phase2/builtins/builtins/record_history.pre new file mode 100644 index 000000000..dd137b3f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/record_history.pre @@ -0,0 +1,3 @@ +[0, "float", 1, "float"] +[0, "float", 1, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/record_history.tag b/tests/phase2/builtins/builtins/record_history.tag new file mode 100644 index 000000000..f2c845b67 --- /dev/null +++ b/tests/phase2/builtins/builtins/record_history.tag @@ -0,0 +1,3 @@ +[0, "undefined_name", 1, "undefined_name"] +[0, "undefined_name", 1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/recv.branch b/tests/phase2/builtins/builtins/recv.branch new file mode 100644 index 000000000..7b90f2c48 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[5, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/recv.eigs b/tests/phase2/builtins/builtins/recv.eigs new file mode 100644 index 000000000..6b7842102 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: recv (int-requiring; channel handle ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +send of [c, 5] +send of [c, 6] +try: + print of ["fraction-answer", recv of {"_channel_id": id + 0.5, "_channel_gen": gen}] +catch e: + print of ["id-fraction", e.kind] +v is recv of {"_channel_id": id + 0.0, "_channel_gen": gen + 0.0} +print of [v, nt of v] diff --git a/tests/phase2/builtins/builtins/recv.pre b/tests/phase2/builtins/builtins/recv.pre new file mode 100644 index 000000000..8a632e7f5 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv.pre @@ -0,0 +1,3 @@ +["fraction-answer", 5] +[6, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/recv.tag b/tests/phase2/builtins/builtins/recv.tag new file mode 100644 index 000000000..961c8a6c5 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv.tag @@ -0,0 +1,3 @@ +["fraction-answer", 5] +[6, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/recv_timeout.branch b/tests/phase2/builtins/builtins/recv_timeout.branch new file mode 100644 index 000000000..2a54f01ab --- /dev/null +++ b/tests/phase2/builtins/builtins/recv_timeout.branch @@ -0,0 +1,6 @@ +["id-fraction", "value"] +["timeout-magnitude", "value"] +["timeout-past-int64", "value"] +[5, "int"] +6 +rc=0 diff --git a/tests/phase2/builtins/builtins/recv_timeout.eigs b/tests/phase2/builtins/builtins/recv_timeout.eigs new file mode 100644 index 000000000..486d75d63 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv_timeout.eigs @@ -0,0 +1,27 @@ +# R1.6 behaviour-table probe: recv_timeout (int-requiring, Q7 magnitude; handle by N5, a timeout the C long cannot hold raises); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"q7":["magnitude"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +send of [c, 5] +send of [c, 6] +send of [c, 7] +try: + print of ["fraction-answer", recv_timeout of [{"_channel_id": id + 0.5, "_channel_gen": gen}, 10]] +catch e: + print of ["id-fraction", e.kind] +try: + print of ["huge-timeout-answer", recv_timeout of [c, 1e300]] +catch e: + print of ["timeout-magnitude", e.kind] +try: + print of ["past-limit-answer", recv_timeout of [c, 9223372036854775808]] +catch e: + print of ["timeout-past-int64", e.kind] +v is recv_timeout of [{"_channel_id": id + 0.0, "_channel_gen": gen + 0.0}, 10] +print of [v, nt of v] +print of (recv_timeout of [c, 0.5]) diff --git a/tests/phase2/builtins/builtins/recv_timeout.pre b/tests/phase2/builtins/builtins/recv_timeout.pre new file mode 100644 index 000000000..0cbe49acb --- /dev/null +++ b/tests/phase2/builtins/builtins/recv_timeout.pre @@ -0,0 +1,6 @@ +["fraction-answer", 5] +["huge-timeout-answer", 6] +["past-limit-answer", 7] +[null, null] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/recv_timeout.tag b/tests/phase2/builtins/builtins/recv_timeout.tag new file mode 100644 index 000000000..70d35d062 --- /dev/null +++ b/tests/phase2/builtins/builtins/recv_timeout.tag @@ -0,0 +1,6 @@ +["fraction-answer", 5] +["huge-timeout-answer", 6] +["past-limit-answer", 7] +[null, "undefined_name"] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/relu.branch b/tests/phase2/builtins/builtins/relu.branch new file mode 100644 index 000000000..ac07adeee --- /dev/null +++ b/tests/phase2/builtins/builtins/relu.branch @@ -0,0 +1,3 @@ +[[0.0, 2.0], "float", "float"] +[3.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/relu.eigs b/tests/phase2/builtins/builtins/relu.eigs new file mode 100644 index 000000000..03d21f81f --- /dev/null +++ b/tests/phase2/builtins/builtins/relu.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: relu (float-producing; ML activation (float tensors; no Lua counterpart)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is relu of [-1, 2] +print of [r, nt of r[0], nt of r[1]] +s is relu of 3 +print of [s, nt of s] diff --git a/tests/phase2/builtins/builtins/relu.pre b/tests/phase2/builtins/builtins/relu.pre new file mode 100644 index 000000000..045a22507 --- /dev/null +++ b/tests/phase2/builtins/builtins/relu.pre @@ -0,0 +1,3 @@ +[[0, 2], "float", "float"] +[3, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/relu.tag b/tests/phase2/builtins/builtins/relu.tag new file mode 100644 index 000000000..9eb345eba --- /dev/null +++ b/tests/phase2/builtins/builtins/relu.tag @@ -0,0 +1,3 @@ +[[0, 2], "undefined_name", "undefined_name"] +[3, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/reshape.branch b/tests/phase2/builtins/builtins/reshape.branch new file mode 100644 index 000000000..72e603df9 --- /dev/null +++ b/tests/phase2/builtins/builtins/reshape.branch @@ -0,0 +1,4 @@ +[2, 2] +["rows-fraction", "value"] +[1, 4] +rc=0 diff --git a/tests/phase2/builtins/builtins/reshape.eigs b/tests/phase2/builtins/builtins/reshape.eigs new file mode 100644 index 000000000..2205188da --- /dev/null +++ b/tests/phase2/builtins/builtins/reshape.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: reshape (int-requiring; rows and cols); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +b is buf_from_list of [1, 2, 3, 4] +print of (shape of (reshape of [b, 2, 2])) +try: + q is reshape of [b, 2.5, 2] + print of ["fraction accepted", shape of q] +catch e: + print of ["rows-fraction", e.kind] +print of (shape of (reshape of [b, 1.0, 4.0])) diff --git a/tests/phase2/builtins/builtins/reshape.pre b/tests/phase2/builtins/builtins/reshape.pre new file mode 100644 index 000000000..bc4d64524 --- /dev/null +++ b/tests/phase2/builtins/builtins/reshape.pre @@ -0,0 +1,4 @@ +[2, 2] +["fraction accepted", [2, 2]] +[1, 4] +rc=0 diff --git a/tests/phase2/builtins/builtins/reshape.tag b/tests/phase2/builtins/builtins/reshape.tag new file mode 100644 index 000000000..bc4d64524 --- /dev/null +++ b/tests/phase2/builtins/builtins/reshape.tag @@ -0,0 +1,4 @@ +[2, 2] +["fraction accepted", [2, 2]] +[1, 4] +rc=0 diff --git a/tests/phase2/builtins/builtins/round.branch b/tests/phase2/builtins/builtins/round.branch new file mode 100644 index 000000000..39b90afe4 --- /dev/null +++ b/tests/phase2/builtins/builtins/round.branch @@ -0,0 +1,5 @@ +[9007199254740993, "int"] +[-3, "int"] +[3, "int"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/round.eigs b/tests/phase2/builtins/builtins/round.eigs new file mode 100644 index 000000000..1bc14faf0 --- /dev/null +++ b/tests/phase2/builtins/builtins/round.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: round (int-preserving, LOSS; Q4: half away from zero, an int when it fits int64) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is round of 9007199254740993 +print of [a, nt of a] +b is round of -2.5 +print of [b, nt of b] +c is round of 2.5 +print of [c, nt of c] +d is round of 1e300 +print of [d, nt of d] diff --git a/tests/phase2/builtins/builtins/round.pre b/tests/phase2/builtins/builtins/round.pre new file mode 100644 index 000000000..1f0e2752a --- /dev/null +++ b/tests/phase2/builtins/builtins/round.pre @@ -0,0 +1,5 @@ +[9007199254740992, "float"] +[-3, "float"] +[3, "float"] +[1e+300, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/round.tag b/tests/phase2/builtins/builtins/round.tag new file mode 100644 index 000000000..1b0d9b453 --- /dev/null +++ b/tests/phase2/builtins/builtins/round.tag @@ -0,0 +1,5 @@ +[9007199254740992, "undefined_name"] +[-3, "undefined_name"] +[3, "undefined_name"] +[1e+300, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sandbox_run.branch b/tests/phase2/builtins/builtins/sandbox_run.branch new file mode 100644 index 000000000..eab92482e --- /dev/null +++ b/tests/phase2/builtins/builtins/sandbox_run.branch @@ -0,0 +1,5 @@ +{"ok": true, "result": 7} +["iterations-fraction", "value"] +{"ok": true, "result": 7} +[false, "type_mismatch", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sandbox_run.eigs b/tests/phase2/builtins/builtins/sandbox_run.eigs new file mode 100644 index 000000000..1dd2a7118 --- /dev/null +++ b/tests/phase2/builtins/builtins/sandbox_run.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: sandbox_run (int-requiring; iteration, byte and work limits; returns line ints); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is [1, [0, 0, 0, 40], [7]] +print of (sandbox_run of [d, 1000]) +try: + print of ["fraction-answer", sandbox_run of [d, 1000.5]] +catch e: + print of ["iterations-fraction", e.kind] +print of (sandbox_run of [d, 1000.0]) +bad is sandbox_run of [[1, [0, 0, 0, 0, 1, 0, 4, 40], ["a", 1]], 1000] +print of [bad["ok"], bad["error"]["kind"], nt of bad["error"]["line"]] diff --git a/tests/phase2/builtins/builtins/sandbox_run.pre b/tests/phase2/builtins/builtins/sandbox_run.pre new file mode 100644 index 000000000..7403a8bbe --- /dev/null +++ b/tests/phase2/builtins/builtins/sandbox_run.pre @@ -0,0 +1,5 @@ +{"ok": true, "result": 7} +["fraction-answer", {"ok": true, "result": 7}] +{"ok": true, "result": 7} +[false, "type_mismatch", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sandbox_run.tag b/tests/phase2/builtins/builtins/sandbox_run.tag new file mode 100644 index 000000000..abee157fc --- /dev/null +++ b/tests/phase2/builtins/builtins/sandbox_run.tag @@ -0,0 +1,5 @@ +{"ok": true, "result": 7} +["fraction-answer", {"ok": true, "result": 7}] +{"ok": true, "result": 7} +[false, "type_mismatch", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_int_tokens.branch b/tests/phase2/builtins/builtins/scan_int_tokens.branch new file mode 100644 index 000000000..de3577355 --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_int_tokens.branch @@ -0,0 +1,4 @@ +[["7", 1, 0, 0, 1, 1, 7], ["9007199254740993", 1, 2, 2, 18, 1, 9007199254740993], ["ab", 2, 0, 19, 21, 0, 0]] +["int", "int", "int"] +["beyond-int64", "value", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_int_tokens.eigs b/tests/phase2/builtins/builtins/scan_int_tokens.eigs new file mode 100644 index 000000000..b3764241b --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_int_tokens.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: scan_int_tokens (int-preserving, LOSS; integers accumulate in a double) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +rows is scan_int_tokens of "7 9007199254740993\nab" +print of rows +print of [nt of rows[0][1], nt of rows[1][6], nt of rows[2][5]] +try: + print of ["beyond-answer", scan_int_tokens of "9223372036854775808"] +catch e: + print of ["beyond-int64", e.kind, contains of [e.message, "9223372036854775808"]] diff --git a/tests/phase2/builtins/builtins/scan_int_tokens.pre b/tests/phase2/builtins/builtins/scan_int_tokens.pre new file mode 100644 index 000000000..c08a1a0dd --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_int_tokens.pre @@ -0,0 +1,4 @@ +[["7", 1, 0, 0, 1, 1, 7], ["9007199254740993", 1, 2, 2, 18, 1, 9007199254740992], ["ab", 2, 0, 19, 21, 0, 0]] +["float", "float", "float"] +["beyond-answer", [["9223372036854775808", 1, 0, 0, 19, 1, 9.223372036854778e+18]]] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_int_tokens.tag b/tests/phase2/builtins/builtins/scan_int_tokens.tag new file mode 100644 index 000000000..4d052acd4 --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_int_tokens.tag @@ -0,0 +1,4 @@ +[["7", 1, 0, 0, 1, 1, 7], ["9007199254740993", 1, 2, 2, 18, 1, 9007199254740992], ["ab", 2, 0, 19, 21, 0, 0]] +["undefined_name", "undefined_name", "undefined_name"] +["beyond-answer", [["9223372036854775808", 1, 0, 0, 19, 1, 9.223372036854778e+18]]] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_ints.branch b/tests/phase2/builtins/builtins/scan_ints.branch new file mode 100644 index 000000000..f7b87398c --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_ints.branch @@ -0,0 +1,5 @@ +[1, -2, 3, 9007199254740993, 8] +["int", "int"] +[6] +["beyond-int64", "value", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_ints.eigs b/tests/phase2/builtins/builtins/scan_ints.eigs new file mode 100644 index 000000000..d9809ab4a --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_ints.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: scan_ints (int-preserving, LOSS; integers accumulate in a double) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is scan_ints of "1 -2 +3 9007199254740993 x7 8" +print of r +print of [nt of r[0], nt of r[3]] +c is scan_ints of ["# 5\n6", "#"] +print of c +try: + print of ["beyond-answer", scan_ints of "1 9223372036854775808"] +catch e: + print of ["beyond-int64", e.kind, contains of [e.message, "9223372036854775808"]] diff --git a/tests/phase2/builtins/builtins/scan_ints.pre b/tests/phase2/builtins/builtins/scan_ints.pre new file mode 100644 index 000000000..66d8e9d1d --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_ints.pre @@ -0,0 +1,5 @@ +[1, -2, 3, 9007199254740992, 8] +["float", "float"] +[6] +["beyond-answer", [1, 9.223372036854778e+18]] +rc=0 diff --git a/tests/phase2/builtins/builtins/scan_ints.tag b/tests/phase2/builtins/builtins/scan_ints.tag new file mode 100644 index 000000000..fd77c9f28 --- /dev/null +++ b/tests/phase2/builtins/builtins/scan_ints.tag @@ -0,0 +1,5 @@ +[1, -2, 3, 9007199254740992, 8] +["undefined_name", "undefined_name"] +[6] +["beyond-answer", [1, 9.223372036854778e+18]] +rc=0 diff --git a/tests/phase2/builtins/builtins/scatter_add.branch b/tests/phase2/builtins/builtins/scatter_add.branch new file mode 100644 index 000000000..fdaa07fa0 --- /dev/null +++ b/tests/phase2/builtins/builtins/scatter_add.branch @@ -0,0 +1,4 @@ +[1.0, 0.0, 5.0, "float"] +["index-fraction", "value"] +[1.0, 5.0] +rc=0 diff --git a/tests/phase2/builtins/builtins/scatter_add.eigs b/tests/phase2/builtins/builtins/scatter_add.eigs new file mode 100644 index 000000000..8c096863b --- /dev/null +++ b/tests/phase2/builtins/builtins/scatter_add.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: scatter_add (float-producing; indices through the N5 index reader (value); float accumulation) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +d is buffer of 3 +scatter_add of [d, [0, 2], [1, 5]] +print of [d[0], d[1], d[2], nt of d[0]] +try: + scatter_add of [d, [1.5, 0], [1, 1]] + print of ["fraction accepted", d[1]] +catch e: + print of ["index-fraction", e.kind] +scatter_add of [d, [1.0, 1.0], [2, 3]] +print of [d[0], d[1]] diff --git a/tests/phase2/builtins/builtins/scatter_add.pre b/tests/phase2/builtins/builtins/scatter_add.pre new file mode 100644 index 000000000..cfd83616b --- /dev/null +++ b/tests/phase2/builtins/builtins/scatter_add.pre @@ -0,0 +1,4 @@ +[1, 0, 5, "float"] +["index-fraction", "index_range"] +[1, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/scatter_add.tag b/tests/phase2/builtins/builtins/scatter_add.tag new file mode 100644 index 000000000..35a2cdabd --- /dev/null +++ b/tests/phase2/builtins/builtins/scatter_add.tag @@ -0,0 +1,4 @@ +[1, 0, 5, "undefined_name"] +["index-fraction", "index_range"] +[1, 5] +rc=0 diff --git a/tests/phase2/builtins/builtins/seed_random.branch b/tests/phase2/builtins/builtins/seed_random.branch new file mode 100644 index 000000000..2e723de86 --- /dev/null +++ b/tests/phase2/builtins/builtins/seed_random.branch @@ -0,0 +1,5 @@ +["seeds-2^53+1-and-2^53-differ", true] +true +["fraction", "value"] +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/seed_random.eigs b/tests/phase2/builtins/builtins/seed_random.eigs new file mode 100644 index 000000000..a6cb0a117 --- /dev/null +++ b/tests/phase2/builtins/builtins/seed_random.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: seed_random (int-requiring, LOSS; a seed's range is all of int64; Lua randomseed takes integers); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is seed_random of 9007199254740993 +a is random_int of [0, 1000000] +seed_random of 9007199254740992 +b is random_int of [0, 1000000] +print of ["seeds-2^53+1-and-2^53-differ", a != b] +print of r +try: + seed_random of 1.5 + print of "fraction accepted" +catch e: + print of ["fraction", e.kind] +seed_random of 42.0 +x is random_int of [0, 1000000] +seed_random of 42 +y is random_int of [0, 1000000] +print of ["integral-float-seed", x == y] diff --git a/tests/phase2/builtins/builtins/seed_random.pre b/tests/phase2/builtins/builtins/seed_random.pre new file mode 100644 index 000000000..6bc5e9a03 --- /dev/null +++ b/tests/phase2/builtins/builtins/seed_random.pre @@ -0,0 +1,5 @@ +["seeds-2^53+1-and-2^53-differ", false] +true +fraction accepted +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/seed_random.tag b/tests/phase2/builtins/builtins/seed_random.tag new file mode 100644 index 000000000..6bc5e9a03 --- /dev/null +++ b/tests/phase2/builtins/builtins/seed_random.tag @@ -0,0 +1,5 @@ +["seeds-2^53+1-and-2^53-differ", false] +true +fraction accepted +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/send.branch b/tests/phase2/builtins/builtins/send.branch new file mode 100644 index 000000000..09c61ebba --- /dev/null +++ b/tests/phase2/builtins/builtins/send.branch @@ -0,0 +1,4 @@ +["id-fraction", "value"] +["gen-fraction", "value"] +[7, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/send.eigs b/tests/phase2/builtins/builtins/send.eigs new file mode 100644 index 000000000..f337c78f5 --- /dev/null +++ b/tests/phase2/builtins/builtins/send.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: send (int-requiring; channel handle ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +try: + send of [{"_channel_id": id + 0.5, "_channel_gen": gen}, 1] + print of "id fraction accepted" +catch e: + print of ["id-fraction", e.kind] +try: + send of [{"_channel_id": id, "_channel_gen": gen + 0.5}, 2] + print of "gen fraction accepted" +catch e: + print of ["gen-fraction", e.kind] +send of [{"_channel_id": id + 0.0, "_channel_gen": gen + 0.0}, 7] +v is recv of c +print of [v, nt of v] diff --git a/tests/phase2/builtins/builtins/send.pre b/tests/phase2/builtins/builtins/send.pre new file mode 100644 index 000000000..a07225033 --- /dev/null +++ b/tests/phase2/builtins/builtins/send.pre @@ -0,0 +1,4 @@ +id fraction accepted +gen fraction accepted +[1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/send.tag b/tests/phase2/builtins/builtins/send.tag new file mode 100644 index 000000000..80938ebd4 --- /dev/null +++ b/tests/phase2/builtins/builtins/send.tag @@ -0,0 +1,4 @@ +id fraction accepted +gen fraction accepted +[1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/set_at.branch b/tests/phase2/builtins/builtins/set_at.branch new file mode 100644 index 000000000..492a97bb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/set_at.branch @@ -0,0 +1,5 @@ +[10, 99, 30] +["index-fraction", "value"] +[10, 99, 5] +[7.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/set_at.eigs b/tests/phase2/builtins/builtins/set_at.eigs new file mode 100644 index 000000000..09a13f92e --- /dev/null +++ b/tests/phase2/builtins/builtins/set_at.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: set_at (int-requiring; index, row and col; a buffer value converts to float); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [10, 20, 30] +set_at of [xs, 1, 99] +print of xs +try: + set_at of [xs, 1.5, 7] + print of ["fraction accepted", xs] +catch e: + print of ["index-fraction", e.kind] +set_at of [xs, 2.0, 5] +print of xs +b is buffer of 2 +set_at of [b, 0, 7] +print of [b[0], nt of b[0]] diff --git a/tests/phase2/builtins/builtins/set_at.pre b/tests/phase2/builtins/builtins/set_at.pre new file mode 100644 index 000000000..113c544e3 --- /dev/null +++ b/tests/phase2/builtins/builtins/set_at.pre @@ -0,0 +1,5 @@ +[10, 99, 30] +["fraction accepted", [10, 7, 30]] +[10, 7, 5] +[7, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/set_at.tag b/tests/phase2/builtins/builtins/set_at.tag new file mode 100644 index 000000000..8a4f86a12 --- /dev/null +++ b/tests/phase2/builtins/builtins/set_at.tag @@ -0,0 +1,5 @@ +[10, 99, 30] +["fraction accepted", [10, 7, 30]] +[10, 7, 5] +[7, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/set_exit_code.branch b/tests/phase2/builtins/builtins/set_exit_code.branch new file mode 100644 index 000000000..9a86adc4c --- /dev/null +++ b/tests/phase2/builtins/builtins/set_exit_code.branch @@ -0,0 +1,6 @@ +start +["code-fraction", "value"] +["code-past-byte", "value"] +["code-negative", "value"] +continued +rc=3 diff --git a/tests/phase2/builtins/builtins/set_exit_code.eigs b/tests/phase2/builtins/builtins/set_exit_code.eigs new file mode 100644 index 000000000..b1fc03ad6 --- /dev/null +++ b/tests/phase2/builtins/builtins/set_exit_code.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: set_exit_code (int-requiring; N5, 0..255; R1.6-S2); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"exit-code":3}} +print of "start" +try: + set_exit_code of 1.5 +catch e: + print of ["code-fraction", e.kind] +try: + set_exit_code of 256 +catch e: + print of ["code-past-byte", e.kind] +try: + set_exit_code of -1 +catch e: + print of ["code-negative", e.kind] +set_exit_code of 3.0 +print of "continued" diff --git a/tests/phase2/builtins/builtins/set_exit_code.pre b/tests/phase2/builtins/builtins/set_exit_code.pre new file mode 100644 index 000000000..9a86adc4c --- /dev/null +++ b/tests/phase2/builtins/builtins/set_exit_code.pre @@ -0,0 +1,6 @@ +start +["code-fraction", "value"] +["code-past-byte", "value"] +["code-negative", "value"] +continued +rc=3 diff --git a/tests/phase2/builtins/builtins/set_exit_code.tag b/tests/phase2/builtins/builtins/set_exit_code.tag new file mode 100644 index 000000000..525e87db2 --- /dev/null +++ b/tests/phase2/builtins/builtins/set_exit_code.tag @@ -0,0 +1,5 @@ +start +["code-fraction", "undefined_name"] +["code-past-byte", "undefined_name"] +["code-negative", "undefined_name"] +rc=1 diff --git a/tests/phase2/builtins/builtins/sgd_update.branch b/tests/phase2/builtins/builtins/sgd_update.branch new file mode 100644 index 000000000..cf75cc2bb --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update.branch @@ -0,0 +1,2 @@ +[[0.0, 1.0], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update.eigs b/tests/phase2/builtins/builtins/sgd_update.eigs new file mode 100644 index 000000000..4b29ffd14 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: sgd_update (float-producing; float update) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is sgd_update of [[1, 2], [2, 2], 0.5] +print of [r, nt of r[0]] diff --git a/tests/phase2/builtins/builtins/sgd_update.pre b/tests/phase2/builtins/builtins/sgd_update.pre new file mode 100644 index 000000000..6bba358eb --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update.pre @@ -0,0 +1,2 @@ +[[0, 1], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update.tag b/tests/phase2/builtins/builtins/sgd_update.tag new file mode 100644 index 000000000..fe398f5b5 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update.tag @@ -0,0 +1,2 @@ +[[0, 1], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_cols.branch b/tests/phase2/builtins/builtins/sgd_update_cols.branch new file mode 100644 index 000000000..e0a3ee70b --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_cols.branch @@ -0,0 +1,4 @@ +[[[-1.0, 2.0], [1.0, 4.0]], "float"] +["col-fraction", "value"] +[[-1.0, 0.0], [1.0, 2.0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_cols.eigs b/tests/phase2/builtins/builtins/sgd_update_cols.eigs new file mode 100644 index 000000000..6ec84c5a5 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_cols.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: sgd_update_cols (float-producing; column indices int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +m is [[1.0, 2.0], [3.0, 4.0]] +g is [[2, 2], [2, 2]] +sgd_update_cols of [m, g, [0, 0], 0.5] +print of [m, nt of m[1][0]] +try: + sgd_update_cols of [m, g, [0.5, 1], 0.5] + print of ["fraction accepted", m] +catch e: + print of ["col-fraction", e.kind] +sgd_update_cols of [m, g, [1.0, 1.0], 0.5] +print of m diff --git a/tests/phase2/builtins/builtins/sgd_update_cols.pre b/tests/phase2/builtins/builtins/sgd_update_cols.pre new file mode 100644 index 000000000..657aae73d --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_cols.pre @@ -0,0 +1,4 @@ +[[[-1, 2], [1, 4]], "float"] +["fraction accepted", [[-2, 1], [0, 3]]] +[[-2, -1], [0, 1]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_cols.tag b/tests/phase2/builtins/builtins/sgd_update_cols.tag new file mode 100644 index 000000000..8cd24b9ac --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_cols.tag @@ -0,0 +1,4 @@ +[[[-1, 2], [1, 4]], "undefined_name"] +["fraction accepted", [[-2, 1], [0, 3]]] +[[-2, -1], [0, 1]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_rows.branch b/tests/phase2/builtins/builtins/sgd_update_rows.branch new file mode 100644 index 000000000..79c3cdbf5 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_rows.branch @@ -0,0 +1,4 @@ +[[[1.0, 2.0], [1.0, 2.0]], "float"] +["row-fraction", "value"] +[[-1.0, 0.0], [1.0, 2.0]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_rows.eigs b/tests/phase2/builtins/builtins/sgd_update_rows.eigs new file mode 100644 index 000000000..6fd0d9244 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_rows.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: sgd_update_rows (float-producing; row indices int) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +m is [[1.0, 2.0], [3.0, 4.0]] +g is [[2, 2], [2, 2]] +sgd_update_rows of [m, g, [1, 1], 0.5] +print of [m, nt of m[1][0]] +try: + sgd_update_rows of [m, g, [0.5, 1], 0.5] + print of ["fraction accepted", m] +catch e: + print of ["row-fraction", e.kind] +sgd_update_rows of [m, g, [0.0, 0.0], 0.5] +print of m diff --git a/tests/phase2/builtins/builtins/sgd_update_rows.pre b/tests/phase2/builtins/builtins/sgd_update_rows.pre new file mode 100644 index 000000000..f4dac5f58 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_rows.pre @@ -0,0 +1,4 @@ +[[[1, 2], [1, 2]], "float"] +["fraction accepted", [[0, 1], [0, 1]]] +[[-2, -1], [0, 1]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sgd_update_rows.tag b/tests/phase2/builtins/builtins/sgd_update_rows.tag new file mode 100644 index 000000000..f0fc40332 --- /dev/null +++ b/tests/phase2/builtins/builtins/sgd_update_rows.tag @@ -0,0 +1,4 @@ +[[[1, 2], [1, 2]], "undefined_name"] +["fraction accepted", [[0, 1], [0, 1]]] +[[-2, -1], [0, 1]] +rc=0 diff --git a/tests/phase2/builtins/builtins/shape.branch b/tests/phase2/builtins/builtins/shape.branch new file mode 100644 index 000000000..567848cf4 --- /dev/null +++ b/tests/phase2/builtins/builtins/shape.branch @@ -0,0 +1,4 @@ +[[2, 3], "int"] +[[4], "int"] +[[2, 2], "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shape.eigs b/tests/phase2/builtins/builtins/shape.eigs new file mode 100644 index 000000000..12d0dd22d --- /dev/null +++ b/tests/phase2/builtins/builtins/shape.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: shape (int-preserving; dimension counts) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is shape of [[1, 2, 3], [4, 5, 6]] +print of [a, nt of a[0]] +b is shape of (buffer of 4) +print of [b, nt of b[0]] +c is shape of (reshape of [buf_from_list of [1, 2, 3, 4], 2, 2]) +print of [c, nt of c[1]] diff --git a/tests/phase2/builtins/builtins/shape.pre b/tests/phase2/builtins/builtins/shape.pre new file mode 100644 index 000000000..0065671ea --- /dev/null +++ b/tests/phase2/builtins/builtins/shape.pre @@ -0,0 +1,4 @@ +[[2, 3], "float"] +[[4], "float"] +[[2, 2], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shape.tag b/tests/phase2/builtins/builtins/shape.tag new file mode 100644 index 000000000..289116c47 --- /dev/null +++ b/tests/phase2/builtins/builtins/shape.tag @@ -0,0 +1,4 @@ +[[2, 3], "undefined_name"] +[[4], "undefined_name"] +[[2, 2], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_delete.branch b/tests/phase2/builtins/builtins/shared_delete.branch new file mode 100644 index 000000000..7b156a5e3 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_delete.branch @@ -0,0 +1,2 @@ +[1, "int", 0, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_delete.eigs b/tests/phase2/builtins/builtins/shared_delete.eigs new file mode 100644 index 000000000..223356123 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_delete.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: shared_delete (int-preserving; removed count; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + shared_set of ["k", 1] +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +a is shared_delete of "k" +b is shared_delete of "k" +print of [a, nt of a, b, nt of b] diff --git a/tests/phase2/builtins/builtins/shared_delete.pre b/tests/phase2/builtins/builtins/shared_delete.pre new file mode 100644 index 000000000..e68eaaf68 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_delete.pre @@ -0,0 +1,2 @@ +[1, "float", 0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_delete.tag b/tests/phase2/builtins/builtins/shared_delete.tag new file mode 100644 index 000000000..ed0883aa1 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_delete.tag @@ -0,0 +1,2 @@ +[1, "undefined_name", 0, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_incr.branch b/tests/phase2/builtins/builtins/shared_incr.branch new file mode 100644 index 000000000..7d199a841 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_incr.branch @@ -0,0 +1,4 @@ +[9007199254740993, "int"] +[9007199254740993, "int"] +["overflow", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_incr.eigs b/tests/phase2/builtins/builtins/shared_incr.eigs new file mode 100644 index 000000000..6141b27a7 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_incr.eigs @@ -0,0 +1,20 @@ +# R1.6 behaviour-table probe: shared_incr (int-preserving, LOSS; PROFILE server (make server): the HTTP capability) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + shared_set of ["k", 9007199254740992] +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +r is shared_incr of ["k", 1] +print of [r, nt of r] +g is shared_get of "k" +print of [g, nt of g] +shared_set of ["m", 9223372036854775807] +try: + print of (shared_incr of ["m", 1]) +catch e: + print of ["overflow", e.kind] diff --git a/tests/phase2/builtins/builtins/shared_incr.pre b/tests/phase2/builtins/builtins/shared_incr.pre new file mode 100644 index 000000000..da6c78039 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_incr.pre @@ -0,0 +1,4 @@ +[9007199254740992, "float"] +[9007199254740992, "float"] +9.223372036854776e+18 +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_incr.tag b/tests/phase2/builtins/builtins/shared_incr.tag new file mode 100644 index 000000000..a968a0104 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_incr.tag @@ -0,0 +1,4 @@ +[9007199254740992, "undefined_name"] +[9007199254740992, "undefined_name"] +9.223372036854776e+18 +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_size.branch b/tests/phase2/builtins/builtins/shared_size.branch new file mode 100644 index 000000000..0a1a225e1 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_size.branch @@ -0,0 +1,2 @@ +[2, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_size.eigs b/tests/phase2/builtins/builtins/shared_size.eigs new file mode 100644 index 000000000..2db1c05e1 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_size.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: shared_size (int-preserving; entry count; PROFILE server (make server)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + shared_set of ["k1", 1] +catch e: + print of ["capability", e.kind, e.message] + exit of 0 +shared_set of ["k2", 2] +n is shared_size of null +print of [n, nt of n] diff --git a/tests/phase2/builtins/builtins/shared_size.pre b/tests/phase2/builtins/builtins/shared_size.pre new file mode 100644 index 000000000..18e19ed07 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_size.pre @@ -0,0 +1,2 @@ +[2, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/shared_size.tag b/tests/phase2/builtins/builtins/shared_size.tag new file mode 100644 index 000000000..5af912796 --- /dev/null +++ b/tests/phase2/builtins/builtins/shared_size.tag @@ -0,0 +1,2 @@ +[2, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sign_extend.branch b/tests/phase2/builtins/builtins/sign_extend.branch new file mode 100644 index 000000000..fe1f35e46 --- /dev/null +++ b/tests/phase2/builtins/builtins/sign_extend.branch @@ -0,0 +1,7 @@ +[-1, "int"] +[9007199254740865, "int"] +["bits-fraction", "value"] +["value-fraction", "value"] +[-1, "int"] +["overflow", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sign_extend.eigs b/tests/phase2/builtins/builtins/sign_extend.eigs new file mode 100644 index 000000000..9dbfd788f --- /dev/null +++ b/tests/phase2/builtins/builtins/sign_extend.eigs @@ -0,0 +1,24 @@ +# R1.6 behaviour-table probe: sign_extend (int-requiring, LOSS; (int64_t) of a double); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is sign_extend of [0xFF, 8] +print of [r, nt of r] +big is sign_extend of [9007199254741121, 8] +print of [big, nt of big] +try: + print of (sign_extend of [255, 8.5]) +catch e: + print of ["bits-fraction", e.kind] +try: + print of (sign_extend of [255.5, 8]) +catch e: + print of ["value-fraction", e.kind] +f is sign_extend of [255.0, 8.0] +print of [f, nt of f] +try: + print of ["overflow-answer", sign_extend of [-9223372036854775553, 8]] +catch e: + print of ["overflow", e.kind] diff --git a/tests/phase2/builtins/builtins/sign_extend.pre b/tests/phase2/builtins/builtins/sign_extend.pre new file mode 100644 index 000000000..b43b1f2fd --- /dev/null +++ b/tests/phase2/builtins/builtins/sign_extend.pre @@ -0,0 +1,7 @@ +[-1, "float"] +[9007199254740864, "float"] +-1 +-1 +[-1, "float"] +["overflow-answer", -9.223372036854776e+18] +rc=0 diff --git a/tests/phase2/builtins/builtins/sign_extend.tag b/tests/phase2/builtins/builtins/sign_extend.tag new file mode 100644 index 000000000..9386c6299 --- /dev/null +++ b/tests/phase2/builtins/builtins/sign_extend.tag @@ -0,0 +1,7 @@ +[-1, "undefined_name"] +[9007199254740864, "undefined_name"] +-1 +-1 +[-1, "undefined_name"] +["overflow-answer", -9.223372036854776e+18] +rc=0 diff --git a/tests/phase2/builtins/builtins/sin.branch b/tests/phase2/builtins/builtins/sin.branch new file mode 100644 index 000000000..ce787b86a --- /dev/null +++ b/tests/phase2/builtins/builtins/sin.branch @@ -0,0 +1,3 @@ +[0.0, "float"] +[0.8414709848078965, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sin.eigs b/tests/phase2/builtins/builtins/sin.eigs new file mode 100644 index 000000000..581e978d1 --- /dev/null +++ b/tests/phase2/builtins/builtins/sin.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: sin (float-producing; Lua math.sin) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is sin of 0 +print of [a, nt of a] +b is sin of 1 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/sin.pre b/tests/phase2/builtins/builtins/sin.pre new file mode 100644 index 000000000..db4f7ee0a --- /dev/null +++ b/tests/phase2/builtins/builtins/sin.pre @@ -0,0 +1,3 @@ +[0, "float"] +[0.8414709848078965, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sin.tag b/tests/phase2/builtins/builtins/sin.tag new file mode 100644 index 000000000..e37fbb151 --- /dev/null +++ b/tests/phase2/builtins/builtins/sin.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[0.8414709848078965, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/softmax.branch b/tests/phase2/builtins/builtins/softmax.branch new file mode 100644 index 000000000..7adc9a629 --- /dev/null +++ b/tests/phase2/builtins/builtins/softmax.branch @@ -0,0 +1,2 @@ +[[0.5, 0.5], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/softmax.eigs b/tests/phase2/builtins/builtins/softmax.eigs new file mode 100644 index 000000000..4f42fa524 --- /dev/null +++ b/tests/phase2/builtins/builtins/softmax.eigs @@ -0,0 +1,8 @@ +# R1.6 behaviour-table probe: softmax (float-producing; ML (float)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is softmax of [0, 0] +print of [r, nt of r[0]] diff --git a/tests/phase2/builtins/builtins/softmax.pre b/tests/phase2/builtins/builtins/softmax.pre new file mode 100644 index 000000000..7adc9a629 --- /dev/null +++ b/tests/phase2/builtins/builtins/softmax.pre @@ -0,0 +1,2 @@ +[[0.5, 0.5], "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/softmax.tag b/tests/phase2/builtins/builtins/softmax.tag new file mode 100644 index 000000000..2c3eb226a --- /dev/null +++ b/tests/phase2/builtins/builtins/softmax.tag @@ -0,0 +1,2 @@ +[[0.5, 0.5], "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort.branch b/tests/phase2/builtins/builtins/sort.branch new file mode 100644 index 000000000..c70cad1dd --- /dev/null +++ b/tests/phase2/builtins/builtins/sort.branch @@ -0,0 +1,3 @@ +[2.5, 3, 9007199254740992, 9007199254740993] +["float", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort.eigs b/tests/phase2/builtins/builtins/sort.eigs new file mode 100644 index 000000000..43f2ee24d --- /dev/null +++ b/tests/phase2/builtins/builtins/sort.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: sort (other, LOSS; exact int comparator written in R1.2, live from R1.3) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +xs is [9007199254740993, 9007199254740992, 3, 2.5] +sort of xs +print of xs +print of [nt of xs[0], nt of xs[1]] diff --git a/tests/phase2/builtins/builtins/sort.pre b/tests/phase2/builtins/builtins/sort.pre new file mode 100644 index 000000000..c70cad1dd --- /dev/null +++ b/tests/phase2/builtins/builtins/sort.pre @@ -0,0 +1,3 @@ +[2.5, 3, 9007199254740992, 9007199254740993] +["float", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort.tag b/tests/phase2/builtins/builtins/sort.tag new file mode 100644 index 000000000..36c1d0e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/sort.tag @@ -0,0 +1,3 @@ +[2.5, 3, 9007199254740992, 9007199254740992] +["undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort_by.branch b/tests/phase2/builtins/builtins/sort_by.branch new file mode 100644 index 000000000..c134b9283 --- /dev/null +++ b/tests/phase2/builtins/builtins/sort_by.branch @@ -0,0 +1,2 @@ +[["c", 2.5], ["b", 9007199254740992], ["a", 9007199254740993]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort_by.eigs b/tests/phase2/builtins/builtins/sort_by.eigs new file mode 100644 index 000000000..da165ef39 --- /dev/null +++ b/tests/phase2/builtins/builtins/sort_by.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: sort_by (other, LOSS; numeric keys compared exactly from R1.3) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define key(r) as: + return r[1] +rows is [["a", 9007199254740993], ["b", 9007199254740992], ["c", 2.5]] +s is sort_by of [rows, key] +print of s diff --git a/tests/phase2/builtins/builtins/sort_by.pre b/tests/phase2/builtins/builtins/sort_by.pre new file mode 100644 index 000000000..c134b9283 --- /dev/null +++ b/tests/phase2/builtins/builtins/sort_by.pre @@ -0,0 +1,2 @@ +[["c", 2.5], ["b", 9007199254740992], ["a", 9007199254740993]] +rc=0 diff --git a/tests/phase2/builtins/builtins/sort_by.tag b/tests/phase2/builtins/builtins/sort_by.tag new file mode 100644 index 000000000..cd327c2d1 --- /dev/null +++ b/tests/phase2/builtins/builtins/sort_by.tag @@ -0,0 +1,2 @@ +[["c", 2.5], ["a", 9007199254740992], ["b", 9007199254740992]] +rc=0 diff --git a/tests/phase2/builtins/builtins/spawn.branch b/tests/phase2/builtins/builtins/spawn.branch new file mode 100644 index 000000000..35234d3af --- /dev/null +++ b/tests/phase2/builtins/builtins/spawn.branch @@ -0,0 +1,3 @@ +["int", "int", "int"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/spawn.eigs b/tests/phase2/builtins/builtins/spawn.eigs new file mode 100644 index 000000000..eaf097404 --- /dev/null +++ b/tests/phase2/builtins/builtins/spawn.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: spawn (int-preserving; handle dict with int id/gen fields) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x + 1 +h is spawn of [w, 41] +print of [nt of h["_handle_id"], nt of h["_handle_gen"], nt of h["done"]] +r is thread_join of h +print of [r, nt of r] diff --git a/tests/phase2/builtins/builtins/spawn.pre b/tests/phase2/builtins/builtins/spawn.pre new file mode 100644 index 000000000..dc2844752 --- /dev/null +++ b/tests/phase2/builtins/builtins/spawn.pre @@ -0,0 +1,3 @@ +["float", "float", "float"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/spawn.tag b/tests/phase2/builtins/builtins/spawn.tag new file mode 100644 index 000000000..738ec4dd4 --- /dev/null +++ b/tests/phase2/builtins/builtins/spawn.tag @@ -0,0 +1,3 @@ +["undefined_name", "undefined_name", "undefined_name"] +[42, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sqrt.branch b/tests/phase2/builtins/builtins/sqrt.branch new file mode 100644 index 000000000..d1f4ecfd8 --- /dev/null +++ b/tests/phase2/builtins/builtins/sqrt.branch @@ -0,0 +1,3 @@ +[2.0, "float"] +[1.4142135623730951, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sqrt.eigs b/tests/phase2/builtins/builtins/sqrt.eigs new file mode 100644 index 000000000..56653401a --- /dev/null +++ b/tests/phase2/builtins/builtins/sqrt.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: sqrt (float-producing; Lua math.sqrt) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is sqrt of 4 +print of [a, nt of a] +b is sqrt of 2 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/sqrt.pre b/tests/phase2/builtins/builtins/sqrt.pre new file mode 100644 index 000000000..ac31f6992 --- /dev/null +++ b/tests/phase2/builtins/builtins/sqrt.pre @@ -0,0 +1,3 @@ +[2, "float"] +[1.4142135623730951, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sqrt.tag b/tests/phase2/builtins/builtins/sqrt.tag new file mode 100644 index 000000000..167d1c67a --- /dev/null +++ b/tests/phase2/builtins/builtins/sqrt.tag @@ -0,0 +1,3 @@ +[2, "undefined_name"] +[1.4142135623730951, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/state_at.branch b/tests/phase2/builtins/builtins/state_at.branch new file mode 100644 index 000000000..c43f52b66 --- /dev/null +++ b/tests/phase2/builtins/builtins/state_at.branch @@ -0,0 +1,4 @@ +[5, 7] +["line-fraction", "value"] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/state_at.eigs b/tests/phase2/builtins/builtins/state_at.eigs new file mode 100644 index 000000000..f27829bbe --- /dev/null +++ b/tests/phase2/builtins/builtins/state_at.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: state_at (int-requiring; step index (the probe's own line numbers)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +x is 5 +y is 7 +s is state_at of 8 +print of [s["x"], s["y"]] +try: + t is state_at of 8.5 + print of ["fraction accepted", t["y"]] +catch e: + print of ["line-fraction", e.kind] +u is state_at of 8.0 +print of u["y"] diff --git a/tests/phase2/builtins/builtins/state_at.pre b/tests/phase2/builtins/builtins/state_at.pre new file mode 100644 index 000000000..2eecda98a --- /dev/null +++ b/tests/phase2/builtins/builtins/state_at.pre @@ -0,0 +1,4 @@ +[5, 7] +["fraction accepted", 7] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/state_at.tag b/tests/phase2/builtins/builtins/state_at.tag new file mode 100644 index 000000000..2eecda98a --- /dev/null +++ b/tests/phase2/builtins/builtins/state_at.tag @@ -0,0 +1,4 @@ +[5, 7] +["fraction accepted", 7] +7 +rc=0 diff --git a/tests/phase2/builtins/builtins/store_close.branch b/tests/phase2/builtins/builtins/store_close.branch new file mode 100644 index 000000000..69e77131b --- /dev/null +++ b/tests/phase2/builtins/builtins/store_close.branch @@ -0,0 +1,4 @@ +["id-fraction", "value"] +null +["after-close", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_close.eigs b/tests/phase2/builtins/builtins/store_close.eigs new file mode 100644 index 000000000..9d9b689db --- /dev/null +++ b/tests/phase2/builtins/builtins/store_close.eigs @@ -0,0 +1,21 @@ +# R1.6 behaviour-table probe: store_close (int-requiring; handle id/gen); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +try: + store_close of {"_store_id": s["_store_id"] + 0.5, "_store_gen": s["_store_gen"], "_type": "eigenstore"} + print of "id fraction closed it" +catch e: + print of ["id-fraction", e.kind] +try: + print of (store_close of {"_store_id": s["_store_id"] + 0.0, "_store_gen": s["_store_gen"] + 0.0, "_type": "eigenstore"}) +catch e: + print of ["integral-close", e.kind] +try: + print of ["count-after-close", store_count of [s, "c"]] +catch e: + print of ["after-close", e.kind] diff --git a/tests/phase2/builtins/builtins/store_close.pre b/tests/phase2/builtins/builtins/store_close.pre new file mode 100644 index 000000000..ef7ec25bd --- /dev/null +++ b/tests/phase2/builtins/builtins/store_close.pre @@ -0,0 +1,4 @@ +id fraction closed it +["integral-close", "value"] +["after-close", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_close.tag b/tests/phase2/builtins/builtins/store_close.tag new file mode 100644 index 000000000..ef7ec25bd --- /dev/null +++ b/tests/phase2/builtins/builtins/store_close.tag @@ -0,0 +1,4 @@ +id fraction closed it +["integral-close", "value"] +["after-close", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_collections.branch b/tests/phase2/builtins/builtins/store_collections.branch new file mode 100644 index 000000000..3b65103dc --- /dev/null +++ b/tests/phase2/builtins/builtins/store_collections.branch @@ -0,0 +1,4 @@ +["c"] +["id-fraction", "value"] +["c"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_collections.eigs b/tests/phase2/builtins/builtins/store_collections.eigs new file mode 100644 index 000000000..9cc1f6f27 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_collections.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: store_collections (int-requiring; handle id); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"v": 1}] +print of (store_collections of s) +try: + print of ["fraction-answer", store_collections of {"_store_id": s["_store_id"] + 0.5, "_store_gen": s["_store_gen"], "_type": "eigenstore"}] +catch e: + print of ["id-fraction", e.kind] +print of (store_collections of {"_store_id": s["_store_id"] + 0.0, "_store_gen": s["_store_gen"] + 0.0, "_type": "eigenstore"}) +store_close of s diff --git a/tests/phase2/builtins/builtins/store_collections.pre b/tests/phase2/builtins/builtins/store_collections.pre new file mode 100644 index 000000000..e27d98533 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_collections.pre @@ -0,0 +1,4 @@ +["c"] +["fraction-answer", ["c"]] +["c"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_collections.tag b/tests/phase2/builtins/builtins/store_collections.tag new file mode 100644 index 000000000..e27d98533 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_collections.tag @@ -0,0 +1,4 @@ +["c"] +["fraction-answer", ["c"]] +["c"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_count.branch b/tests/phase2/builtins/builtins/store_count.branch new file mode 100644 index 000000000..f0fdfcab0 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_count.branch @@ -0,0 +1,4 @@ +[2, "int"] +["id-fraction", "value"] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/store_count.eigs b/tests/phase2/builtins/builtins/store_count.eigs new file mode 100644 index 000000000..b67727ada --- /dev/null +++ b/tests/phase2/builtins/builtins/store_count.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: store_count (int-requiring; handle and page ids; returns a count); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k1 is store_put of [s, "c", {"v": 1}] +k2 is store_put of [s, "c", {"v": 2}] +n is store_count of [s, "c"] +print of [n, nt of n] +try: + print of ["fraction-answer", store_count of [{"_store_id": s["_store_id"] + 0.5, "_store_gen": s["_store_gen"], "_type": "eigenstore"}, "c"]] +catch e: + print of ["id-fraction", e.kind] +print of (store_count of [{"_store_id": s["_store_id"] + 0.0, "_store_gen": s["_store_gen"] + 0.0, "_type": "eigenstore"}, "c"]) +store_close of s diff --git a/tests/phase2/builtins/builtins/store_count.pre b/tests/phase2/builtins/builtins/store_count.pre new file mode 100644 index 000000000..bf2714698 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_count.pre @@ -0,0 +1,4 @@ +[2, "float"] +["fraction-answer", 2] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/store_count.tag b/tests/phase2/builtins/builtins/store_count.tag new file mode 100644 index 000000000..a6e71e913 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_count.tag @@ -0,0 +1,4 @@ +[2, "undefined_name"] +["fraction-answer", 2] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/store_delete.branch b/tests/phase2/builtins/builtins/store_delete.branch new file mode 100644 index 000000000..1451acc56 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_delete.branch @@ -0,0 +1,5 @@ +["key-fraction", "value"] +["count-after-fraction", 1] +[true, 0] +["delete-big-key", true, 0] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_delete.eigs b/tests/phase2/builtins/builtins/store_delete.eigs new file mode 100644 index 000000000..1a18d6847 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_delete.eigs @@ -0,0 +1,20 @@ +# R1.6 behaviour-table probe: store_delete (int-requiring, LOSS; key formatted %d of an (int) cast); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"v": 1}] +try: + print of (store_delete of [s, "c", 1.9]) +catch e: + print of ["key-fraction", e.kind] +print of ["count-after-fraction", store_count of [s, "c"]] +d is store_delete of [s, "c", 1.0] +print of [d, store_count of [s, "c"]] +k2 is store_put of [s, "c", {"_id": "9007199254740993", "v": 1}] +d2 is store_delete of [s, "c", 9007199254740993] +print of ["delete-big-key", d2, store_count of [s, "c"]] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_delete.pre b/tests/phase2/builtins/builtins/store_delete.pre new file mode 100644 index 000000000..a3af37d26 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_delete.pre @@ -0,0 +1,5 @@ +true +["count-after-fraction", 0] +[false, 0] +["delete-big-key", false, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_delete.tag b/tests/phase2/builtins/builtins/store_delete.tag new file mode 100644 index 000000000..a3af37d26 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_delete.tag @@ -0,0 +1,5 @@ +true +["count-after-fraction", 0] +[false, 0] +["delete-big-key", false, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_drop.branch b/tests/phase2/builtins/builtins/store_drop.branch new file mode 100644 index 000000000..3ff92e8f6 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_drop.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[true, []] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_drop.eigs b/tests/phase2/builtins/builtins/store_drop.eigs new file mode 100644 index 000000000..a63dc39bf --- /dev/null +++ b/tests/phase2/builtins/builtins/store_drop.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: store_drop (int-requiring; handle and page ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"v": 1}] +try: + print of ["fraction-answer", store_drop of [{"_store_id": s["_store_id"] + 0.5, "_store_gen": s["_store_gen"], "_type": "eigenstore"}, "c"]] +catch e: + print of ["id-fraction", e.kind] +d is store_drop of [{"_store_id": s["_store_id"] + 0.0, "_store_gen": s["_store_gen"] + 0.0, "_type": "eigenstore"}, "c"] +print of [d, store_collections of s] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_drop.pre b/tests/phase2/builtins/builtins/store_drop.pre new file mode 100644 index 000000000..986321df1 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_drop.pre @@ -0,0 +1,3 @@ +["fraction-answer", true] +[false, []] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_drop.tag b/tests/phase2/builtins/builtins/store_drop.tag new file mode 100644 index 000000000..986321df1 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_drop.tag @@ -0,0 +1,3 @@ +["fraction-answer", true] +[false, []] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_get.branch b/tests/phase2/builtins/builtins/store_get.branch new file mode 100644 index 000000000..c7bbcd5f9 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_get.branch @@ -0,0 +1,6 @@ +["1", "2", "4294967297"] +[9007199254740993, "int"] +["key-fraction", "value"] +["key-past-int32-found", true] +["key-2^53+1-found", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_get.eigs b/tests/phase2/builtins/builtins/store_get.eigs new file mode 100644 index 000000000..7b412bdf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_get.eigs @@ -0,0 +1,24 @@ +# R1.6 behaviour-table probe: store_get (int-requiring, LOSS; key by N5 (eigs_int_arg, closes #1760); decodes records (N14)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k1 is store_put of [s, "c", {"v": 1}] +k2 is store_put of [s, "c", {"big": 9007199254740993}] +k3 is store_put of [s, "c", {"_id": "4294967297", "w": 3}] +print of [k1, k2, k3] +r is store_get of [s, "c", 2.0] +print of [r["big"], nt of r["big"]] +try: + print of (store_get of [s, "c", 1.9]) +catch e: + print of ["key-fraction", e.kind] +q is store_get of [s, "c", 4294967297] +print of ["key-past-int32-found", q != null] +k4 is store_put of [s, "c", {"_id": "9007199254740993", "x": 5}] +q2 is store_get of [s, "c", 9007199254740993] +print of ["key-2^53+1-found", q2 != null] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_get.pre b/tests/phase2/builtins/builtins/store_get.pre new file mode 100644 index 000000000..f7c8d24f5 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_get.pre @@ -0,0 +1,6 @@ +["1", "2", "4294967297"] +[9007199254740992, "float"] +{"v": 1, "_id": "1"} +["key-past-int32-found", false] +["key-2^53+1-found", false] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_get.tag b/tests/phase2/builtins/builtins/store_get.tag new file mode 100644 index 000000000..a86927b1a --- /dev/null +++ b/tests/phase2/builtins/builtins/store_get.tag @@ -0,0 +1,6 @@ +["1", "2", "4294967297"] +[9007199254740990, "undefined_name"] +{"v": 1, "_id": "1"} +["key-past-int32-found", false] +["key-2^53+1-found", false] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_open.branch b/tests/phase2/builtins/builtins/store_open.branch new file mode 100644 index 000000000..76c9dc465 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_open.branch @@ -0,0 +1,2 @@ +["eigenstore", "int", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_open.eigs b/tests/phase2/builtins/builtins/store_open.eigs new file mode 100644 index 000000000..b37b7779c --- /dev/null +++ b/tests/phase2/builtins/builtins/store_open.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: store_open (int-preserving; handle dict with int id/gen fields) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +print of [s["_type"], nt of s["_store_id"], nt of s["_store_gen"]] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_open.pre b/tests/phase2/builtins/builtins/store_open.pre new file mode 100644 index 000000000..958343d82 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_open.pre @@ -0,0 +1,2 @@ +["eigenstore", "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_open.tag b/tests/phase2/builtins/builtins/store_open.tag new file mode 100644 index 000000000..2c8ad4900 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_open.tag @@ -0,0 +1,2 @@ +["eigenstore", "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_put.branch b/tests/phase2/builtins/builtins/store_put.branch new file mode 100644 index 000000000..8a80bb18a --- /dev/null +++ b/tests/phase2/builtins/builtins/store_put.branch @@ -0,0 +1,3 @@ +["1", "str"] +[9007199254740993, "int", -9223372036854775808, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_put.eigs b/tests/phase2/builtins/builtins/store_put.eigs new file mode 100644 index 000000000..b867da47b --- /dev/null +++ b/tests/phase2/builtins/builtins/store_put.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: store_put (other, LOSS; the writer's %.15g float arm is R1.7; the round trip decodes ints exactly from R1.6) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"big": 9007199254740993, "min": -9223372036854775807 - 1}] +print of [k, type of k] +r is store_get of [s, "c", k] +print of [r["big"], nt of r["big"], r["min"], nt of r["min"]] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_put.pre b/tests/phase2/builtins/builtins/store_put.pre new file mode 100644 index 000000000..b24f085ba --- /dev/null +++ b/tests/phase2/builtins/builtins/store_put.pre @@ -0,0 +1,3 @@ +["1", "str"] +[9007199254740992, "float", -9.223372036854776e+18, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_put.tag b/tests/phase2/builtins/builtins/store_put.tag new file mode 100644 index 000000000..9f39000e6 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_put.tag @@ -0,0 +1,3 @@ +["1", "str"] +[9007199254740990, "undefined_name", -9.22337203685478e+18, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_query.branch b/tests/phase2/builtins/builtins/store_query.branch new file mode 100644 index 000000000..f9c0a5f2b --- /dev/null +++ b/tests/phase2/builtins/builtins/store_query.branch @@ -0,0 +1,2 @@ +[9007199254740993, "int", 2.5, "float", -7, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_query.eigs b/tests/phase2/builtins/builtins/store_query.eigs new file mode 100644 index 000000000..3614c8523 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_query.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: store_query (other, LOSS; decodes records; integer text through strtod today) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"n": 9007199254740993, "f": 2.5, "i": -7}] +rows is store_query of [s, "c"] +r is rows[0] +print of [r["n"], nt of r["n"], r["f"], nt of r["f"], r["i"], nt of r["i"]] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_query.pre b/tests/phase2/builtins/builtins/store_query.pre new file mode 100644 index 000000000..4305febb3 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_query.pre @@ -0,0 +1,2 @@ +[9007199254740992, "float", 2.5, "float", -7, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_query.tag b/tests/phase2/builtins/builtins/store_query.tag new file mode 100644 index 000000000..e900adacc --- /dev/null +++ b/tests/phase2/builtins/builtins/store_query.tag @@ -0,0 +1,2 @@ +[9007199254740990, "undefined_name", 2.5, "undefined_name", -7, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_update.branch b/tests/phase2/builtins/builtins/store_update.branch new file mode 100644 index 000000000..0b5881f88 --- /dev/null +++ b/tests/phase2/builtins/builtins/store_update.branch @@ -0,0 +1,5 @@ +["key-fraction", "value"] +true +[9007199254740993, "int"] +["update-big-key", true, 2] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_update.eigs b/tests/phase2/builtins/builtins/store_update.eigs new file mode 100644 index 000000000..dd620774b --- /dev/null +++ b/tests/phase2/builtins/builtins/store_update.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: store_update (other, LOSS; key as store_delete; re-puts through the record encoder) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +s is store_open of (p + "/db") +k is store_put of [s, "c", {"v": 1}] +try: + print of (store_update of [s, "c", 1.9, {"v": 2}]) +catch e: + print of ["key-fraction", e.kind] +u is store_update of [s, "c", 1.0, {"v": 9007199254740993}] +print of u +r is store_get of [s, "c", "1"] +print of [r["v"], nt of r["v"]] +k2 is store_put of [s, "c", {"_id": "9007199254740993", "v": 1}] +u2 is store_update of [s, "c", 9007199254740993, {"v": 2}] +r2 is store_get of [s, "c", "9007199254740993"] +print of ["update-big-key", u2, r2["v"]] +store_close of s diff --git a/tests/phase2/builtins/builtins/store_update.pre b/tests/phase2/builtins/builtins/store_update.pre new file mode 100644 index 000000000..6956ff3dc --- /dev/null +++ b/tests/phase2/builtins/builtins/store_update.pre @@ -0,0 +1,5 @@ +true +true +[9007199254740992, "float"] +["update-big-key", false, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/store_update.tag b/tests/phase2/builtins/builtins/store_update.tag new file mode 100644 index 000000000..f7b16a4dd --- /dev/null +++ b/tests/phase2/builtins/builtins/store_update.tag @@ -0,0 +1,5 @@ +true +true +[9007199254740990, "undefined_name"] +["update-big-key", false, 1] +rc=0 diff --git a/tests/phase2/builtins/builtins/str.branch b/tests/phase2/builtins/builtins/str.branch new file mode 100644 index 000000000..6a0ef490b --- /dev/null +++ b/tests/phase2/builtins/builtins/str.branch @@ -0,0 +1,2 @@ +["9007199254740993", "2.5", "3.0", "-9223372036854775808"] +rc=0 diff --git a/tests/phase2/builtins/builtins/str.eigs b/tests/phase2/builtins/builtins/str.eigs new file mode 100644 index 000000000..236e2c57d --- /dev/null +++ b/tests/phase2/builtins/builtins/str.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: str (other; N6 printing via value_to_string) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of [str of 9007199254740993, str of 2.5, str of 3.0, str of (-9223372036854775807 - 1)] diff --git a/tests/phase2/builtins/builtins/str.pre b/tests/phase2/builtins/builtins/str.pre new file mode 100644 index 000000000..aa053e4f2 --- /dev/null +++ b/tests/phase2/builtins/builtins/str.pre @@ -0,0 +1,2 @@ +["9007199254740993", "2.5", "3", "-9223372036854775808"] +rc=0 diff --git a/tests/phase2/builtins/builtins/str.tag b/tests/phase2/builtins/builtins/str.tag new file mode 100644 index 000000000..77dc046e5 --- /dev/null +++ b/tests/phase2/builtins/builtins/str.tag @@ -0,0 +1,2 @@ +["9007199254740992", "2.5", "3", "-9.223372036854776e+18"] +rc=0 diff --git a/tests/phase2/builtins/builtins/str_from_bytes.branch b/tests/phase2/builtins/builtins/str_from_bytes.branch new file mode 100644 index 000000000..467a19cfe --- /dev/null +++ b/tests/phase2/builtins/builtins/str_from_bytes.branch @@ -0,0 +1,6 @@ +Hi +["byte-fraction", "value"] +["byte-328", "value"] +Hi +Hi +rc=0 diff --git a/tests/phase2/builtins/builtins/str_from_bytes.eigs b/tests/phase2/builtins/builtins/str_from_bytes.eigs new file mode 100644 index 000000000..fe484c145 --- /dev/null +++ b/tests/phase2/builtins/builtins/str_from_bytes.eigs @@ -0,0 +1,17 @@ +# R1.6 behaviour-table probe: str_from_bytes (int-requiring; byte values: N5, then 0..255 (Q1; Lua string.char)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (str_from_bytes of [72, 105]) +try: + print of ["fraction-answer", str_from_bytes of ([65.5])] +catch e: + print of ["byte-fraction", e.kind] +try: + print of ["328-answer", str_from_bytes of ([328])] +catch e: + print of ["byte-328", e.kind] +print of (str_from_bytes of [72.0, 105.0]) +print of (str_from_bytes of (buf_from_list of [72, 105])) diff --git a/tests/phase2/builtins/builtins/str_from_bytes.pre b/tests/phase2/builtins/builtins/str_from_bytes.pre new file mode 100644 index 000000000..43e190ead --- /dev/null +++ b/tests/phase2/builtins/builtins/str_from_bytes.pre @@ -0,0 +1,6 @@ +Hi +["fraction-answer", "A"] +["328-answer", "H"] +Hi +Hi +rc=0 diff --git a/tests/phase2/builtins/builtins/str_from_bytes.tag b/tests/phase2/builtins/builtins/str_from_bytes.tag new file mode 100644 index 000000000..43e190ead --- /dev/null +++ b/tests/phase2/builtins/builtins/str_from_bytes.tag @@ -0,0 +1,6 @@ +Hi +["fraction-answer", "A"] +["328-answer", "H"] +Hi +Hi +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_open.branch b/tests/phase2/builtins/builtins/stream_open.branch new file mode 100644 index 000000000..ab03a7b61 --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_open.branch @@ -0,0 +1,5 @@ +true +["count-fraction", "value"] +true +["count-zero", "limit"] +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_open.eigs b/tests/phase2/builtins/builtins/stream_open.eigs new file mode 100644 index 000000000..d4f7b9bfb --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_open.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: stream_open (int-requiring; element count, already floor-checked); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/s.tensor" +print of (stream_open of [f, 2]) +try: + print of ["fraction-answer", stream_open of [f, 2.5]] +catch e: + print of ["count-fraction", e.kind] +print of (stream_open of [f, 2.0]) +try: + print of ["zero-answer", stream_open of [f, 0]] +catch e: + print of ["count-zero", e.kind] +stream_close of null diff --git a/tests/phase2/builtins/builtins/stream_open.pre b/tests/phase2/builtins/builtins/stream_open.pre new file mode 100644 index 000000000..5169defaf --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_open.pre @@ -0,0 +1,5 @@ +true +["count-fraction", "limit"] +true +["count-zero", "limit"] +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_open.tag b/tests/phase2/builtins/builtins/stream_open.tag new file mode 100644 index 000000000..5169defaf --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_open.tag @@ -0,0 +1,5 @@ +true +["count-fraction", "limit"] +true +["count-zero", "limit"] +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_write.branch b/tests/phase2/builtins/builtins/stream_write.branch new file mode 100644 index 000000000..e9f52604b --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_write.branch @@ -0,0 +1,3 @@ +[true, true, true, true] +[[3.0, 2.5], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_write.eigs b/tests/phase2/builtins/builtins/stream_write.eigs new file mode 100644 index 000000000..b23d8275f --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_write.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: stream_write (float-producing; writes a raw f64, an int converts (Q2)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/s.tensor" +print of [stream_open of [f, 2], stream_write of 3, stream_write of 2.5, stream_close of null] +t is tensor_load of f +print of [t, nt of t[0], nt of t[1]] diff --git a/tests/phase2/builtins/builtins/stream_write.pre b/tests/phase2/builtins/builtins/stream_write.pre new file mode 100644 index 000000000..1dd436491 --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_write.pre @@ -0,0 +1,3 @@ +[true, true, true, true] +[[3, 2.5], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/stream_write.tag b/tests/phase2/builtins/builtins/stream_write.tag new file mode 100644 index 000000000..7cc440252 --- /dev/null +++ b/tests/phase2/builtins/builtins/stream_write.tag @@ -0,0 +1,3 @@ +[true, true, true, true] +[[3, 2.5], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/substr.branch b/tests/phase2/builtins/builtins/substr.branch new file mode 100644 index 000000000..e7bce11eb --- /dev/null +++ b/tests/phase2/builtins/builtins/substr.branch @@ -0,0 +1,5 @@ +el +["start-fraction", "value"] +["length-fraction", "value"] +el +rc=0 diff --git a/tests/phase2/builtins/builtins/substr.eigs b/tests/phase2/builtins/builtins/substr.eigs new file mode 100644 index 000000000..dbf39eea8 --- /dev/null +++ b/tests/phase2/builtins/builtins/substr.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: substr (int-requiring; start and length (Lua string.sub)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (substr of ["hello", 1, 2]) +try: + print of ["fraction-answer", substr of ["hello", 1.5, 2]] +catch e: + print of ["start-fraction", e.kind] +try: + print of ["fraction-answer", substr of ["hello", 1, 2.5]] +catch e: + print of ["length-fraction", e.kind] +print of (substr of ["hello", 1.0, 2.0]) diff --git a/tests/phase2/builtins/builtins/substr.pre b/tests/phase2/builtins/builtins/substr.pre new file mode 100644 index 000000000..1ef660994 --- /dev/null +++ b/tests/phase2/builtins/builtins/substr.pre @@ -0,0 +1,5 @@ +el +["fraction-answer", "el"] +["fraction-answer", "el"] +el +rc=0 diff --git a/tests/phase2/builtins/builtins/substr.tag b/tests/phase2/builtins/builtins/substr.tag new file mode 100644 index 000000000..1ef660994 --- /dev/null +++ b/tests/phase2/builtins/builtins/substr.tag @@ -0,0 +1,5 @@ +el +["fraction-answer", "el"] +["fraction-answer", "el"] +el +rc=0 diff --git a/tests/phase2/builtins/builtins/subtract.branch b/tests/phase2/builtins/builtins/subtract.branch new file mode 100644 index 000000000..12c85564e --- /dev/null +++ b/tests/phase2/builtins/builtins/subtract.branch @@ -0,0 +1,4 @@ +[9007199254740991, "int"] +["overflow", "value"] +[[3, 9007199254740992], "int", "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/subtract.eigs b/tests/phase2/builtins/builtins/subtract.eigs new file mode 100644 index 000000000..6e2d59b26 --- /dev/null +++ b/tests/phase2/builtins/builtins/subtract.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: subtract (int-preserving, LOSS; scalar arm mirrors -, (A)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is subtract of [9007199254740993, 2] +print of [r, nt of r] +try: + print of (subtract of [-9223372036854775807, 2]) +catch e: + print of ["overflow", e.kind] +l is subtract of [[5, 9007199254740993], [2, 1]] +print of [l, nt of l[0], nt of l[1]] diff --git a/tests/phase2/builtins/builtins/subtract.pre b/tests/phase2/builtins/builtins/subtract.pre new file mode 100644 index 000000000..734d777c1 --- /dev/null +++ b/tests/phase2/builtins/builtins/subtract.pre @@ -0,0 +1,4 @@ +[9007199254740990, "float"] +-9.223372036854776e+18 +[[3, 9007199254740991], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/subtract.tag b/tests/phase2/builtins/builtins/subtract.tag new file mode 100644 index 000000000..0294eec43 --- /dev/null +++ b/tests/phase2/builtins/builtins/subtract.tag @@ -0,0 +1,4 @@ +[9007199254740990, "undefined_name"] +-9.223372036854776e+18 +[[3, 9007199254740991], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sum.branch b/tests/phase2/builtins/builtins/sum.branch new file mode 100644 index 000000000..c0e6fd25c --- /dev/null +++ b/tests/phase2/builtins/builtins/sum.branch @@ -0,0 +1,6 @@ +[9007199254740994, "int"] +["overflow", "value", true] +[3.5, "float"] +[0, "int"] +[0, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sum.eigs b/tests/phase2/builtins/builtins/sum.eigs new file mode 100644 index 000000000..c1f268b0a --- /dev/null +++ b/tests/phase2/builtins/builtins/sum.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: sum (int-preserving, LOSS; all-int input sums exactly, (A)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is sum of [9007199254740993, 1] +print of [r, nt of r] +try: + print of (sum of [9223372036854775807, 1]) +catch e: + print of ["overflow", e.kind, contains of [e.message, "sum"]] +m is sum of [1, 2.5] +print of [m, nt of m] +e1 is sum of [] +print of [e1, nt of e1] +e2 is sum of ([]) +print of [e2, nt of e2] diff --git a/tests/phase2/builtins/builtins/sum.pre b/tests/phase2/builtins/builtins/sum.pre new file mode 100644 index 000000000..fe2def6bc --- /dev/null +++ b/tests/phase2/builtins/builtins/sum.pre @@ -0,0 +1,6 @@ +[9007199254740992, "float"] +9.223372036854776e+18 +[3.5, "float"] +[0, "float"] +[0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/sum.tag b/tests/phase2/builtins/builtins/sum.tag new file mode 100644 index 000000000..0b5e45b85 --- /dev/null +++ b/tests/phase2/builtins/builtins/sum.tag @@ -0,0 +1,6 @@ +[9007199254740992, "undefined_name"] +9.223372036854776e+18 +[3.5, "undefined_name"] +[0, "undefined_name"] +[0, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tan.branch b/tests/phase2/builtins/builtins/tan.branch new file mode 100644 index 000000000..384ef45af --- /dev/null +++ b/tests/phase2/builtins/builtins/tan.branch @@ -0,0 +1,3 @@ +[0.0, "float"] +[1.5574077246549023, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tan.eigs b/tests/phase2/builtins/builtins/tan.eigs new file mode 100644 index 000000000..36891481e --- /dev/null +++ b/tests/phase2/builtins/builtins/tan.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: tan (float-producing; Lua math.tan) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is tan of 0 +print of [a, nt of a] +b is tan of 1 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/tan.pre b/tests/phase2/builtins/builtins/tan.pre new file mode 100644 index 000000000..f49e83313 --- /dev/null +++ b/tests/phase2/builtins/builtins/tan.pre @@ -0,0 +1,3 @@ +[0, "float"] +[1.5574077246549023, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tan.tag b/tests/phase2/builtins/builtins/tan.tag new file mode 100644 index 000000000..f9fb6a4b2 --- /dev/null +++ b/tests/phase2/builtins/builtins/tan.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[1.5574077246549023, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_alive.branch b/tests/phase2/builtins/builtins/task_alive.branch new file mode 100644 index 000000000..73f3d15a6 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_alive.branch @@ -0,0 +1,4 @@ +["id-fraction", "value"] +true +[42, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_alive.eigs b/tests/phase2/builtins/builtins/task_alive.eigs new file mode 100644 index 000000000..79a211f8f --- /dev/null +++ b/tests/phase2/builtins/builtins/task_alive.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: task_alive (int-requiring; task handle (packed below 2^53, exactness-checked)); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x * 2 +t is task_spawn of [w, 21] +try: + print of ["fraction-answer", task_alive of (t + 0.5)] +catch e: + print of ["id-fraction", e.kind] +print of (task_alive of (t + 0.0)) +r is task_join of t +print of [r, task_alive of t] diff --git a/tests/phase2/builtins/builtins/task_alive.pre b/tests/phase2/builtins/builtins/task_alive.pre new file mode 100644 index 000000000..932bdf07a --- /dev/null +++ b/tests/phase2/builtins/builtins/task_alive.pre @@ -0,0 +1,4 @@ +["fraction-answer", false] +true +[42, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_alive.tag b/tests/phase2/builtins/builtins/task_alive.tag new file mode 100644 index 000000000..932bdf07a --- /dev/null +++ b/tests/phase2/builtins/builtins/task_alive.tag @@ -0,0 +1,4 @@ +["fraction-answer", false] +true +[42, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_detach.branch b/tests/phase2/builtins/builtins/task_detach.branch new file mode 100644 index 000000000..9e37a157d --- /dev/null +++ b/tests/phase2/builtins/builtins/task_detach.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[1, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_detach.eigs b/tests/phase2/builtins/builtins/task_detach.eigs new file mode 100644 index 000000000..658a8f10b --- /dev/null +++ b/tests/phase2/builtins/builtins/task_detach.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: task_detach (int-requiring; task handle); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x * 2 +t is task_spawn of [w, 21] +try: + print of ["fraction-answer", task_detach of (t + 0.5)] +catch e: + print of ["id-fraction", e.kind] +d is task_detach of (t + 0.0) +print of [d, nt of d] diff --git a/tests/phase2/builtins/builtins/task_detach.pre b/tests/phase2/builtins/builtins/task_detach.pre new file mode 100644 index 000000000..5da1c6743 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_detach.pre @@ -0,0 +1,3 @@ +["fraction-answer", 0] +[1, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_detach.tag b/tests/phase2/builtins/builtins/task_detach.tag new file mode 100644 index 000000000..120b07c06 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_detach.tag @@ -0,0 +1,3 @@ +["fraction-answer", 0] +[1, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_join.branch b/tests/phase2/builtins/builtins/task_join.branch new file mode 100644 index 000000000..bab30910f --- /dev/null +++ b/tests/phase2/builtins/builtins/task_join.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_join.eigs b/tests/phase2/builtins/builtins/task_join.eigs new file mode 100644 index 000000000..2e46c5370 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_join.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: task_join (int-requiring; task handle); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x * 2 +t is task_spawn of [w, 21] +try: + print of ["fraction-answer", task_join of (t + 0.5)] +catch e: + print of ["id-fraction", e.kind] +r is task_join of (t + 0.0) +print of [r, nt of r] diff --git a/tests/phase2/builtins/builtins/task_join.pre b/tests/phase2/builtins/builtins/task_join.pre new file mode 100644 index 000000000..a3aeb1b2b --- /dev/null +++ b/tests/phase2/builtins/builtins/task_join.pre @@ -0,0 +1,3 @@ +["fraction-answer", null] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_join.tag b/tests/phase2/builtins/builtins/task_join.tag new file mode 100644 index 000000000..1aae61fdd --- /dev/null +++ b/tests/phase2/builtins/builtins/task_join.tag @@ -0,0 +1,3 @@ +["fraction-answer", null] +[42, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_kill.branch b/tests/phase2/builtins/builtins/task_kill.branch new file mode 100644 index 000000000..d836de842 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_kill.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[true, true, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_kill.eigs b/tests/phase2/builtins/builtins/task_kill.eigs new file mode 100644 index 000000000..461161920 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_kill.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: task_kill (int-requiring; task handle); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x * 2 +t is task_spawn of [w, 21] +try: + print of ["fraction-answer", task_kill of (t + 0.5)] +catch e: + print of ["id-fraction", e.kind] +print of [task_alive of t, task_kill of (t + 0.0), task_alive of t] diff --git a/tests/phase2/builtins/builtins/task_kill.pre b/tests/phase2/builtins/builtins/task_kill.pre new file mode 100644 index 000000000..ea42754f0 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_kill.pre @@ -0,0 +1,3 @@ +["fraction-answer", false] +[true, true, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_kill.tag b/tests/phase2/builtins/builtins/task_kill.tag new file mode 100644 index 000000000..ea42754f0 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_kill.tag @@ -0,0 +1,3 @@ +["fraction-answer", false] +[true, true, false] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sched_seed.branch b/tests/phase2/builtins/builtins/task_sched_seed.branch new file mode 100644 index 000000000..7cb4c4c25 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sched_seed.branch @@ -0,0 +1,4 @@ +["seeds-2^53-and-2^53+1-differ", true] +["fraction", "value"] +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sched_seed.eigs b/tests/phase2/builtins/builtins/task_sched_seed.eigs new file mode 100644 index 000000000..02a8ab99d --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sched_seed.eigs @@ -0,0 +1,31 @@ +# R1.6 behaviour-table probe: task_sched_seed (int-requiring, LOSS; (int64_t) of a double); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define worker(i) as: + task_send of [0, i] + return i +define order_for(seed) as: + task_sched_seed of seed + local ts is [] + for i in range of 3: + append of [ts, task_spawn of [worker, i]] + local got is [] + for i in range of 3: + append of [got, task_recv of null] + for t in ts: + task_join of t + return got +a is order_for of 9007199254740992 +b is order_for of 9007199254740993 +print of ["seeds-2^53-and-2^53+1-differ", a != b] +try: + task_sched_seed of 1.5 + print of "fraction accepted" +catch e: + print of ["fraction", e.kind] +c is order_for of 5.0 +d is order_for of 5 +print of ["integral-float-seed", c == d] diff --git a/tests/phase2/builtins/builtins/task_sched_seed.pre b/tests/phase2/builtins/builtins/task_sched_seed.pre new file mode 100644 index 000000000..36c2e1408 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sched_seed.pre @@ -0,0 +1,4 @@ +["seeds-2^53-and-2^53+1-differ", false] +fraction accepted +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sched_seed.tag b/tests/phase2/builtins/builtins/task_sched_seed.tag new file mode 100644 index 000000000..36c2e1408 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sched_seed.tag @@ -0,0 +1,4 @@ +["seeds-2^53-and-2^53+1-differ", false] +fraction accepted +["integral-float-seed", true] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_self.branch b/tests/phase2/builtins/builtins/task_self.branch new file mode 100644 index 000000000..9d2fe4bee --- /dev/null +++ b/tests/phase2/builtins/builtins/task_self.branch @@ -0,0 +1,3 @@ +[0, "int"] +[true, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_self.eigs b/tests/phase2/builtins/builtins/task_self.eigs new file mode 100644 index 000000000..6adf734e1 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_self.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: task_self (int-preserving; task handle) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +s is task_self of null +print of [s, nt of s] +define w(x) as: + return task_self of null +t is task_spawn of [w, 0] +r is task_join of t +print of [r == t, nt of r] diff --git a/tests/phase2/builtins/builtins/task_self.pre b/tests/phase2/builtins/builtins/task_self.pre new file mode 100644 index 000000000..6079ab924 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_self.pre @@ -0,0 +1,3 @@ +[0, "float"] +[true, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_self.tag b/tests/phase2/builtins/builtins/task_self.tag new file mode 100644 index 000000000..b662b0cb1 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_self.tag @@ -0,0 +1,3 @@ +[0, "undefined_name"] +[true, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_send.branch b/tests/phase2/builtins/builtins/task_send.branch new file mode 100644 index 000000000..46fe9a1a3 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_send.branch @@ -0,0 +1,4 @@ +["id-fraction", "value"] +true +hello +rc=0 diff --git a/tests/phase2/builtins/builtins/task_send.eigs b/tests/phase2/builtins/builtins/task_send.eigs new file mode 100644 index 000000000..d5baa40b0 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_send.eigs @@ -0,0 +1,16 @@ +# R1.6 behaviour-table probe: task_send (int-requiring; task handle); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + local m is task_recv of null + return m +t is task_spawn of [w, 0] +try: + print of ["fraction-answer", task_send of [t + 0.5, "lost"]] +catch e: + print of ["id-fraction", e.kind] +print of (task_send of [t + 0.0, "hello"]) +print of (task_join of t) diff --git a/tests/phase2/builtins/builtins/task_send.pre b/tests/phase2/builtins/builtins/task_send.pre new file mode 100644 index 000000000..21c4d4336 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_send.pre @@ -0,0 +1,4 @@ +["fraction-answer", false] +true +hello +rc=0 diff --git a/tests/phase2/builtins/builtins/task_send.tag b/tests/phase2/builtins/builtins/task_send.tag new file mode 100644 index 000000000..21c4d4336 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_send.tag @@ -0,0 +1,4 @@ +["fraction-answer", false] +true +hello +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sleep.branch b/tests/phase2/builtins/builtins/task_sleep.branch new file mode 100644 index 000000000..aee59456b --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sleep.branch @@ -0,0 +1,2 @@ +["slept-2", "slept-2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sleep.eigs b/tests/phase2/builtins/builtins/task_sleep.eigs new file mode 100644 index 000000000..427a39f49 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sleep.eigs @@ -0,0 +1,15 @@ +# R1.6 behaviour-table probe: task_sleep (other; virtual milliseconds kept as a float, no C integer conversion) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define sleeper(args) as: + task_sleep of args[0] + task_send of [0, args[1]] + return 0 +a is task_spawn of [sleeper, [2.5, "slept-2.5"]] +b is task_spawn of [sleeper, [2, "slept-2"]] +r1 is task_recv of null +r2 is task_recv of null +print of [r1, r2] diff --git a/tests/phase2/builtins/builtins/task_sleep.pre b/tests/phase2/builtins/builtins/task_sleep.pre new file mode 100644 index 000000000..aee59456b --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sleep.pre @@ -0,0 +1,2 @@ +["slept-2", "slept-2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_sleep.tag b/tests/phase2/builtins/builtins/task_sleep.tag new file mode 100644 index 000000000..aee59456b --- /dev/null +++ b/tests/phase2/builtins/builtins/task_sleep.tag @@ -0,0 +1,2 @@ +["slept-2", "slept-2.5"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_spawn.branch b/tests/phase2/builtins/builtins/task_spawn.branch new file mode 100644 index 000000000..3db451342 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_spawn.branch @@ -0,0 +1,3 @@ +[true, "int"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_spawn.eigs b/tests/phase2/builtins/builtins/task_spawn.eigs new file mode 100644 index 000000000..80a5b73a5 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_spawn.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: task_spawn (int-preserving; returns a task handle) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x * 2 +t is task_spawn of [w, 21] +print of [t > 0, nt of t] +r is task_join of t +print of [r, nt of r] diff --git a/tests/phase2/builtins/builtins/task_spawn.pre b/tests/phase2/builtins/builtins/task_spawn.pre new file mode 100644 index 000000000..da5c85295 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_spawn.pre @@ -0,0 +1,3 @@ +[true, "float"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/task_spawn.tag b/tests/phase2/builtins/builtins/task_spawn.tag new file mode 100644 index 000000000..8ce70be78 --- /dev/null +++ b/tests/phase2/builtins/builtins/task_spawn.tag @@ -0,0 +1,3 @@ +[true, "undefined_name"] +[42, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_load.branch b/tests/phase2/builtins/builtins/tensor_load.branch new file mode 100644 index 000000000..2c5512484 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_load.branch @@ -0,0 +1,2 @@ +[[[1.0, 2.0], [3.0, 4.0]], "float", [2, 2]] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_load.eigs b/tests/phase2/builtins/builtins/tensor_load.eigs new file mode 100644 index 000000000..5ea3306ab --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_load.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: tensor_load (float-producing; float tensor; shape ints) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/t.bin" +tensor_save of [[[1, 2], [3, 4]], f] +t is tensor_load of f +print of [t, nt of t[0][0], shape of t] diff --git a/tests/phase2/builtins/builtins/tensor_load.pre b/tests/phase2/builtins/builtins/tensor_load.pre new file mode 100644 index 000000000..bbc9be316 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_load.pre @@ -0,0 +1,2 @@ +[[[1, 2], [3, 4]], "float", [2, 2]] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_load.tag b/tests/phase2/builtins/builtins/tensor_load.tag new file mode 100644 index 000000000..03ffb629f --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_load.tag @@ -0,0 +1,2 @@ +[[[1, 2], [3, 4]], "undefined_name", [2, 2]] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_save.branch b/tests/phase2/builtins/builtins/tensor_save.branch new file mode 100644 index 000000000..b239c2f69 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_save.branch @@ -0,0 +1,3 @@ +true +[[1.0, 9007199254740992.0, 2.5], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_save.eigs b/tests/phase2/builtins/builtins/tensor_save.eigs new file mode 100644 index 000000000..2ae16b675 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_save.eigs @@ -0,0 +1,11 @@ +# R1.6 behaviour-table probe: tensor_save (float-producing; writes float cells) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/t.bin" +print of (tensor_save of [[1, 9007199254740993, 2.5], f]) +t is tensor_load of f +print of [t, nt of t[0], nt of t[1]] diff --git a/tests/phase2/builtins/builtins/tensor_save.pre b/tests/phase2/builtins/builtins/tensor_save.pre new file mode 100644 index 000000000..48e2ab899 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_save.pre @@ -0,0 +1,3 @@ +true +[[1, 9007199254740992, 2.5], "float", "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/tensor_save.tag b/tests/phase2/builtins/builtins/tensor_save.tag new file mode 100644 index 000000000..f58257d51 --- /dev/null +++ b/tests/phase2/builtins/builtins/tensor_save.tag @@ -0,0 +1,3 @@ +true +[[1, 9007199254740992, 2.5], "undefined_name", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append.branch b/tests/phase2/builtins/builtins/text_builder_append.branch new file mode 100644 index 000000000..594c0a55f --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append.branch @@ -0,0 +1,2 @@ +9007199254740993|2.5|3.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append.eigs b/tests/phase2/builtins/builtins/text_builder_append.eigs new file mode 100644 index 000000000..56f8e7c6e --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append.eigs @@ -0,0 +1,13 @@ +# R1.6 behaviour-table probe: text_builder_append (other; formats via value_to_string (N6)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +tb is text_builder_new of null +text_builder_append of [tb, 9007199254740993] +text_builder_append of [tb, "|"] +text_builder_append of [tb, 2.5] +text_builder_append of [tb, "|"] +text_builder_append of [tb, 3.0] +print of (text_builder_to_string of tb) diff --git a/tests/phase2/builtins/builtins/text_builder_append.pre b/tests/phase2/builtins/builtins/text_builder_append.pre new file mode 100644 index 000000000..da3da8a33 --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append.pre @@ -0,0 +1,2 @@ +9007199254740993|2.5|3 +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append.tag b/tests/phase2/builtins/builtins/text_builder_append.tag new file mode 100644 index 000000000..ffc48078c --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append.tag @@ -0,0 +1,2 @@ +9007199254740992|2.5|3 +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append_line.branch b/tests/phase2/builtins/builtins/text_builder_append_line.branch new file mode 100644 index 000000000..dfd831881 --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append_line.branch @@ -0,0 +1,3 @@ +9007199254740993 +2.5 +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append_line.eigs b/tests/phase2/builtins/builtins/text_builder_append_line.eigs new file mode 100644 index 000000000..dbbbe3d03 --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append_line.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: text_builder_append_line (other; formats via value_to_string (N6)) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +tb is text_builder_new of null +text_builder_append_line of [tb, 9007199254740993] +text_builder_append_line of [tb, 2.5] +write of (text_builder_to_string of tb) diff --git a/tests/phase2/builtins/builtins/text_builder_append_line.pre b/tests/phase2/builtins/builtins/text_builder_append_line.pre new file mode 100644 index 000000000..dfd831881 --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append_line.pre @@ -0,0 +1,3 @@ +9007199254740993 +2.5 +rc=0 diff --git a/tests/phase2/builtins/builtins/text_builder_append_line.tag b/tests/phase2/builtins/builtins/text_builder_append_line.tag new file mode 100644 index 000000000..473054735 --- /dev/null +++ b/tests/phase2/builtins/builtins/text_builder_append_line.tag @@ -0,0 +1,3 @@ +9007199254740992 +2.5 +rc=0 diff --git a/tests/phase2/builtins/builtins/thread_join.branch b/tests/phase2/builtins/builtins/thread_join.branch new file mode 100644 index 000000000..bab30910f --- /dev/null +++ b/tests/phase2/builtins/builtins/thread_join.branch @@ -0,0 +1,3 @@ +["id-fraction", "value"] +[42, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/thread_join.eigs b/tests/phase2/builtins/builtins/thread_join.eigs new file mode 100644 index 000000000..28712d279 --- /dev/null +++ b/tests/phase2/builtins/builtins/thread_join.eigs @@ -0,0 +1,18 @@ +# R1.6 behaviour-table probe: thread_join (int-requiring; thread handle id/gen); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +define w(x) as: + return x + 1 +h is spawn of [w, 41] +try: + print of ["fraction-answer", thread_join of {"_handle_id": h["_handle_id"] + 0.5, "_handle_gen": h["_handle_gen"], "done": 0}] +catch e: + print of ["id-fraction", e.kind] +try: + r is thread_join of {"_handle_id": h["_handle_id"] + 0.0, "_handle_gen": h["_handle_gen"] + 0.0, "done": 0} + print of [r, nt of r] +catch e: + print of ["join", e.kind] diff --git a/tests/phase2/builtins/builtins/thread_join.pre b/tests/phase2/builtins/builtins/thread_join.pre new file mode 100644 index 000000000..9cb625c33 --- /dev/null +++ b/tests/phase2/builtins/builtins/thread_join.pre @@ -0,0 +1,3 @@ +["fraction-answer", 42] +["join", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/thread_join.tag b/tests/phase2/builtins/builtins/thread_join.tag new file mode 100644 index 000000000..9cb625c33 --- /dev/null +++ b/tests/phase2/builtins/builtins/thread_join.tag @@ -0,0 +1,3 @@ +["fraction-answer", 42] +["join", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/throw.branch b/tests/phase2/builtins/builtins/throw.branch new file mode 100644 index 000000000..8e213e343 --- /dev/null +++ b/tests/phase2/builtins/builtins/throw.branch @@ -0,0 +1,3 @@ +[9007199254740993, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/throw.eigs b/tests/phase2/builtins/builtins/throw.eigs new file mode 100644 index 000000000..8fdb7964f --- /dev/null +++ b/tests/phase2/builtins/builtins/throw.eigs @@ -0,0 +1,14 @@ +# R1.6 behaviour-table probe: throw (other; message via value_to_string) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +try: + throw of 9007199254740993 +catch e: + print of [e, nt of e] +try: + throw of 2.5 +catch e: + print of [e, nt of e] diff --git a/tests/phase2/builtins/builtins/throw.pre b/tests/phase2/builtins/builtins/throw.pre new file mode 100644 index 000000000..8e213e343 --- /dev/null +++ b/tests/phase2/builtins/builtins/throw.pre @@ -0,0 +1,3 @@ +[9007199254740993, "int"] +[2.5, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/throw.tag b/tests/phase2/builtins/builtins/throw.tag new file mode 100644 index 000000000..63acdf9a7 --- /dev/null +++ b/tests/phase2/builtins/builtins/throw.tag @@ -0,0 +1,3 @@ +[9007199254740992, "undefined_name"] +[2.5, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/token_name.branch b/tests/phase2/builtins/builtins/token_name.branch new file mode 100644 index 000000000..2e62488b9 --- /dev/null +++ b/tests/phase2/builtins/builtins/token_name.branch @@ -0,0 +1,4 @@ +STR +["id-fraction", "value"] +STR +rc=0 diff --git a/tests/phase2/builtins/builtins/token_name.eigs b/tests/phase2/builtins/builtins/token_name.eigs new file mode 100644 index 000000000..6a1885548 --- /dev/null +++ b/tests/phase2/builtins/builtins/token_name.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: token_name (int-requiring; token id); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":3}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (token_name of 1) +try: + print of ["fraction-answer", token_name of 1.5] +catch e: + print of ["id-fraction", e.kind] +print of (token_name of 1.0) diff --git a/tests/phase2/builtins/builtins/token_name.pre b/tests/phase2/builtins/builtins/token_name.pre new file mode 100644 index 000000000..6289aa0ac --- /dev/null +++ b/tests/phase2/builtins/builtins/token_name.pre @@ -0,0 +1,4 @@ +STR +["fraction-answer", "STR"] +STR +rc=0 diff --git a/tests/phase2/builtins/builtins/token_name.tag b/tests/phase2/builtins/builtins/token_name.tag new file mode 100644 index 000000000..6289aa0ac --- /dev/null +++ b/tests/phase2/builtins/builtins/token_name.tag @@ -0,0 +1,4 @@ +STR +["fraction-answer", "STR"] +STR +rc=0 diff --git a/tests/phase2/builtins/builtins/try_recv.branch b/tests/phase2/builtins/builtins/try_recv.branch new file mode 100644 index 000000000..bf6f7bc5f --- /dev/null +++ b/tests/phase2/builtins/builtins/try_recv.branch @@ -0,0 +1,5 @@ +["id-fraction", "value"] +[5, "int"] +6 +null +rc=0 diff --git a/tests/phase2/builtins/builtins/try_recv.eigs b/tests/phase2/builtins/builtins/try_recv.eigs new file mode 100644 index 000000000..4679e3679 --- /dev/null +++ b/tests/phase2/builtins/builtins/try_recv.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: try_recv (int-requiring; channel handle ids); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":2}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +c is channel of null +id is c["_channel_id"] +gen is c["_channel_gen"] +send of [c, 5] +send of [c, 6] +try: + print of ["fraction-answer", try_recv of {"_channel_id": id + 0.5, "_channel_gen": gen}] +catch e: + print of ["id-fraction", e.kind] +v is try_recv of {"_channel_id": id + 0.0, "_channel_gen": gen + 0.0} +print of [v, nt of v] +print of (try_recv of c) +print of (try_recv of c) diff --git a/tests/phase2/builtins/builtins/try_recv.pre b/tests/phase2/builtins/builtins/try_recv.pre new file mode 100644 index 000000000..f8200b637 --- /dev/null +++ b/tests/phase2/builtins/builtins/try_recv.pre @@ -0,0 +1,5 @@ +["fraction-answer", 5] +[6, "int"] +null +null +rc=0 diff --git a/tests/phase2/builtins/builtins/try_recv.tag b/tests/phase2/builtins/builtins/try_recv.tag new file mode 100644 index 000000000..e0336aa43 --- /dev/null +++ b/tests/phase2/builtins/builtins/try_recv.tag @@ -0,0 +1,5 @@ +["fraction-answer", 5] +[6, "undefined_name"] +null +null +rc=0 diff --git a/tests/phase2/builtins/builtins/type.branch b/tests/phase2/builtins/builtins/type.branch new file mode 100644 index 000000000..7486a67a1 --- /dev/null +++ b/tests/phase2/builtins/builtins/type.branch @@ -0,0 +1,2 @@ +["num", "num", "num", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/type.eigs b/tests/phase2/builtins/builtins/type.eigs new file mode 100644 index 000000000..f5f76c25f --- /dev/null +++ b/tests/phase2/builtins/builtins/type.eigs @@ -0,0 +1,7 @@ +# R1.6 behaviour-table probe: type (other; type of an int or a float is num ((B))) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of [type of 3, type of 3.0, type of 9007199254740993, type of "3"] diff --git a/tests/phase2/builtins/builtins/type.pre b/tests/phase2/builtins/builtins/type.pre new file mode 100644 index 000000000..7486a67a1 --- /dev/null +++ b/tests/phase2/builtins/builtins/type.pre @@ -0,0 +1,2 @@ +["num", "num", "num", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/type.tag b/tests/phase2/builtins/builtins/type.tag new file mode 100644 index 000000000..7486a67a1 --- /dev/null +++ b/tests/phase2/builtins/builtins/type.tag @@ -0,0 +1,2 @@ +["num", "num", "num", "str"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_add.branch b/tests/phase2/builtins/builtins/u16_checked_add.branch new file mode 100644 index 000000000..920fa1906 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_add.eigs b/tests/phase2/builtins/builtins/u16_checked_add.eigs new file mode 100644 index 000000000..33734f175 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_checked_add"]) >= 0] +try: + x is u16_checked_add of [65535, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_checked_add"]) >= 0] +try: + x is u16_checked_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_checked_add"]) >= 0] +try: + x is u16_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_checked_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_checked_add of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_checked_add.pre b/tests/phase2/builtins/builtins/u16_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_add.tag b/tests/phase2/builtins/builtins/u16_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_mul.branch b/tests/phase2/builtins/builtins/u16_checked_mul.branch new file mode 100644 index 000000000..108307400 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_mul.eigs b/tests/phase2/builtins/builtins/u16_checked_mul.eigs new file mode 100644 index 000000000..d04f01525 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_checked_mul"]) >= 0] +try: + x is u16_checked_mul of [65535, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_checked_mul"]) >= 0] +try: + x is u16_checked_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_checked_mul"]) >= 0] +try: + x is u16_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_checked_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_checked_mul of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_checked_mul.pre b/tests/phase2/builtins/builtins/u16_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_mul.tag b/tests/phase2/builtins/builtins/u16_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_sub.branch b/tests/phase2/builtins/builtins/u16_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_sub.eigs b/tests/phase2/builtins/builtins/u16_checked_sub.eigs new file mode 100644 index 000000000..f45fed8b4 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_checked_sub"]) >= 0] +try: + x is u16_checked_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_checked_sub"]) >= 0] +try: + x is u16_checked_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_checked_sub"]) >= 0] +try: + x is u16_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_checked_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_checked_sub of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_checked_sub.pre b/tests/phase2/builtins/builtins/u16_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_checked_sub.tag b/tests/phase2/builtins/builtins/u16_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_add.branch b/tests/phase2/builtins/builtins/u16_saturating_add.branch new file mode 100644 index 000000000..171a4c4ad --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[65535, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_add.eigs b/tests/phase2/builtins/builtins/u16_saturating_add.eigs new file mode 100644 index 000000000..04775ac29 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_saturating_add"]) >= 0] +try: + x is u16_saturating_add of [65535, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_saturating_add"]) >= 0] +try: + x is u16_saturating_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_saturating_add"]) >= 0] +try: + x is u16_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_saturating_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_saturating_add of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_saturating_add.pre b/tests/phase2/builtins/builtins/u16_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_add.tag b/tests/phase2/builtins/builtins/u16_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_mul.branch b/tests/phase2/builtins/builtins/u16_saturating_mul.branch new file mode 100644 index 000000000..3cace8fa6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[65535, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_mul.eigs b/tests/phase2/builtins/builtins/u16_saturating_mul.eigs new file mode 100644 index 000000000..553f70515 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_saturating_mul"]) >= 0] +try: + x is u16_saturating_mul of [65535, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_saturating_mul"]) >= 0] +try: + x is u16_saturating_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_saturating_mul"]) >= 0] +try: + x is u16_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_saturating_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_saturating_mul of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_saturating_mul.pre b/tests/phase2/builtins/builtins/u16_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_mul.tag b/tests/phase2/builtins/builtins/u16_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_sub.branch b/tests/phase2/builtins/builtins/u16_saturating_sub.branch new file mode 100644 index 000000000..c5dafd985 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[0, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_sub.eigs b/tests/phase2/builtins/builtins/u16_saturating_sub.eigs new file mode 100644 index 000000000..379786f2b --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_saturating_sub"]) >= 0] +try: + x is u16_saturating_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_saturating_sub"]) >= 0] +try: + x is u16_saturating_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_saturating_sub"]) >= 0] +try: + x is u16_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_saturating_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_saturating_sub of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_saturating_sub.pre b/tests/phase2/builtins/builtins/u16_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_saturating_sub.tag b/tests/phase2/builtins/builtins/u16_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_add.branch b/tests/phase2/builtins/builtins/u16_wrapping_add.branch new file mode 100644 index 000000000..eaabc7db6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[0, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_add.eigs b/tests/phase2/builtins/builtins/u16_wrapping_add.eigs new file mode 100644 index 000000000..84f900b54 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_wrapping_add"]) >= 0] +try: + x is u16_wrapping_add of [65535, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_wrapping_add"]) >= 0] +try: + x is u16_wrapping_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_wrapping_add"]) >= 0] +try: + x is u16_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_wrapping_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_wrapping_add of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_wrapping_add.pre b/tests/phase2/builtins/builtins/u16_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_add.tag b/tests/phase2/builtins/builtins/u16_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_mul.branch b/tests/phase2/builtins/builtins/u16_wrapping_mul.branch new file mode 100644 index 000000000..583e03db5 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[65534, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_mul.eigs b/tests/phase2/builtins/builtins/u16_wrapping_mul.eigs new file mode 100644 index 000000000..d41e6a184 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_wrapping_mul"]) >= 0] +try: + x is u16_wrapping_mul of [65535, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_wrapping_mul"]) >= 0] +try: + x is u16_wrapping_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_wrapping_mul"]) >= 0] +try: + x is u16_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_wrapping_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_wrapping_mul of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_wrapping_mul.pre b/tests/phase2/builtins/builtins/u16_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_mul.tag b/tests/phase2/builtins/builtins/u16_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_sub.branch b/tests/phase2/builtins/builtins/u16_wrapping_sub.branch new file mode 100644 index 000000000..189fef46e --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[65535, "int"] +[65534, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_sub.eigs b/tests/phase2/builtins/builtins/u16_wrapping_sub.eigs new file mode 100644 index 000000000..a80059159 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u16_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u16_wrapping_sub"]) >= 0] +try: + x is u16_wrapping_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u16_wrapping_sub"]) >= 0] +try: + x is u16_wrapping_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u16_wrapping_sub"]) >= 0] +try: + x is u16_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u16_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u16_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u16_wrapping_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u16_wrapping_sub of [1, 65536] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u16_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u16_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u16_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u16_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u16_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u16_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u16_wrapping_sub.pre b/tests/phase2/builtins/builtins/u16_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u16_wrapping_sub.tag b/tests/phase2/builtins/builtins/u16_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u16_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_add.branch b/tests/phase2/builtins/builtins/u32_checked_add.branch new file mode 100644 index 000000000..920fa1906 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_add.eigs b/tests/phase2/builtins/builtins/u32_checked_add.eigs new file mode 100644 index 000000000..e7696d35d --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_checked_add"]) >= 0] +try: + x is u32_checked_add of [4294967295, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_checked_add"]) >= 0] +try: + x is u32_checked_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_checked_add"]) >= 0] +try: + x is u32_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_checked_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_checked_add of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_checked_add.pre b/tests/phase2/builtins/builtins/u32_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_add.tag b/tests/phase2/builtins/builtins/u32_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_mul.branch b/tests/phase2/builtins/builtins/u32_checked_mul.branch new file mode 100644 index 000000000..108307400 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_mul.eigs b/tests/phase2/builtins/builtins/u32_checked_mul.eigs new file mode 100644 index 000000000..d02016fbf --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_checked_mul"]) >= 0] +try: + x is u32_checked_mul of [4294967295, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_checked_mul"]) >= 0] +try: + x is u32_checked_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_checked_mul"]) >= 0] +try: + x is u32_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_checked_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_checked_mul of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_checked_mul.pre b/tests/phase2/builtins/builtins/u32_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_mul.tag b/tests/phase2/builtins/builtins/u32_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_sub.branch b/tests/phase2/builtins/builtins/u32_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_sub.eigs b/tests/phase2/builtins/builtins/u32_checked_sub.eigs new file mode 100644 index 000000000..23086544f --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_checked_sub"]) >= 0] +try: + x is u32_checked_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_checked_sub"]) >= 0] +try: + x is u32_checked_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_checked_sub"]) >= 0] +try: + x is u32_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_checked_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_checked_sub of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_checked_sub.pre b/tests/phase2/builtins/builtins/u32_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_checked_sub.tag b/tests/phase2/builtins/builtins/u32_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_add.branch b/tests/phase2/builtins/builtins/u32_saturating_add.branch new file mode 100644 index 000000000..9181f2367 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[4294967295, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_add.eigs b/tests/phase2/builtins/builtins/u32_saturating_add.eigs new file mode 100644 index 000000000..613418e6b --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_saturating_add"]) >= 0] +try: + x is u32_saturating_add of [4294967295, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_saturating_add"]) >= 0] +try: + x is u32_saturating_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_saturating_add"]) >= 0] +try: + x is u32_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_saturating_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_saturating_add of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_saturating_add.pre b/tests/phase2/builtins/builtins/u32_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_add.tag b/tests/phase2/builtins/builtins/u32_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_mul.branch b/tests/phase2/builtins/builtins/u32_saturating_mul.branch new file mode 100644 index 000000000..ce594c744 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[4294967295, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_mul.eigs b/tests/phase2/builtins/builtins/u32_saturating_mul.eigs new file mode 100644 index 000000000..92c175198 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_saturating_mul"]) >= 0] +try: + x is u32_saturating_mul of [4294967295, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_saturating_mul"]) >= 0] +try: + x is u32_saturating_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_saturating_mul"]) >= 0] +try: + x is u32_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_saturating_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_saturating_mul of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_saturating_mul.pre b/tests/phase2/builtins/builtins/u32_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_mul.tag b/tests/phase2/builtins/builtins/u32_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_sub.branch b/tests/phase2/builtins/builtins/u32_saturating_sub.branch new file mode 100644 index 000000000..c5dafd985 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[0, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_sub.eigs b/tests/phase2/builtins/builtins/u32_saturating_sub.eigs new file mode 100644 index 000000000..4bd966d5e --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_saturating_sub"]) >= 0] +try: + x is u32_saturating_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_saturating_sub"]) >= 0] +try: + x is u32_saturating_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_saturating_sub"]) >= 0] +try: + x is u32_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_saturating_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_saturating_sub of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_saturating_sub.pre b/tests/phase2/builtins/builtins/u32_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_saturating_sub.tag b/tests/phase2/builtins/builtins/u32_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_add.branch b/tests/phase2/builtins/builtins/u32_wrapping_add.branch new file mode 100644 index 000000000..eaabc7db6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[0, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_add.eigs b/tests/phase2/builtins/builtins/u32_wrapping_add.eigs new file mode 100644 index 000000000..28f5a1ee8 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_wrapping_add"]) >= 0] +try: + x is u32_wrapping_add of [4294967295, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_wrapping_add"]) >= 0] +try: + x is u32_wrapping_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_wrapping_add"]) >= 0] +try: + x is u32_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_wrapping_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_wrapping_add of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_wrapping_add.pre b/tests/phase2/builtins/builtins/u32_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_add.tag b/tests/phase2/builtins/builtins/u32_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_mul.branch b/tests/phase2/builtins/builtins/u32_wrapping_mul.branch new file mode 100644 index 000000000..34ef1c5c5 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[4294967294, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_mul.eigs b/tests/phase2/builtins/builtins/u32_wrapping_mul.eigs new file mode 100644 index 000000000..ac797766f --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_wrapping_mul"]) >= 0] +try: + x is u32_wrapping_mul of [4294967295, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_wrapping_mul"]) >= 0] +try: + x is u32_wrapping_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_wrapping_mul"]) >= 0] +try: + x is u32_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_wrapping_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_wrapping_mul of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_wrapping_mul.pre b/tests/phase2/builtins/builtins/u32_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_mul.tag b/tests/phase2/builtins/builtins/u32_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_sub.branch b/tests/phase2/builtins/builtins/u32_wrapping_sub.branch new file mode 100644 index 000000000..844bc63a3 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[4294967295, "int"] +[4294967294, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_sub.eigs b/tests/phase2/builtins/builtins/u32_wrapping_sub.eigs new file mode 100644 index 000000000..0902e521c --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u32_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u32_wrapping_sub"]) >= 0] +try: + x is u32_wrapping_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u32_wrapping_sub"]) >= 0] +try: + x is u32_wrapping_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u32_wrapping_sub"]) >= 0] +try: + x is u32_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u32_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u32_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u32_wrapping_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u32_wrapping_sub of [1, 4294967296] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u32_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u32_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u32_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u32_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u32_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u32_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u32_wrapping_sub.pre b/tests/phase2/builtins/builtins/u32_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u32_wrapping_sub.tag b/tests/phase2/builtins/builtins/u32_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u32_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_add.branch b/tests/phase2/builtins/builtins/u64_checked_add.branch new file mode 100644 index 000000000..3c6d5b4c8 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +["upper-or-lower", "value", true] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_add.eigs b/tests/phase2/builtins/builtins/u64_checked_add.eigs new file mode 100644 index 000000000..2a50f6243 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_checked_add"]) >= 0] +try: + x is u64_checked_add of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_checked_add"]) >= 0] +try: + x is u64_checked_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_checked_add"]) >= 0] +try: + x is u64_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_checked_add.pre b/tests/phase2/builtins/builtins/u64_checked_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_add.tag b/tests/phase2/builtins/builtins/u64_checked_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_mul.branch b/tests/phase2/builtins/builtins/u64_checked_mul.branch new file mode 100644 index 000000000..fc9af82a2 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +["upper-or-lower", "value", true] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_mul.eigs b/tests/phase2/builtins/builtins/u64_checked_mul.eigs new file mode 100644 index 000000000..d02b8b6d2 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_checked_mul"]) >= 0] +try: + x is u64_checked_mul of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_checked_mul"]) >= 0] +try: + x is u64_checked_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_checked_mul"]) >= 0] +try: + x is u64_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_checked_mul.pre b/tests/phase2/builtins/builtins/u64_checked_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_mul.tag b/tests/phase2/builtins/builtins/u64_checked_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_sub.branch b/tests/phase2/builtins/builtins/u64_checked_sub.branch new file mode 100644 index 000000000..ed342b1b8 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_sub.eigs b/tests/phase2/builtins/builtins/u64_checked_sub.eigs new file mode 100644 index 000000000..c437a0ffc --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_checked_sub"]) >= 0] +try: + x is u64_checked_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_checked_sub"]) >= 0] +try: + x is u64_checked_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_checked_sub"]) >= 0] +try: + x is u64_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_checked_sub.pre b/tests/phase2/builtins/builtins/u64_checked_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_checked_sub.tag b/tests/phase2/builtins/builtins/u64_checked_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_checked_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_compare.branch b/tests/phase2/builtins/builtins/u64_compare.branch new file mode 100644 index 000000000..959777d64 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_compare.branch @@ -0,0 +1,15 @@ +[1, "int"] +[1, "int"] +[1, "int"] +[1, "int"] +[-1, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_compare.eigs b/tests/phase2/builtins/builtins/u64_compare.eigs new file mode 100644 index 000000000..d81f14742 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_compare.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_compare of [4.0, 2.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_compare"]) >= 0] +try: + x is u64_compare of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_compare"]) >= 0] +try: + x is u64_compare of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_compare"]) >= 0] +try: + x is u64_compare of [-9223372036854775808, 9223372036854775807] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_compare"]) >= 0] +try: + x is u64_compare of [1, -1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_compare"]) >= 0] +try: + x is u64_compare of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_compare of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_compare of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_compare of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_compare of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_compare of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_compare of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_compare of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_compare of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_compare.pre b/tests/phase2/builtins/builtins/u64_compare.pre new file mode 100644 index 000000000..500450df2 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_compare.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_compare.tag b/tests/phase2/builtins/builtins/u64_compare.tag new file mode 100644 index 000000000..500450df2 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_compare.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_div.branch b/tests/phase2/builtins/builtins/u64_div.branch new file mode 100644 index 000000000..ae1e8b42b --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_div.branch @@ -0,0 +1,16 @@ +[2, "int"] +[-1, "int"] +[9223372036854775807, "int"] +[1, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +["zero-divisor", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_div.eigs b/tests/phase2/builtins/builtins/u64_div.eigs new file mode 100644 index 000000000..6b1edaf66 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_div.eigs @@ -0,0 +1,76 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_div of [4.0, 2.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_div"]) >= 0] +try: + x is u64_div of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_div"]) >= 0] +try: + x is u64_div of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_div"]) >= 0] +try: + x is u64_div of [-9223372036854775808, 9223372036854775807] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_div"]) >= 0] +try: + x is u64_div of [1, -1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_div"]) >= 0] +try: + x is u64_div of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_div of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_div of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_div of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_div of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_div of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_div of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_div of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_div of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] +try: + x is u64_div of [1, 0] + print of ["zero-divisor-ANSWER", x] +catch e: + print of ["zero-divisor", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_div.pre b/tests/phase2/builtins/builtins/u64_div.pre new file mode 100644 index 000000000..f83fefc0d --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_div.pre @@ -0,0 +1,16 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +["zero-divisor", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_div.tag b/tests/phase2/builtins/builtins/u64_div.tag new file mode 100644 index 000000000..f83fefc0d --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_div.tag @@ -0,0 +1,16 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +["zero-divisor", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_rem.branch b/tests/phase2/builtins/builtins/u64_rem.branch new file mode 100644 index 000000000..bb292353f --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_rem.branch @@ -0,0 +1,16 @@ +[0, "int"] +[0, "int"] +[1, "int"] +[1, "int"] +[1, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +["zero-divisor", "value"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_rem.eigs b/tests/phase2/builtins/builtins/u64_rem.eigs new file mode 100644 index 000000000..e42143a33 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_rem.eigs @@ -0,0 +1,76 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_rem of [4.0, 2.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_rem"]) >= 0] +try: + x is u64_rem of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_rem"]) >= 0] +try: + x is u64_rem of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_rem"]) >= 0] +try: + x is u64_rem of [-9223372036854775808, 9223372036854775807] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_rem"]) >= 0] +try: + x is u64_rem of [1, -1] + print of [x, num_type of x] +catch e: + print of ["unsigned", e.kind, (index_of of [e.message, "u64_rem"]) >= 0] +try: + x is u64_rem of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_rem of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_rem of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_rem of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_rem of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_rem of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_rem of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_rem of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_rem of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] +try: + x is u64_rem of [1, 0] + print of ["zero-divisor-ANSWER", x] +catch e: + print of ["zero-divisor", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_rem.pre b/tests/phase2/builtins/builtins/u64_rem.pre new file mode 100644 index 000000000..f83fefc0d --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_rem.pre @@ -0,0 +1,16 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +["zero-divisor", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_rem.tag b/tests/phase2/builtins/builtins/u64_rem.tag new file mode 100644 index 000000000..f83fefc0d --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_rem.tag @@ -0,0 +1,16 @@ +["integral-float", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["unsigned", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +["zero-divisor", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_add.branch b/tests/phase2/builtins/builtins/u64_saturating_add.branch new file mode 100644 index 000000000..d0a1263d0 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +[-1, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_add.eigs b/tests/phase2/builtins/builtins/u64_saturating_add.eigs new file mode 100644 index 000000000..7d26b7d11 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_saturating_add"]) >= 0] +try: + x is u64_saturating_add of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_saturating_add"]) >= 0] +try: + x is u64_saturating_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_saturating_add"]) >= 0] +try: + x is u64_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_saturating_add.pre b/tests/phase2/builtins/builtins/u64_saturating_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_add.tag b/tests/phase2/builtins/builtins/u64_saturating_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_mul.branch b/tests/phase2/builtins/builtins/u64_saturating_mul.branch new file mode 100644 index 000000000..63b1fc321 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +[-1, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_mul.eigs b/tests/phase2/builtins/builtins/u64_saturating_mul.eigs new file mode 100644 index 000000000..6e38dd092 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_saturating_mul"]) >= 0] +try: + x is u64_saturating_mul of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_saturating_mul"]) >= 0] +try: + x is u64_saturating_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_saturating_mul"]) >= 0] +try: + x is u64_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_saturating_mul.pre b/tests/phase2/builtins/builtins/u64_saturating_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_mul.tag b/tests/phase2/builtins/builtins/u64_saturating_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_sub.branch b/tests/phase2/builtins/builtins/u64_saturating_sub.branch new file mode 100644 index 000000000..adb8d1774 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +[0, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_sub.eigs b/tests/phase2/builtins/builtins/u64_saturating_sub.eigs new file mode 100644 index 000000000..a7bd3a2d6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_saturating_sub"]) >= 0] +try: + x is u64_saturating_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_saturating_sub"]) >= 0] +try: + x is u64_saturating_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_saturating_sub"]) >= 0] +try: + x is u64_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_saturating_sub.pre b/tests/phase2/builtins/builtins/u64_saturating_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_saturating_sub.tag b/tests/phase2/builtins/builtins/u64_saturating_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_saturating_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_to_string.branch b/tests/phase2/builtins/builtins/u64_to_string.branch new file mode 100644 index 000000000..cb8713d0c --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_to_string.branch @@ -0,0 +1,8 @@ +["1", "str"] +["18446744073709551615", "str"] +["9223372036854775808", "str"] +["9223372036854775807", "str"] +["fraction", "value"] +["float-outside-int64", "value"] +["non-number", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_to_string.eigs b/tests/phase2/builtins/builtins/u64_to_string.eigs new file mode 100644 index 000000000..87aa387e7 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_to_string.eigs @@ -0,0 +1,36 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_to_string of 1.0 + print of [x, type of x] +catch e: + print of ["unary", e.kind, (index_of of [e.message, "u64_to_string"]) >= 0] +try: + x is u64_to_string of -1 + print of [x, type of x] +catch e: + print of ["unary", e.kind, (index_of of [e.message, "u64_to_string"]) >= 0] +try: + x is u64_to_string of -9223372036854775808 + print of [x, type of x] +catch e: + print of ["unary", e.kind, (index_of of [e.message, "u64_to_string"]) >= 0] +try: + x is u64_to_string of 9223372036854775807 + print of [x, type of x] +catch e: + print of ["unary", e.kind, (index_of of [e.message, "u64_to_string"]) >= 0] +try: + x is u64_to_string of 1.5 + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_to_string of 1e30 + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_to_string of "bad" + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_to_string.pre b/tests/phase2/builtins/builtins/u64_to_string.pre new file mode 100644 index 000000000..7ad9d7be9 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_to_string.pre @@ -0,0 +1,8 @@ +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["non-number", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_to_string.tag b/tests/phase2/builtins/builtins/u64_to_string.tag new file mode 100644 index 000000000..7ad9d7be9 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_to_string.tag @@ -0,0 +1,8 @@ +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["unary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["non-number", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_add.branch b/tests/phase2/builtins/builtins/u64_wrapping_add.branch new file mode 100644 index 000000000..c6dd387cf --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_add.branch @@ -0,0 +1,13 @@ +[2, "int"] +[0, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_add.eigs b/tests/phase2/builtins/builtins/u64_wrapping_add.eigs new file mode 100644 index 000000000..52ffcf0e2 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_add.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_wrapping_add"]) >= 0] +try: + x is u64_wrapping_add of [-1, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_wrapping_add"]) >= 0] +try: + x is u64_wrapping_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_wrapping_add"]) >= 0] +try: + x is u64_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_wrapping_add.pre b/tests/phase2/builtins/builtins/u64_wrapping_add.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_add.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_add.tag b/tests/phase2/builtins/builtins/u64_wrapping_add.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_add.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_mul.branch b/tests/phase2/builtins/builtins/u64_wrapping_mul.branch new file mode 100644 index 000000000..ecd4942d5 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_mul.branch @@ -0,0 +1,13 @@ +[1, "int"] +[-2, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_mul.eigs b/tests/phase2/builtins/builtins/u64_wrapping_mul.eigs new file mode 100644 index 000000000..f9205f4df --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_mul.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_wrapping_mul"]) >= 0] +try: + x is u64_wrapping_mul of [-1, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_wrapping_mul"]) >= 0] +try: + x is u64_wrapping_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_wrapping_mul"]) >= 0] +try: + x is u64_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_wrapping_mul.pre b/tests/phase2/builtins/builtins/u64_wrapping_mul.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_mul.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_mul.tag b/tests/phase2/builtins/builtins/u64_wrapping_mul.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_mul.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_sub.branch b/tests/phase2/builtins/builtins/u64_wrapping_sub.branch new file mode 100644 index 000000000..c582401c5 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_sub.branch @@ -0,0 +1,13 @@ +[0, "int"] +[-1, "int"] +[-2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_sub.eigs b/tests/phase2/builtins/builtins/u64_wrapping_sub.eigs new file mode 100644 index 000000000..0decac722 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_sub.eigs @@ -0,0 +1,61 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u64_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u64_wrapping_sub"]) >= 0] +try: + x is u64_wrapping_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u64_wrapping_sub"]) >= 0] +try: + x is u64_wrapping_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u64_wrapping_sub"]) >= 0] +try: + x is u64_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u64_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u64_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u64_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u64_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u64_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u64_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u64_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u64_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u64_wrapping_sub.pre b/tests/phase2/builtins/builtins/u64_wrapping_sub.pre new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_sub.pre @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u64_wrapping_sub.tag b/tests/phase2/builtins/builtins/u64_wrapping_sub.tag new file mode 100644 index 000000000..596acbc39 --- /dev/null +++ b/tests/phase2/builtins/builtins/u64_wrapping_sub.tag @@ -0,0 +1,13 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_add.branch b/tests/phase2/builtins/builtins/u8_checked_add.branch new file mode 100644 index 000000000..920fa1906 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +["upper-or-lower", "value", true] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_add.eigs b/tests/phase2/builtins/builtins/u8_checked_add.eigs new file mode 100644 index 000000000..51d5d9784 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_checked_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_checked_add"]) >= 0] +try: + x is u8_checked_add of [255, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_checked_add"]) >= 0] +try: + x is u8_checked_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_checked_add"]) >= 0] +try: + x is u8_checked_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_checked_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_checked_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_checked_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_checked_add of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_checked_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_checked_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_checked_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_checked_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_checked_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_checked_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_checked_add.pre b/tests/phase2/builtins/builtins/u8_checked_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_add.tag b/tests/phase2/builtins/builtins/u8_checked_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_mul.branch b/tests/phase2/builtins/builtins/u8_checked_mul.branch new file mode 100644 index 000000000..108307400 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +["upper-or-lower", "value", true] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_mul.eigs b/tests/phase2/builtins/builtins/u8_checked_mul.eigs new file mode 100644 index 000000000..ff5090ff8 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_checked_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_checked_mul"]) >= 0] +try: + x is u8_checked_mul of [255, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_checked_mul"]) >= 0] +try: + x is u8_checked_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_checked_mul"]) >= 0] +try: + x is u8_checked_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_checked_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_checked_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_checked_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_checked_mul of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_checked_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_checked_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_checked_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_checked_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_checked_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_checked_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_checked_mul.pre b/tests/phase2/builtins/builtins/u8_checked_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_mul.tag b/tests/phase2/builtins/builtins/u8_checked_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_sub.branch b/tests/phase2/builtins/builtins/u8_checked_sub.branch new file mode 100644 index 000000000..bd48b8e78 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +["upper-or-lower", "value", true] +["other-boundary", "value", true] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_sub.eigs b/tests/phase2/builtins/builtins/u8_checked_sub.eigs new file mode 100644 index 000000000..9d8bba93d --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_checked_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_checked_sub"]) >= 0] +try: + x is u8_checked_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_checked_sub"]) >= 0] +try: + x is u8_checked_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_checked_sub"]) >= 0] +try: + x is u8_checked_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_checked_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_checked_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_checked_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_checked_sub of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_checked_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_checked_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_checked_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_checked_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_checked_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_checked_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_checked_sub.pre b/tests/phase2/builtins/builtins/u8_checked_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_checked_sub.tag b/tests/phase2/builtins/builtins/u8_checked_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_checked_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_add.branch b/tests/phase2/builtins/builtins/u8_saturating_add.branch new file mode 100644 index 000000000..5d0e2d2d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[255, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_add.eigs b/tests/phase2/builtins/builtins/u8_saturating_add.eigs new file mode 100644 index 000000000..d60e68876 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_saturating_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_saturating_add"]) >= 0] +try: + x is u8_saturating_add of [255, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_saturating_add"]) >= 0] +try: + x is u8_saturating_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_saturating_add"]) >= 0] +try: + x is u8_saturating_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_saturating_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_saturating_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_saturating_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_saturating_add of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_saturating_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_saturating_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_saturating_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_saturating_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_saturating_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_saturating_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_saturating_add.pre b/tests/phase2/builtins/builtins/u8_saturating_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_add.tag b/tests/phase2/builtins/builtins/u8_saturating_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_mul.branch b/tests/phase2/builtins/builtins/u8_saturating_mul.branch new file mode 100644 index 000000000..addc3f1ab --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[255, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_mul.eigs b/tests/phase2/builtins/builtins/u8_saturating_mul.eigs new file mode 100644 index 000000000..54b051e34 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_saturating_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_saturating_mul"]) >= 0] +try: + x is u8_saturating_mul of [255, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_saturating_mul"]) >= 0] +try: + x is u8_saturating_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_saturating_mul"]) >= 0] +try: + x is u8_saturating_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_saturating_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_saturating_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_saturating_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_saturating_mul of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_saturating_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_saturating_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_saturating_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_saturating_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_saturating_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_saturating_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_saturating_mul.pre b/tests/phase2/builtins/builtins/u8_saturating_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_mul.tag b/tests/phase2/builtins/builtins/u8_saturating_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_sub.branch b/tests/phase2/builtins/builtins/u8_saturating_sub.branch new file mode 100644 index 000000000..c5dafd985 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[0, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_sub.eigs b/tests/phase2/builtins/builtins/u8_saturating_sub.eigs new file mode 100644 index 000000000..4118f9ad9 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_saturating_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_saturating_sub"]) >= 0] +try: + x is u8_saturating_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_saturating_sub"]) >= 0] +try: + x is u8_saturating_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_saturating_sub"]) >= 0] +try: + x is u8_saturating_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_saturating_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_saturating_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_saturating_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_saturating_sub of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_saturating_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_saturating_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_saturating_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_saturating_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_saturating_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_saturating_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_saturating_sub.pre b/tests/phase2/builtins/builtins/u8_saturating_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_saturating_sub.tag b/tests/phase2/builtins/builtins/u8_saturating_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_saturating_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_add.branch b/tests/phase2/builtins/builtins/u8_wrapping_add.branch new file mode 100644 index 000000000..eaabc7db6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_add.branch @@ -0,0 +1,15 @@ +[2, "int"] +[0, "int"] +[2, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_add.eigs b/tests/phase2/builtins/builtins/u8_wrapping_add.eigs new file mode 100644 index 000000000..6a187f351 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_add.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_wrapping_add of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_wrapping_add"]) >= 0] +try: + x is u8_wrapping_add of [255, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_wrapping_add"]) >= 0] +try: + x is u8_wrapping_add of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_wrapping_add"]) >= 0] +try: + x is u8_wrapping_add of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_wrapping_add of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_wrapping_add of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_wrapping_add of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_wrapping_add of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_wrapping_add of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_wrapping_add of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_wrapping_add of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_wrapping_add of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_wrapping_add of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_wrapping_add of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_wrapping_add.pre b/tests/phase2/builtins/builtins/u8_wrapping_add.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_add.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_add.tag b/tests/phase2/builtins/builtins/u8_wrapping_add.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_add.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_mul.branch b/tests/phase2/builtins/builtins/u8_wrapping_mul.branch new file mode 100644 index 000000000..5697d9866 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_mul.branch @@ -0,0 +1,15 @@ +[1, "int"] +[254, "int"] +[0, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_mul.eigs b/tests/phase2/builtins/builtins/u8_wrapping_mul.eigs new file mode 100644 index 000000000..56d73bfbf --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_mul.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_wrapping_mul of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_wrapping_mul"]) >= 0] +try: + x is u8_wrapping_mul of [255, 2] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_wrapping_mul"]) >= 0] +try: + x is u8_wrapping_mul of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_wrapping_mul"]) >= 0] +try: + x is u8_wrapping_mul of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_wrapping_mul of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_wrapping_mul of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_wrapping_mul of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_wrapping_mul of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_wrapping_mul of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_wrapping_mul of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_wrapping_mul of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_wrapping_mul of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_wrapping_mul of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_wrapping_mul of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_wrapping_mul.pre b/tests/phase2/builtins/builtins/u8_wrapping_mul.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_mul.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_mul.tag b/tests/phase2/builtins/builtins/u8_wrapping_mul.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_mul.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_sub.branch b/tests/phase2/builtins/builtins/u8_wrapping_sub.branch new file mode 100644 index 000000000..7356e0cab --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_sub.branch @@ -0,0 +1,15 @@ +[0, "int"] +[255, "int"] +[254, "int"] +["fraction", "value"] +["float-outside-int64", "value"] +["fraction-right", "value"] +["range-low", "value"] +["range-high", "value"] +["non-number", "type_mismatch"] +["non-number-right", "type_mismatch"] +["few", "type_mismatch"] +["empty", "type_mismatch"] +["many", "type_mismatch"] +["scalar", "type_mismatch"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_sub.eigs b/tests/phase2/builtins/builtins/u8_wrapping_sub.eigs new file mode 100644 index 000000000..b3c34c033 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_sub.eigs @@ -0,0 +1,71 @@ +# phase2-exercises: {"int-requiring": ["fractional", "integral-float-success"], "integral-float-success": {"stdout-line": 1}, "q7": []} +try: + x is u8_wrapping_sub of [1.0, 1.0] + print of [x, num_type of x] +catch e: + print of ["integral-float", e.kind, (index_of of [e.message, "u8_wrapping_sub"]) >= 0] +try: + x is u8_wrapping_sub of [0, 1] + print of [x, num_type of x] +catch e: + print of ["upper-or-lower", e.kind, (index_of of [e.message, "u8_wrapping_sub"]) >= 0] +try: + x is u8_wrapping_sub of [0, 2] + print of [x, num_type of x] +catch e: + print of ["other-boundary", e.kind, (index_of of [e.message, "u8_wrapping_sub"]) >= 0] +try: + x is u8_wrapping_sub of [1.5, 1] + print of ["fraction-ANSWER", x] +catch e: + print of ["fraction", e.kind] +try: + x is u8_wrapping_sub of [1, 1e30] + print of ["float-outside-int64-ANSWER", x] +catch e: + print of ["float-outside-int64", e.kind] +try: + x is u8_wrapping_sub of [1, 1.5] + print of ["fraction-right-ANSWER", x] +catch e: + print of ["fraction-right", e.kind] +try: + x is u8_wrapping_sub of [-1, 1] + print of ["range-low-ANSWER", x] +catch e: + print of ["range-low", e.kind] +try: + x is u8_wrapping_sub of [1, 256] + print of ["range-high-ANSWER", x] +catch e: + print of ["range-high", e.kind] +try: + x is u8_wrapping_sub of ["bad", 1] + print of ["non-number-ANSWER", x] +catch e: + print of ["non-number", e.kind] +try: + x is u8_wrapping_sub of [1, null] + print of ["non-number-right-ANSWER", x] +catch e: + print of ["non-number-right", e.kind] +try: + x is u8_wrapping_sub of ([1]) + print of ["few-ANSWER", x] +catch e: + print of ["few", e.kind] +try: + x is u8_wrapping_sub of ([]) + print of ["empty-ANSWER", x] +catch e: + print of ["empty", e.kind] +try: + x is u8_wrapping_sub of [1, 2, 3] + print of ["many-ANSWER", x] +catch e: + print of ["many", e.kind] +try: + x is u8_wrapping_sub of 1 + print of ["scalar-ANSWER", x] +catch e: + print of ["scalar", e.kind] diff --git a/tests/phase2/builtins/builtins/u8_wrapping_sub.pre b/tests/phase2/builtins/builtins/u8_wrapping_sub.pre new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_sub.pre @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/u8_wrapping_sub.tag b/tests/phase2/builtins/builtins/u8_wrapping_sub.tag new file mode 100644 index 000000000..ea2649cf6 --- /dev/null +++ b/tests/phase2/builtins/builtins/u8_wrapping_sub.tag @@ -0,0 +1,15 @@ +["integral-float", "undefined_name", true] +["upper-or-lower", "undefined_name", true] +["other-boundary", "undefined_name", true] +["fraction", "undefined_name"] +["float-outside-int64", "undefined_name"] +["fraction-right", "undefined_name"] +["range-low", "undefined_name"] +["range-high", "undefined_name"] +["non-number", "undefined_name"] +["non-number-right", "undefined_name"] +["few", "undefined_name"] +["empty", "undefined_name"] +["many", "undefined_name"] +["scalar", "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/usleep.branch b/tests/phase2/builtins/builtins/usleep.branch new file mode 100644 index 000000000..1c35a5a7d --- /dev/null +++ b/tests/phase2/builtins/builtins/usleep.branch @@ -0,0 +1,4 @@ +null +["sleep-magnitude", "value"] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/usleep.eigs b/tests/phase2/builtins/builtins/usleep.eigs new file mode 100644 index 000000000..700d9ec06 --- /dev/null +++ b/tests/phase2/builtins/builtins/usleep.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: usleep (other, Q7 magnitude; rounded, a sleep the C int cannot hold raises); phase2-exercises: {"q7":["magnitude"]} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +print of (usleep of 1000) +try: + print of ["huge-sleep-answer", usleep of 1e300] +catch e: + print of ["sleep-magnitude", e.kind] +print of (usleep of 1.5) diff --git a/tests/phase2/builtins/builtins/usleep.pre b/tests/phase2/builtins/builtins/usleep.pre new file mode 100644 index 000000000..cc38b4914 --- /dev/null +++ b/tests/phase2/builtins/builtins/usleep.pre @@ -0,0 +1,4 @@ +null +["huge-sleep-answer", null] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/usleep.tag b/tests/phase2/builtins/builtins/usleep.tag new file mode 100644 index 000000000..cc38b4914 --- /dev/null +++ b/tests/phase2/builtins/builtins/usleep.tag @@ -0,0 +1,4 @@ +null +["huge-sleep-answer", null] +null +rc=0 diff --git a/tests/phase2/builtins/builtins/vm_run_bytecode.branch b/tests/phase2/builtins/builtins/vm_run_bytecode.branch new file mode 100644 index 000000000..3d6afef36 --- /dev/null +++ b/tests/phase2/builtins/builtins/vm_run_bytecode.branch @@ -0,0 +1,6 @@ +[5, "int"] +["abi-fraction", "value"] +["byte-fraction", "value"] +["byte-256", "value"] +[7, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/vm_run_bytecode.eigs b/tests/phase2/builtins/builtins/vm_run_bytecode.eigs new file mode 100644 index 000000000..a6c79e6c4 --- /dev/null +++ b/tests/phase2/builtins/builtins/vm_run_bytecode.eigs @@ -0,0 +1,22 @@ +# R1.6 behaviour-table probe: vm_run_bytecode (int-requiring; opcode bytes and ABI numbers); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +r is vm_run_bytecode of [1, [0, 0, 0, 0, 1, 0, 4, 40], [2, 3]] +print of [r, nt of r] +try: + print of ["fraction-answer", vm_run_bytecode of [1.5, [0, 0, 0, 40], [7]]] +catch e: + print of ["abi-fraction", e.kind] +try: + print of ["fraction-answer", vm_run_bytecode of [1, [0.5, 0, 0, 40], [7]]] +catch e: + print of ["byte-fraction", e.kind] +try: + print of ["256-answer", vm_run_bytecode of [1, [256, 0, 0, 40], [7]]] +catch e: + print of ["byte-256", e.kind] +f is vm_run_bytecode of [1.0, [0.0, 0.0, 0.0, 40.0], [7]] +print of [f, nt of f] diff --git a/tests/phase2/builtins/builtins/vm_run_bytecode.pre b/tests/phase2/builtins/builtins/vm_run_bytecode.pre new file mode 100644 index 000000000..748738363 --- /dev/null +++ b/tests/phase2/builtins/builtins/vm_run_bytecode.pre @@ -0,0 +1,6 @@ +[5, "int"] +["fraction-answer", 7] +["fraction-answer", 7] +["256-answer", 7] +[7, "int"] +rc=0 diff --git a/tests/phase2/builtins/builtins/vm_run_bytecode.tag b/tests/phase2/builtins/builtins/vm_run_bytecode.tag new file mode 100644 index 000000000..9eb82f285 --- /dev/null +++ b/tests/phase2/builtins/builtins/vm_run_bytecode.tag @@ -0,0 +1,6 @@ +[5, "undefined_name"] +["fraction-answer", 7] +["fraction-answer", 7] +["256-answer", 7] +[7, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/builtins/write.branch b/tests/phase2/builtins/builtins/write.branch new file mode 100644 index 000000000..9a1b2c014 --- /dev/null +++ b/tests/phase2/builtins/builtins/write.branch @@ -0,0 +1,2 @@ +9007199254740993 2.5 3.0 +rc=0 diff --git a/tests/phase2/builtins/builtins/write.eigs b/tests/phase2/builtins/builtins/write.eigs new file mode 100644 index 000000000..4bedbc1b4 --- /dev/null +++ b/tests/phase2/builtins/builtins/write.eigs @@ -0,0 +1,12 @@ +# R1.6 behaviour-table probe: write (other; N6 printing) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +write of 9007199254740993 +write of " " +write of 2.5 +write of " " +write of 3.0 +write of "\n" diff --git a/tests/phase2/builtins/builtins/write.pre b/tests/phase2/builtins/builtins/write.pre new file mode 100644 index 000000000..5e5e7829b --- /dev/null +++ b/tests/phase2/builtins/builtins/write.pre @@ -0,0 +1,2 @@ +9007199254740993 2.5 3 +rc=0 diff --git a/tests/phase2/builtins/builtins/write.tag b/tests/phase2/builtins/builtins/write.tag new file mode 100644 index 000000000..50442839c --- /dev/null +++ b/tests/phase2/builtins/builtins/write.tag @@ -0,0 +1,2 @@ +9007199254740992 2.5 3 +rc=0 diff --git a/tests/phase2/builtins/builtins/write_bytes.branch b/tests/phase2/builtins/builtins/write_bytes.branch new file mode 100644 index 000000000..24778313a --- /dev/null +++ b/tests/phase2/builtins/builtins/write_bytes.branch @@ -0,0 +1,5 @@ +[3, "int"] +["byte-300", "value"] +["byte-fraction", "value"] +[2, "Hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/write_bytes.eigs b/tests/phase2/builtins/builtins/write_bytes.eigs new file mode 100644 index 000000000..cae85b00e --- /dev/null +++ b/tests/phase2/builtins/builtins/write_bytes.eigs @@ -0,0 +1,20 @@ +# R1.6 behaviour-table probe: write_bytes (int-requiring; byte values: N5, then 0..255 (Q1); returns bytes written); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":4}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +p is env_get of "EIGS_PROBE_TMP" +f is p + "/w.bin" +n is write_bytes of [f, [65, 66, 67]] +print of [n, nt of n] +try: + print of ["300-answer", write_bytes of [f, [65, 300]]] +catch e: + print of ["byte-300", e.kind] +try: + print of ["fraction-answer", write_bytes of [f, [65.5]]] +catch e: + print of ["byte-fraction", e.kind] +m is write_bytes of [f, [72.0, 105.0]] +print of [m, read_text of f] diff --git a/tests/phase2/builtins/builtins/write_bytes.pre b/tests/phase2/builtins/builtins/write_bytes.pre new file mode 100644 index 000000000..65c6849fd --- /dev/null +++ b/tests/phase2/builtins/builtins/write_bytes.pre @@ -0,0 +1,5 @@ +[3, "float"] +["300-answer", 2] +["fraction-answer", 1] +[2, "Hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/write_bytes.tag b/tests/phase2/builtins/builtins/write_bytes.tag new file mode 100644 index 000000000..e09a5e5d3 --- /dev/null +++ b/tests/phase2/builtins/builtins/write_bytes.tag @@ -0,0 +1,5 @@ +[3, "undefined_name"] +["300-answer", 2] +["fraction-answer", 1] +[2, "Hi"] +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros.branch b/tests/phase2/builtins/builtins/zeros.branch new file mode 100644 index 000000000..fbe74c63d --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros.branch @@ -0,0 +1,6 @@ +[3, "float"] +["size-fraction", "value"] +[[[0.0, 0.0], [0.0, 0.0]], "float"] +["cols-fraction", "value"] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros.eigs b/tests/phase2/builtins/builtins/zeros.eigs new file mode 100644 index 000000000..308ad7fee --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros.eigs @@ -0,0 +1,19 @@ +# R1.6 behaviour-table probe: zeros (int-requiring; size or [rows, cols]; elements are float 0.0); phase2-exercises: {"int-requiring":["fractional","integral-float-success"],"integral-float-success":{"stdout-line":5}} +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +z is zeros of 3 +print of [buf_len of z, nt of z[0]] +try: + print of ["fraction-answer", buf_len of (zeros of 2.5)] +catch e: + print of ["size-fraction", e.kind] +m is zeros of [2, 2] +print of [m, nt of m[0][0]] +try: + print of ["fraction-answer", zeros of [2, 1.5]] +catch e: + print of ["cols-fraction", e.kind] +print of (buf_len of (zeros of 2.0)) diff --git a/tests/phase2/builtins/builtins/zeros.pre b/tests/phase2/builtins/builtins/zeros.pre new file mode 100644 index 000000000..205bd30ac --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros.pre @@ -0,0 +1,6 @@ +[3, "float"] +["fraction-answer", 2] +[[[0, 0], [0, 0]], "float"] +["fraction-answer", [[0], [0]]] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros.tag b/tests/phase2/builtins/builtins/zeros.tag new file mode 100644 index 000000000..d6095079b --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros.tag @@ -0,0 +1,6 @@ +[3, "undefined_name"] +["fraction-answer", 2] +[[[0, 0], [0, 0]], "undefined_name"] +["fraction-answer", [[0], [0]]] +2 +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros_like.branch b/tests/phase2/builtins/builtins/zeros_like.branch new file mode 100644 index 000000000..5cde5b592 --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros_like.branch @@ -0,0 +1,3 @@ +[[0.0, 0.0], "float"] +[0.0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros_like.eigs b/tests/phase2/builtins/builtins/zeros_like.eigs new file mode 100644 index 000000000..a2af3c936 --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros_like.eigs @@ -0,0 +1,10 @@ +# R1.6 behaviour-table probe: zeros_like (float-producing; float zeros) +define nt(x) as: + try: + return num_type of x + catch e: + return e.kind +a is zeros_like of [1, 2] +print of [a, nt of a[0]] +b is zeros_like of 5 +print of [b, nt of b] diff --git a/tests/phase2/builtins/builtins/zeros_like.pre b/tests/phase2/builtins/builtins/zeros_like.pre new file mode 100644 index 000000000..3f510d785 --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros_like.pre @@ -0,0 +1,3 @@ +[[0, 0], "float"] +[0, "float"] +rc=0 diff --git a/tests/phase2/builtins/builtins/zeros_like.tag b/tests/phase2/builtins/builtins/zeros_like.tag new file mode 100644 index 000000000..d27a52a93 --- /dev/null +++ b/tests/phase2/builtins/builtins/zeros_like.tag @@ -0,0 +1,3 @@ +[[0, 0], "undefined_name"] +[0, "undefined_name"] +rc=0 diff --git a/tests/phase2/builtins/cases.tsv b/tests/phase2/builtins/cases.tsv new file mode 100644 index 000000000..41979e3df --- /dev/null +++ b/tests/phase2/builtins/cases.tsv @@ -0,0 +1,311 @@ +case builtin hook stdin +abs abs - - +acos acos - - +add add - - +append append - - +asin asin - - +assert assert - - +atan atan - - +atan2 atan2 - - +audio_capture_open audio_capture_open - - +audio_envelope audio_envelope - - +audio_gain audio_gain - - +audio_mix audio_mix - - +audio_music_play audio_music_play - - +audio_music_volume audio_music_volume mixvol - +audio_noise audio_noise - - +audio_open audio_open - - +audio_pause audio_pause - - +audio_play audio_play mix - +audio_play_loop audio_play_loop mix - +audio_queue_size audio_queue_size - - +audio_saw audio_saw - - +audio_sine audio_sine - - +audio_square audio_square - - +audio_stop audio_stop - - +audio_stream_open audio_stream_open - - +audio_stream_push audio_stream_push pcm - +audio_stream_queued audio_stream_queued - - +audio_sweep audio_sweep - - +audio_volume audio_volume mix - +bit_and bit_and - - +bit_not bit_not - - +bit_or bit_or - - +bit_shl bit_shl - - +bit_shr bit_shr - - +bit_xor bit_xor - - +buf_copy buf_copy - - +buf_deinterleave buf_deinterleave - - +buf_dot buf_dot - - +buf_fill buf_fill - - +buf_from_list buf_from_list - - +buf_from_pcm16le buf_from_pcm16le - - +buf_get buf_get - - +buf_len buf_len - - +buf_mix buf_mix - - +buf_peak buf_peak - - +buf_resample_linear buf_resample_linear - - +buf_scale_range buf_scale_range - - +buf_set buf_set - - +buf_to_pcm16le buf_to_pcm16le - - +buffer buffer - - +build_corpus build_corpus - - +ceil ceil - - +channel channel - - +channel_closed channel_closed - - +char_at char_at - - +chr chr - - +clock_unix clock_unix - - +close_channel close_channel - - +copy_into copy_into - - +cos cos - - +db_execute db_execute - - +db_query_json db_query_json - - +db_query_value db_query_value - - +dispatch dispatch - - +divide divide - - +dot dot - - +eigen_eval_loss eigen_eval_loss - - +eigen_generate eigen_generate - - +eigen_model_load eigen_model_load - - +exec_capture exec_capture - - +exit exit - - +exp exp - - +f64_from_bytes f64_from_bytes - - +f64_to_bytes f64_to_bytes - - +fill fill - - +floor floor - - +gather gather - - +get_at get_at - - +get_observer_thresholds get_observer_thresholds - - +gfx_circle gfx_circle - - +gfx_clear gfx_clear - - +gfx_clip gfx_clip - - +gfx_delay gfx_delay - - +gfx_fb gfx_fb - - +gfx_line gfx_line - - +gfx_open gfx_open - - +gfx_point gfx_point - - +gfx_poll gfx_poll events - +gfx_poll.empty gfx_poll - - +gfx_read gfx_read - - +gfx_rect gfx_rect - - +gfx_rrect gfx_rrect - - +gfx_text gfx_text - - +gfx_text_height gfx_text_height - - +gfx_text_width gfx_text_width - - +gfx_ticks gfx_ticks - - +hex hex - - +http_early_bind http_early_bind - - +http_post http_post - - +http_route http_route - - +http_route_authed http_route_authed - - +http_serve http_serve - - +http_static http_static - - +index_of index_of - - +inflate inflate - - +join join - - +json_build json_build - - +json_decode json_decode - - +json_encode json_encode - - +json_path json_path - - +leaky_relu leaky_relu - - +len len - - +list_contains list_contains - - +list_index_of list_index_of - - +list_insert_at list_insert_at - - +list_remove_at list_remove_at - - +list_slice list_slice - - +list_truncate list_truncate - - +log log - - +log_softmax log_softmax - - +matmul matmul - - +matmul_at matmul_at - - +matmul_bt matmul_bt - - +max max - - +mean mean - - +min min - - +model_load_weights model_load_weights - - +monotonic_ms monotonic_ms - - +monotonic_ns monotonic_ns clock - +multiply multiply - - +native_train_step_builtin native_train_step_builtin - - +nearest_in_range nearest_in_range - - +nearest_in_range_all nearest_in_range_all - - +negative negative - - +net_accept net_accept - - +net_close net_close - - +net_dial net_dial - - +net_listen net_listen - - +net_port net_port - - +net_recv net_recv - - +net_send net_send - - +norm norm - - +num num - - +num_copy num_copy - - +num_type num_type - - +numerical_grad numerical_grad - - +numerical_grad_cols numerical_grad_cols - - +numerical_grad_rows numerical_grad_rows - - +observe observe - - +ord ord - - +pi pi - - +pow pow - - +ppu_render_frame ppu_render_frame - - +print print - - +random random - - +random_hex random_hex - - +random_int random_int - - +random_normal random_normal - - +range range - - +read_bytes read_bytes - - +read_bytes_buf read_bytes_buf - - +read_text read_text - - +record_history record_history - - +recv recv - - +recv_timeout recv_timeout - - +relu relu - - +reshape reshape - - +round round - - +sandbox_run sandbox_run - - +scan_int_tokens scan_int_tokens - - +scan_ints scan_ints - - +scatter_add scatter_add - - +seed_random seed_random - - +send send - - +set_at set_at - - +set_exit_code set_exit_code - - +sgd_update sgd_update - - +sgd_update_cols sgd_update_cols - - +sgd_update_rows sgd_update_rows - - +shape shape - - +shared_delete shared_delete - - +shared_incr shared_incr - - +shared_size shared_size - - +sign_extend sign_extend - - +sin sin - - +softmax softmax - - +sort sort - - +sort_by sort_by - - +spawn spawn - - +sqrt sqrt - - +state_at state_at - - +store_close store_close - - +store_collections store_collections - - +store_count store_count - - +store_delete store_delete - - +store_drop store_drop - - +store_get store_get - - +store_open store_open - - +store_put store_put - - +store_query store_query - - +store_update store_update - - +str str - - +str_from_bytes str_from_bytes - - +stream_open stream_open - - +stream_write stream_write - - +substr substr - - +subtract subtract - - +sum sum - - +tan tan - - +task_alive task_alive - - +task_detach task_detach - - +task_join task_join - - +task_kill task_kill - - +task_sched_seed task_sched_seed - - +task_self task_self - - +task_send task_send - - +task_sleep task_sleep - - +task_spawn task_spawn - - +tensor_load tensor_load - - +tensor_save tensor_save - - +text_builder_append text_builder_append - - +text_builder_append_line text_builder_append_line - - +thread_join thread_join - - +throw throw - - +token_name token_name - - +try_recv try_recv - - +type type - - +usleep usleep - - +vm_run_bytecode vm_run_bytecode - - +write write - - +write_bytes write_bytes - - +zeros zeros - - +zeros_like zeros_like - - +i8_wrapping_add i8_wrapping_add - - +i8_wrapping_sub i8_wrapping_sub - - +i8_wrapping_mul i8_wrapping_mul - - +i8_checked_add i8_checked_add - - +i8_checked_sub i8_checked_sub - - +i8_checked_mul i8_checked_mul - - +i8_saturating_add i8_saturating_add - - +i8_saturating_sub i8_saturating_sub - - +i8_saturating_mul i8_saturating_mul - - +i16_wrapping_add i16_wrapping_add - - +i16_wrapping_sub i16_wrapping_sub - - +i16_wrapping_mul i16_wrapping_mul - - +i16_checked_add i16_checked_add - - +i16_checked_sub i16_checked_sub - - +i16_checked_mul i16_checked_mul - - +i16_saturating_add i16_saturating_add - - +i16_saturating_sub i16_saturating_sub - - +i16_saturating_mul i16_saturating_mul - - +i32_wrapping_add i32_wrapping_add - - +i32_wrapping_sub i32_wrapping_sub - - +i32_wrapping_mul i32_wrapping_mul - - +i32_checked_add i32_checked_add - - +i32_checked_sub i32_checked_sub - - +i32_checked_mul i32_checked_mul - - +i32_saturating_add i32_saturating_add - - +i32_saturating_sub i32_saturating_sub - - +i32_saturating_mul i32_saturating_mul - - +i64_wrapping_add i64_wrapping_add - - +i64_wrapping_sub i64_wrapping_sub - - +i64_wrapping_mul i64_wrapping_mul - - +i64_checked_add i64_checked_add - - +i64_checked_sub i64_checked_sub - - +i64_checked_mul i64_checked_mul - - +i64_saturating_add i64_saturating_add - - +i64_saturating_sub i64_saturating_sub - - +i64_saturating_mul i64_saturating_mul - - +u8_wrapping_add u8_wrapping_add - - +u8_wrapping_sub u8_wrapping_sub - - +u8_wrapping_mul u8_wrapping_mul - - +u8_checked_add u8_checked_add - - +u8_checked_sub u8_checked_sub - - +u8_checked_mul u8_checked_mul - - +u8_saturating_add u8_saturating_add - - +u8_saturating_sub u8_saturating_sub - - +u8_saturating_mul u8_saturating_mul - - +u16_wrapping_add u16_wrapping_add - - +u16_wrapping_sub u16_wrapping_sub - - +u16_wrapping_mul u16_wrapping_mul - - +u16_checked_add u16_checked_add - - +u16_checked_sub u16_checked_sub - - +u16_checked_mul u16_checked_mul - - +u16_saturating_add u16_saturating_add - - +u16_saturating_sub u16_saturating_sub - - +u16_saturating_mul u16_saturating_mul - - +u32_wrapping_add u32_wrapping_add - - +u32_wrapping_sub u32_wrapping_sub - - +u32_wrapping_mul u32_wrapping_mul - - +u32_checked_add u32_checked_add - - +u32_checked_sub u32_checked_sub - - +u32_checked_mul u32_checked_mul - - +u32_saturating_add u32_saturating_add - - +u32_saturating_sub u32_saturating_sub - - +u32_saturating_mul u32_saturating_mul - - +u64_wrapping_add u64_wrapping_add - - +u64_wrapping_sub u64_wrapping_sub - - +u64_wrapping_mul u64_wrapping_mul - - +u64_checked_add u64_checked_add - - +u64_checked_sub u64_checked_sub - - +u64_checked_mul u64_checked_mul - - +u64_saturating_add u64_saturating_add - - +u64_saturating_sub u64_saturating_sub - - +u64_saturating_mul u64_saturating_mul - - +u64_compare u64_compare - - +u64_div u64_div - - +u64_rem u64_rem - - +u64_to_string u64_to_string - - +fround fround - - diff --git a/tests/phase2/builtins/determinism.sh b/tests/phase2/builtins/determinism.sh new file mode 100644 index 000000000..6e3ae5cc4 --- /dev/null +++ b/tests/phase2/builtins/determinism.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# determinism.sh [case...]: record the cases three times (twice normally, then +# pinned to core 0 at nice 19) and compare every .pre and .tag across the +# three runs. A value the OS or the environment chooses (a port, a pid, a +# time, a path) shows up here as UNSTABLE (ruling R1.6a-D2). The records left +# in builtins/ are the third run's. PRE_BIN, TAG_BIN and the PRE_BIN_

/ +# TAG_BIN_

profile binaries pass through to run_probes.sh. +# Exit 1 on any unstable record, 2 on an instrument failure. +set -u +HERE=$(cd "$(dirname "$0")" && pwd) +WORK=$(mktemp -d "${TMPDIR:-/tmp}/r16det.XXXXXX") || exit 2 +trap 'rm -rf "$WORK"' EXIT +for r in 1 2 3; do + pin=(); [ "$r" -eq 3 ] && pin=(taskset -c 0 nice -n 19) + if ! "${pin[@]}" bash "$HERE/run_probes.sh" "$@" > "$WORK/run$r.log" 2>&1; then + echo "FAIL: recording $r: $(tail -3 "$WORK/run$r.log" | tr '\n' ' ')"; exit 2 + fi + echo "run $r: $(tail -1 "$WORK/run$r.log")" + mkdir "$WORK/R$r" && cp "$HERE"/builtins/*.pre "$HERE"/builtins/*.tag "$WORK/R$r/" || exit 2 +done +n=0; unstable=0 +for f in "$WORK"/R1/*; do + b=$(basename "$f"); n=$((n + 1)) + if ! cmp -s "$f" "$WORK/R2/$b" || ! cmp -s "$f" "$WORK/R3/$b"; then unstable=$((unstable + 1)); echo "UNSTABLE $b"; fi +done +want=$(( $(ls "$HERE"/builtins/*.pre | wc -l) * 2 )) +echo "compared=$n records=$want unstable=$unstable" +[ "$n" -eq "$want" ] && [ "$n" -gt 0 ] && [ "$unstable" -eq 0 ] || exit 1 diff --git a/tests/phase2/builtins/http_capture.py b/tests/phase2/builtins/http_capture.py new file mode 100755 index 000000000..8df726760 --- /dev/null +++ b/tests/phase2/builtins/http_capture.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""B6: one loopback POST; answer the exact received X-Number header bytes.""" +import pathlib +import socket +import sys + +url_file, log_file = map(pathlib.Path, sys.argv[1:]) +with socket.socket() as listener: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(('127.0.0.1', 0)) + listener.listen(1) + listener.settimeout(65) + url_file.write_text('http://127.0.0.1:%d/capture\n' % listener.getsockname()[1]) + with listener.accept()[0] as connection: + connection.settimeout(60) + request = b'' + while b'\r\n\r\n' not in request: + chunk = connection.recv(65536) + if not chunk: + raise RuntimeError('HTTP request ended before its headers') + request += chunk + log_file.write_bytes(request) + headers, body = request.split(b'\r\n\r\n', 1) + fields = {} + for line in headers.split(b'\r\n')[1:]: + key, value = line.split(b':', 1) + fields[key.lower()] = value.lstrip(b' \t') + size = int(fields.get(b'content-length', b'0')) + while len(body) < size: + chunk = connection.recv(min(65536, size - len(body))) + if not chunk: + raise RuntimeError('HTTP request ended before Content-Length') + body += chunk + log_file.write_bytes(headers + b'\r\n\r\n' + body) + log_file.write_bytes(headers + b'\r\n\r\n' + body) + answer = fields.get(b'x-number', b'MISSING') + connection.sendall(b'HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: ' + + str(len(answer)).encode('ascii') + b'\r\n\r\n' + answer) diff --git a/tests/phase2/builtins/make_table.py b/tests/phase2/builtins/make_table.py new file mode 100644 index 000000000..6dd9d39f8 --- /dev/null +++ b/tests/phase2/builtins/make_table.py @@ -0,0 +1,428 @@ +#!/usr/bin/env python3 +"""Validate the pre-registered R1.6 behaviour bar (and explicitly refresh with --write). + +The inventory owns section-6 parsing. This checker retains every original bar +membership, closure, profile, hook, stdin, vacuity and accounting predicate. +Default execution never overwrites builtins.tsv or any expectation. --final +refuses pending profile observations. --run TAG BRANCH runs the same runner +used to record observations and emits the phase2-builtins PASS contract. +""" +import argparse, atexit, hashlib, importlib.util, json, os, re, shutil, subprocess, sys, tempfile +from pathlib import Path + +ROUNDS = ("R1.6a", "R1.6b", "R1.6c", "R1.6d", "R1.6e", "R1.6f", "R1.6g", "R1.6z", "R1.7") + + +# Author-ruled import, independent of the candidate's expectation files. +BAR_PIN = "0a73adb0612f8612d75393891ae2ba2e1c803663" +BAR_DIR = "tests/phase2/builtins/" + + +def frozen_bar(root): + """Compare the complete pathname/content set; amendments waive exact bytes.""" + def git(*args): + result = subprocess.run(["git", "-C", root, *args], capture_output=True) + if result.returncode: + raise ValueError("frozen bar pin %s unavailable: %s (fetch the pin explicitly)" % + (BAR_PIN, result.stderr.decode(errors="replace").strip())) + return result.stdout + + paths = git("ls-tree", "-r", "--name-only", BAR_PIN, "--", BAR_DIR).decode().splitlines() + pinned = {p: git("show", BAR_PIN + ":" + p) for p in paths if p.endswith(".branch")} + if not pinned: + raise ValueError("frozen bar pin %s contains no .branch paths under %s" % (BAR_PIN, BAR_DIR)) + amendments = {} + changes = os.path.join(root, "tests/phase2/builtins_changes.md") + with open(changes, encoding="utf-8") as stream: + section = stream.read().split("## Frozen .branch amendments\n", 1) + if len(section) == 2: + for line in section[1].split("\n## ", 1)[0].splitlines(): + if not line.startswith("|"): continue + fields = [v.strip().strip("`") for v in line.strip("|").split("|")] + if fields[0] == "Path" or all(re.fullmatch(r"[-:]+", v) for v in fields): continue + if len(fields) != 4: + raise ValueError("malformed frozen bar amendment: " + line) + path, digest, ruling, reason = fields + if (not path.startswith(BAR_DIR) or not path.endswith(".branch") or + any(part in ("", ".", "..") for part in path.split("/"))): + raise ValueError("invalid frozen bar amendment path: " + path) + if path in amendments: + raise ValueError("duplicate frozen bar amendment: " + path) + if not re.fullmatch(r"[0-9a-f]{64}|DELETED", digest) or not ruling or not reason: + raise ValueError("%s: amendment needs SHA-256 (or DELETED), ruling id and reason" % path) + amendments[path] = (digest, ruling) + from pathlib import Path + actual = {p.relative_to(root).as_posix(): p.read_bytes() + for p in (Path(root) / BAR_DIR).rglob("*.branch") if p.is_file()} + bad = [] + changed = {p for p in pinned.keys() | actual.keys() if pinned.get(p) != actual.get(p)} + for path in sorted(changed | amendments.keys()): + if path not in amendments: + bad.append("%s: unlisted frozen bar %s relative to %s" % + (path, "addition" if path not in pinned else "deletion" if path not in actual else "difference", BAR_PIN)) + continue + digest, ruling = amendments[path] + observed = hashlib.sha256(actual[path]).hexdigest() if path in actual else "DELETED" + if path not in changed or digest != observed: + bad.append("%s: stale amendment %s: SHA-256=%s actual=%s (must differ from pin)" % + (path, ruling, digest, observed)) + else: + print("frozen-bar: accepted amendment %s path=%s SHA-256=%s" % (ruling, path, digest)) + if bad: raise ValueError("\nFAIL: ".join(bad)) + print("frozen-bar: pin=%s paths=%d amendments=%d PASS" % (BAR_PIN, len(pinned), len(amendments))) + + +def read_landed(path): + """One declared full sub-round per non-comment line, in plan order.""" + seen = set() + with open(path, encoding="utf-8") as stream: + for line_number, line in enumerate(stream, 1): + value = line.split("#", 1)[0].strip() + if not value: continue + if value not in ROUNDS: raise ValueError("line %d: unknown sub-round %r" % (line_number, value)) + if value in seen: raise ValueError("line %d: duplicate sub-round %s" % (line_number, value)) + seen.add(value) + return [value for value in ROUNDS if value in seen] + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--final", action="store_true", help="refuse pending profile records") + parser.add_argument("--write", action="store_true", help="explicitly refresh derived builtins.tsv; never expectations") + parser.add_argument("--run", nargs=2, metavar=("TAG_BINARY", "BRANCH_BINARY")) + args = parser.parse_args() + ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) + try: + frozen_bar(ROOT) + except (OSError, ValueError) as error: + print("FAIL: " + str(error)) + return 1 + TREE = os.environ.get("EIGS_TREE") + if not TREE and os.environ.get("EIGS_REV"): + rev = os.environ["EIGS_REV"] + repo = os.environ.get("EIGS_REPO", ROOT) + TREE = tempfile.mkdtemp(prefix="r16table.") + atexit.register(shutil.rmtree, TREE, True) + archive = subprocess.run(["git", "-C", repo, "archive", rev, "docs/design", "src", "tools/phase2_inventory.py"], capture_output=True) + unpack = subprocess.run(["tar", "-x", "-C", TREE], input=archive.stdout, capture_output=True) if archive.returncode == 0 else None + if archive.returncode != 0 or unpack.returncode != 0: + print("FAIL: could not extract docs/design, src and shared inventory parser at %s from %s" % (rev, repo)) + return 2 + SOURCE = "%s@%s" % (repo, rev) + else: + TREE = TREE or ROOT + SOURCE = TREE + HERE = os.path.dirname(os.path.abspath(__file__)) + BDIR = os.path.join(HERE, "builtins") + FINAL = args.final + PROFILES = ("release", "server", "zlib", "db") + EXTS = (".eigs", ".branch", ".pre", ".tag") + FIXTURE_DIRS = {"_stdin", "_profiles"} + CAPABILITY_GONE = re.compile(r"capability unavailable|compiled without") + fail = [] + + def both_ways(a, b, a_name, b_name): + for n in sorted(a - b): fail.append("in %s, not in %s: %s" % (a_name, b_name, n)) + for n in sorted(b - a): fail.append("in %s, not in %s: %s" % (b_name, a_name, n)) + + def tsv(name, ncols): + lines = open(os.path.join(HERE, name)).read().splitlines() + rows = [l.split("\t") for l in lines[1:]] + for r in rows: + if len(r) != ncols: fail.append("%s: not %d fields: %r" % (name, ncols, r)) + return [r for r in rows if len(r) == ncols] + + spec = importlib.util.spec_from_file_location("phase2_inventory", os.path.join(TREE, "tools/phase2_inventory.py")) + inventory = importlib.util.module_from_spec(spec) + spec.loader.exec_module(inventory) + sec6, table_bad = inventory.read_table(os.path.join(TREE, "docs/design/phase2-numbers.md")) + fail.extend(table_bad) + sec6 = sec6 or {} + + # Exception printouts in these probes are [label, e.kind, ...]. Derive + # the kind vocabulary from the runtime's sole public string mapping. + error_kinds = set(re.findall(r'case EK_\w+:\s*return "([^"]+)";', + open(os.path.join(TREE, "src/eigenscript.c")).read())) + if not error_kinds: fail.append("no runtime error-kind names examined") + + inv_txt = open(os.path.join(TREE, "docs/design/phase2-inventory.txt")).read() + inv_body = inv_txt.split("\n## Builtins\n", 1)[1].split("\n## ", 1)[0] + inv = {} + for line in inv_body.splitlines(): + f = line.split() + if len(f) >= 5 and f[1].startswith("builtin_"): + inv[f[0]] = (f[1], f[2], f[3], f[4]) + both_ways(set(sec6), set(inv), "section 6", "the inventory") + for n in sorted(set(sec6) & set(inv)): + if sec6[n] != inv[n][1:]: + fail.append("section 6 %s vs inventory %s for %s" % (sec6[n], inv[n][1:], n)) + + src = os.path.join(TREE, "src") + defs = {} + for fn in sorted(os.listdir(src)): + if fn.endswith(".c"): + for m in re.finditer(r"^(?:static\s+)?Value\s*\*\s*(builtin_\w+)\s*\([^;{]*\)\s*\{", open(os.path.join(src, fn)).read(), re.M): + defs.setdefault(m.group(1), []).append(fn) + + def round_of(file, cls, loss): + if loss == "LOSS" or (file == "builtins.c" and cls == "int-preserving"): return "b" + if file in ("builtins.c", "builtins_host.c", "vm.c"): return "c" + if file in ("builtins_tensor.c", "builtins_buf.c") or file.startswith("model_"): return "d" + if file == "builtins_int.c": return "g" + if file == "ext_gfx.c": return "e" + if file in ("ext_store.c", "ext_db.c", "ext_http.c", "ext_net.c"): return "f" + fail.append("no round for file " + file); return "?" + + meta = {} + for b, closed_in, profile in tsv("rowmeta.tsv", 3): + if b in meta: fail.append("rowmeta.tsv: duplicate row " + b) + if profile not in PROFILES: fail.append("rowmeta.tsv: %s has profile %r" % (b, profile)) + if not closed_in.strip(): fail.append("rowmeta.tsv: %s has an empty closed_in (write -)" % b) + meta[b] = (closed_in, profile) + both_ways(set(sec6), set(meta), "section 6", "rowmeta.tsv") + + # D5 scopes A2 to earlier closures. R1.7 closures must prove the exact + # D2 transformation, using its anchored source-bound kinds and comparator. + float_amend, float_files = None, {} + if any(ci == "R1.7" for ci, _ in meta.values()): + try: + spec = importlib.util.spec_from_file_location("phase2_float_amend", os.path.join(ROOT, "tools/phase2_float_amend.py")) + float_amend = importlib.util.module_from_spec(spec) + spec.loader.exec_module(float_amend) + data, _ = float_amend.load(Path(ROOT), Path(ROOT) / "tests/phase2/r17_float_text.json") + float_files = data["files"] + except (OSError, ValueError, KeyError, IndexError, TypeError) as error: + fail.append("R1.7-D5: cannot load D2 kind provenance: " + str(error)) + if float_files: + try: + # D6 covers every amended row, including rows without closed_in. + # Reuse D2's comparator, independently of the amendment SHA waiver. + float_amend.check(Path(ROOT), Path(ROOT) / "tests/phase2/r17_float_text.json") + except (OSError, ValueError, KeyError, IndexError, TypeError) as error: + fail.append("R1.7-D6: " + str(error)) + + cases = {} + for c, b, hook, stdin in tsv("cases.tsv", 4): + if c in cases: fail.append("cases.tsv: duplicate case " + c) + if c != b and not c.startswith(b + "."): fail.append("cases.tsv: case %s does not name its builtin %s" % (c, b)) + if b not in sec6: fail.append("cases.tsv: case %s names no section-6 row" % c) + cases[c] = (b, hook, stdin) + both_ways(set(sec6), {c for c, v in cases.items() if c == v[0]}, "section 6", "the primary cases") + + shim = open(os.path.join(HERE, "..", "observation_hooks.c")).read() + both_ways(set(re.findall(r'hook_is\("(\w+)"\)', shim)), {v[1] for v in cases.values()} - {"-"}, + "the shim's hooks", "the cases' hooks") + # S10-D2 retires the only stdin fixture; an absent directory means no fixtures. + stdin_dir = os.path.join(BDIR, "_stdin") + both_ways(set(os.listdir(stdin_dir)) if os.path.isdir(stdin_dir) else set(), {v[2] for v in cases.values()} - {"-"}, + "builtins/_stdin", "the cases' stdin") + both_ways({f[:-5] for f in os.listdir(os.path.join(BDIR, "_profiles")) if f.endswith(".eigs")}, + {p for _, p in meta.values()} - {"release"}, "builtins/_profiles", "the non-release profiles") + listing = set(os.listdir(BDIR)) + for f in sorted(listing - FIXTURE_DIRS): + if not f.endswith(EXTS): fail.append("builtins/%s is neither a case file nor a fixture dir" % f) + for ext in EXTS: + have = {f[:-len(ext)] for f in listing if f.endswith(ext)} + both_ways(set(cases), have, "cases.tsv", "builtins/*" + ext) + print("files %-7s %d (cases %d)" % (ext, len(have), len(cases))) + + def record(c, ext): + p = os.path.join(BDIR, c + ext) + return open(p).read() if os.path.exists(p) else None + + state = {} # (case, "pre"|"tag") -> same | differs | pending-profile | missing + for c in sorted(cases): + b, hook, stdin = cases[c] + closed_in, profile = meta.get(b, ("-", "?")) + br = record(c, ".branch") + for kind in ("branch", "pre", "tag"): + rec = record(c, "." + kind) + if rec is None or br is None: + state[(c, kind)] = "missing"; continue + if rec.startswith("PENDING-PROFILE"): + if kind == "branch" or profile == "release" or rec != "PENDING-PROFILE %s\n" % profile: + fail.append("%s.%s: %r on a %s row" % (c, kind, rec.strip(), profile)) + state[(c, kind)] = "pending-profile"; continue + if profile != "release" and CAPABILITY_GONE.search(rec): + fail.append("%s.%s is a capability-unavailable transcript, not a %s build (ruling A3)" % (c, kind, profile)) + lines = rec.splitlines() + if not lines or not re.fullmatch(r"rc=-?\d+", lines[-1]) or sum(bool(re.fullmatch(r"rc=-?\d+", l)) for l in lines) != 1: + fail.append("%s.%s: expected stdout followed by exactly one final rc= line" % (c, kind)) + loaded = [l for l in lines if l.startswith("hook-loaded")] + if hook == "-" and loaded: + fail.append("%s.%s holds %r but the case has no hook" % (c, kind, loaded[0])) + if hook != "-" and loaded != ["hook-loaded " + hook]: + fail.append("%s.%s holds %r, not one 'hook-loaded %s' line: not recorded with its shim" % (c, kind, loaded, hook)) + state[(c, kind)] = "same" if rec == br else "differs" + + def agg(b, kind): + s = [state[(c, kind)] for c in sorted(cases) if cases[c][0] == b] + if "missing" in s: return "missing" + if "pending-profile" in s: return "pending-profile" + return "yes" if "differs" in s else "no" + + rows, counts, tally = [], {}, {} + r17_cases, r17_floats = 0, 0 + guard = {"guarded": 0, "yes": 0, "closed_in": 0, "pending-profile": 0, "vacuous": 0} + for n in sorted(sec6): + cls, loss, q7 = sec6[n] + files = sorted(set(defs.get(inv.get(n, ("",))[0], []))) # #if/#else twins (inflate) are one file + if len(files) != 1: + fail.append("%s: C function defined in %r (want exactly one file)" % (n, files)); file = "?" + else: + file = files[0] + r = round_of(file, cls, loss) + closed_in, profile = meta.get(n, ("-", "?")) + mine = [c for c in sorted(cases) if cases[c][0] == n] + hooks = [cases[c][1] for c in mine] + stdins = [cases[c][2] for c in mine] + vs_pre, vs_tag = agg(n, "pre"), agg(n, "tag") + for kind, v in (("pre", vs_pre), ("tag", vs_tag)): + if v == "missing": fail.append("%s: a .branch or .%s is missing" % (n, kind)) + if v == "pending-profile" and FINAL: fail.append("%s: .%s is PENDING-PROFILE %s (--final)" % (n, kind, profile)) + # Result subtypes are visible in the immutable expected output. Inputs + # whose success/continuous class is not self-describing have a declaration + # on the existing first comment line (no state_at line numbers move). + brs = "".join(record(c, ".branch") or "" for c in mine) + probe = record(n, ".eigs") or "" + header = re.search(r"phase2-exercises: (\{[^\n]*\})", probe.split("\n", 1)[0]) + exercises = {} + if header: + try: + exercises = json.loads(header.group(1)) + if not isinstance(exercises, dict): raise ValueError("expected object") + except (ValueError, TypeError): + fail.append("%s: malformed phase2-exercises header" % n) + exercises = {} + if cls in ("int-preserving", "float-producing"): + kind = "int" if cls == "int-preserving" else "float" + if '"' + kind + '"' not in brs: + fail.append("%s: %s .branch does not show result subtype %s" % (n, cls, kind)) + if cls == "int-requiring": + if not re.search(r'^\[.*fraction[^\n]*, "value"(?:,[^\n]*)?\]$', brs, re.M): + fail.append("%s: int-requiring .branch has no fractional-call value raise" % n) + if exercises.get("int-requiring") != ["fractional", "integral-float-success"] or not re.search(r"\d+\.0(?:\D|$)", probe): + fail.append("%s: int-requiring probe needs fractional and successful integral-float exercise header" % n) + success = exercises.get("integral-float-success") + lines = (record(n, ".branch") or "").splitlines() + if not isinstance(success, dict): + fail.append("%s: integral-float success needs an identified stdout line or exit code" % n) + elif set(success) == {"exit-code"}: + code = success["exit-code"] + # exit and set_exit_code expose successful integral-float + # conversion through the requested process status (R1.6-S2). + if (n not in ("exit", "set_exit_code") or type(code) is not int + or not re.search(r"\b%s of %d\.0(?:\D|$)" % (re.escape(n), code), probe) + or not lines or lines[-1] != "rc=%d" % code): + fail.append("%s: integral-float success exit code is not witnessed in .branch" % n) + elif set(success) == {"stdout-line"}: + line_number = success["stdout-line"] + if type(line_number) is not int or not 1 <= line_number < len(lines): + fail.append("%s: integral-float success stdout line is outside .branch" % n) + else: + output = lines[line_number - 1] + try: value = json.loads(output) + except ValueError: value = output + error = (isinstance(value, str) and value in error_kinds + or isinstance(value, list) and len(value) > 1 + and isinstance(value[1], str) and value[1] in error_kinds + or isinstance(value, dict) and (value.get("ok") is False or "error" in value)) + if not output or output.startswith(("rc=", "hook-loaded", "mix:", "SDL_QueueAudio")) or error: + fail.append("%s: integral-float success stdout line %d is an error or non-result: %s" % (n, line_number, output)) + if lines[-1] != "rc=0": + fail.append("%s: integral-float success .branch does not finish with rc=0" % n) + else: + fail.append("%s: integral-float success header has unknown fields" % n) + want_q7 = [] if q7 == "-" else q7.split(",") + if exercises.get("q7", []) != want_q7: + fail.append("%s: phase2-exercises q7 %r != table %r" % (n, exercises.get("q7", []), want_q7)) + guarded = loss == "LOSS" or cls == "int-requiring" or q7 != "-" + if closed_in != "-": + if not guarded: fail.append("%s: closed_in %r on a row the vacuity guard does not cover" % (n, closed_in)) + if closed_in == "R1.7": + for c in mine: + path = "tests/phase2/builtins/builtins/" + c + ".branch" + try: + if path not in float_files: + raise ValueError(path + ": no source-bound D2 kind provenance") + r17_floats += float_amend.check_pair( + (Path(BDIR) / (c + ".pre")).read_bytes(), + (Path(ROOT) / path).read_bytes(), float_files[path]["line_kinds"], path) + r17_cases += 1 + except (OSError, ValueError, KeyError, IndexError, TypeError) as error: + fail.append("%s: closed_in R1.7 must be exactly the D2 float re-render (R1.7-D5): %s" % (n, error)) + elif vs_pre != "no": fail.append("%s: closed_in %r but changes_vs_pre is %s: the closure must hold as .pre == .branch (ruling A2)" % (n, closed_in, vs_pre)) + if guarded: + guard["guarded"] += 1 + if closed_in != "-": guard["closed_in"] += 1 + elif vs_pre == "yes": guard["yes"] += 1 + elif vs_pre == "pending-profile": guard["pending-profile"] += 1 + else: + guard["vacuous"] += 1 + fail.append("vacuous: %s is a %s row without closed_in and changes_vs_pre %s" % (n, "/".join(x for x in (cls, loss, q7) if x != "-"), vs_pre)) + rows.append((n, file, cls, loss, q7, r, closed_in, profile, + ",".join(hooks) if set(hooks) != {"-"} else "-", + ",".join(stdins) if set(stdins) != {"-"} else "-", vs_pre, vs_tag)) + counts[r] = counts.get(r, 0) + 1 + for kind, v in (("pre", vs_pre), ("tag", vs_tag)): + tally[(r, kind, v)] = tally.get((r, kind, v), 0) + 1 + + want = {"b": 46, "c": 76, "d": 53, "e": 38, "f": 19, "g": 77} # S10-D2 retires proc_read_buf from c; all other round memberships stay fixed. + if counts != want: fail.append("round counts %s, the plan says %s" % (counts, want)) + if not rows or len(rows) != len(sec6): fail.append("examined=%d table=%d" % (len(rows), len(sec6))) + if guard["guarded"] != guard["yes"] + guard["closed_in"] + guard["pending-profile"] + guard["vacuous"]: + fail.append("guard accounting %r" % guard) + + header = "builtin\tfile\tclass\tloss\tq7\tround\tclosed_in\tprofile\thook\tstdin\tchanges_vs_pre\tchanges_vs_tag\n" + expected = header + "".join("\t".join(row) + "\n" for row in rows) + if args.write: + if not fail: + with open(os.path.join(HERE, "builtins.tsv"), "w") as out: out.write(expected) + else: + with open(os.path.join(HERE, "builtins.tsv")) as stream: + if stream.readline() != header: fail.append("builtins.tsv: header differs from the declared schema") + current = tsv("builtins.tsv", 12) + have = {} + for row in current: + if row[0] in have: fail.append("builtins.tsv: duplicate row " + row[0]) + have[row[0]] = row + both_ways(set(have), set(sec6), "builtins.tsv", "section 6") + fields = header.strip().split("\t") + for row in rows: + if row[0] in have: + for field, got, want_value in zip(fields[1:], have[row[0]][1:], row[1:]): + if got != want_value: fail.append("%s: builtins.tsv %s %r != derived %r" % (row[0], field, got, want_value)) + + + prof = {p: sum(1 for _, q in meta.values() if q == p) for p in PROFILES} + print("source=%s" % SOURCE) + print("examined=%d section6=%d inventory=%d rowmeta=%d cases=%d" % (len(rows), len(sec6), len(inv), len(meta), len(cases))) + print("closed_in=%d %s hook_cases=%d stdin_cases=%d" % (sum(1 for ci, _ in meta.values() if ci != "-"), + " ".join("%s=%d" % kv for kv in prof.items()), sum(1 for v in cases.values() if v[1] != "-"), + sum(1 for v in cases.values() if v[2] != "-"))) + for r in sorted(want): + print("round %s: %d rows; " % (r, counts.get(r, 0)) + "; ".join( + "vs_%s yes=%d no=%d pending-profile=%d" % (k, tally.get((r, k, "yes"), 0), tally.get((r, k, "no"), 0), + tally.get((r, k, "pending-profile"), 0)) for k in ("pre", "tag"))) + print("guard: guarded=%(guarded)d yes=%(yes)d closed_in=%(closed_in)d pending_profile=%(pending-profile)d vacuous=%(vacuous)d" % guard + + (" (final)" if FINAL else "")) + print("closure-r17: cases=%d floats=%d (R1.7-D5)" % (r17_cases, r17_floats)) + for f in fail: print("FAIL: " + f) + if fail: return 1 + if args.run: + try: landed = read_landed(os.path.join(HERE, "..", "builtins_landed.txt")) + except (OSError, ValueError) as error: + print("FAIL: builtins_landed.txt: " + str(error)); return 1 + env = os.environ.copy() + env["R16_LANDED"] = " ".join(landed) + sys.stdout.flush() + rc = subprocess.call(["bash", os.path.join(HERE, "run_probes.sh"), "--check", *args.run], env=env) + if rc: return rc + migrated = sum(1 for row in rows if "R1.6" + row[5] in landed or row[6] == "R1.7" and "R1.7" in landed) + print("phase2-builtins: PASS rows=%d landed=%s migrated=%d unchanged=%d" % + (len(rows), ",".join(landed) or "-", migrated, len(rows) - migrated)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/phase2/builtins/oracle/abs.lua b/tests/phase2/builtins/oracle/abs.lua new file mode 100644 index 000000000..199110c54 --- /dev/null +++ b/tests/phase2/builtins/oracle/abs.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(math.abs(-9007199254740993))) +print(t(math.abs(-2.5))) +print(t(math.abs(math.mininteger))) diff --git a/tests/phase2/builtins/oracle/abs.lua.out b/tests/phase2/builtins/oracle/abs.lua.out new file mode 100644 index 000000000..7c60a3112 --- /dev/null +++ b/tests/phase2/builtins/oracle/abs.lua.out @@ -0,0 +1,3 @@ +9007199254740993 integer +2.5 float +-9223372036854775808 integer diff --git a/tests/phase2/builtins/oracle/acos.lua b/tests/phase2/builtins/oracle/acos.lua new file mode 100644 index 000000000..bced79e40 --- /dev/null +++ b/tests/phase2/builtins/oracle/acos.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({1, 0}) do print(t(math.acos(v))) end diff --git a/tests/phase2/builtins/oracle/acos.lua.out b/tests/phase2/builtins/oracle/acos.lua.out new file mode 100644 index 000000000..4f460752d --- /dev/null +++ b/tests/phase2/builtins/oracle/acos.lua.out @@ -0,0 +1,2 @@ +0.0 float 0 +1.5707963267949 float 1.5707963267948966 diff --git a/tests/phase2/builtins/oracle/asin.lua b/tests/phase2/builtins/oracle/asin.lua new file mode 100644 index 000000000..df7d0249b --- /dev/null +++ b/tests/phase2/builtins/oracle/asin.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({0, 1}) do print(t(math.asin(v))) end diff --git a/tests/phase2/builtins/oracle/asin.lua.out b/tests/phase2/builtins/oracle/asin.lua.out new file mode 100644 index 000000000..4f460752d --- /dev/null +++ b/tests/phase2/builtins/oracle/asin.lua.out @@ -0,0 +1,2 @@ +0.0 float 0 +1.5707963267949 float 1.5707963267948966 diff --git a/tests/phase2/builtins/oracle/atan.lua b/tests/phase2/builtins/oracle/atan.lua new file mode 100644 index 000000000..534e3dcac --- /dev/null +++ b/tests/phase2/builtins/oracle/atan.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({1, 0}) do print(t(math.atan(v))) end diff --git a/tests/phase2/builtins/oracle/atan.lua.out b/tests/phase2/builtins/oracle/atan.lua.out new file mode 100644 index 000000000..321832f6b --- /dev/null +++ b/tests/phase2/builtins/oracle/atan.lua.out @@ -0,0 +1,2 @@ +0.78539816339745 float 0.78539816339744828 +0.0 float 0 diff --git a/tests/phase2/builtins/oracle/atan2.lua b/tests/phase2/builtins/oracle/atan2.lua new file mode 100644 index 000000000..194f253d0 --- /dev/null +++ b/tests/phase2/builtins/oracle/atan2.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(math.atan(1, 1))) +print(t(math.atan(0, -1))) diff --git a/tests/phase2/builtins/oracle/atan2.lua.out b/tests/phase2/builtins/oracle/atan2.lua.out new file mode 100644 index 000000000..988df9d16 --- /dev/null +++ b/tests/phase2/builtins/oracle/atan2.lua.out @@ -0,0 +1,2 @@ +0.78539816339745 float 0.78539816339744828 +3.1415926535898 float 3.1415926535897931 diff --git a/tests/phase2/builtins/oracle/bit_and.lua b/tests/phase2/builtins/oracle/bit_and.lua new file mode 100644 index 000000000..607fcf3f9 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_and.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(9007199254740993 & 9007199254740993)) +print(pcall(function() return 1.5 & 1 end)) +print(t(6.0 & 3.0)) diff --git a/tests/phase2/builtins/oracle/bit_and.lua.out b/tests/phase2/builtins/oracle/bit_and.lua.out new file mode 100644 index 000000000..75c75dcd2 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_and.lua.out @@ -0,0 +1,3 @@ +9007199254740993 integer +false bit_and.lua:3: number has no integer representation +2 integer diff --git a/tests/phase2/builtins/oracle/bit_not.lua b/tests/phase2/builtins/oracle/bit_not.lua new file mode 100644 index 000000000..10dd6c5ed --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_not.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(~9007199254740992)) +print(pcall(function() return ~1.5 end)) +print(t(~0.0)) diff --git a/tests/phase2/builtins/oracle/bit_not.lua.out b/tests/phase2/builtins/oracle/bit_not.lua.out new file mode 100644 index 000000000..04321d7ca --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_not.lua.out @@ -0,0 +1,3 @@ +-9007199254740993 integer +false bit_not.lua:3: number has no integer representation +-1 integer diff --git a/tests/phase2/builtins/oracle/bit_or.lua b/tests/phase2/builtins/oracle/bit_or.lua new file mode 100644 index 000000000..eab57b8ab --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_or.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(9007199254740992 | 1)) +print(pcall(function() return 1.5 | 1 end)) +print(t(4.0 | 1.0)) diff --git a/tests/phase2/builtins/oracle/bit_or.lua.out b/tests/phase2/builtins/oracle/bit_or.lua.out new file mode 100644 index 000000000..e59ef84e4 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_or.lua.out @@ -0,0 +1,3 @@ +9007199254740993 integer +false bit_or.lua:3: number has no integer representation +5 integer diff --git a/tests/phase2/builtins/oracle/bit_shl.lua b/tests/phase2/builtins/oracle/bit_shl.lua new file mode 100644 index 000000000..3d36f1573 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_shl.lua @@ -0,0 +1,5 @@ +local function t(x) return x, math.type(x) end +print(t(1 << 63)) +print(1 << 64, 8 << -1) +print(pcall(function() return 1 << 1.5 end)) +print(t(1.0 << 4.0)) diff --git a/tests/phase2/builtins/oracle/bit_shl.lua.out b/tests/phase2/builtins/oracle/bit_shl.lua.out new file mode 100644 index 000000000..c0e1853e9 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_shl.lua.out @@ -0,0 +1,4 @@ +-9223372036854775808 integer +0 4 +false bit_shl.lua:4: number has no integer representation +16 integer diff --git a/tests/phase2/builtins/oracle/bit_shr.lua b/tests/phase2/builtins/oracle/bit_shr.lua new file mode 100644 index 000000000..db276811c --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_shr.lua @@ -0,0 +1,5 @@ +local function t(x) return x, math.type(x) end +print(t(-1 >> 1)) +print(1 >> 64, 1 >> -1) +print(pcall(function() return 16 >> 1.5 end)) +print(t(16.0 >> 2.0)) diff --git a/tests/phase2/builtins/oracle/bit_shr.lua.out b/tests/phase2/builtins/oracle/bit_shr.lua.out new file mode 100644 index 000000000..760e01aa0 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_shr.lua.out @@ -0,0 +1,4 @@ +9223372036854775807 integer +0 2 +false bit_shr.lua:4: number has no integer representation +4 integer diff --git a/tests/phase2/builtins/oracle/bit_xor.lua b/tests/phase2/builtins/oracle/bit_xor.lua new file mode 100644 index 000000000..04bf72b61 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_xor.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(9007199254740993 ~ 1)) +print(pcall(function() return 1.5 ~ 1 end)) +print(t(6.0 ~ 3.0)) diff --git a/tests/phase2/builtins/oracle/bit_xor.lua.out b/tests/phase2/builtins/oracle/bit_xor.lua.out new file mode 100644 index 000000000..4a144f6a5 --- /dev/null +++ b/tests/phase2/builtins/oracle/bit_xor.lua.out @@ -0,0 +1,3 @@ +9007199254740992 integer +false bit_xor.lua:3: number has no integer representation +5 integer diff --git a/tests/phase2/builtins/oracle/ceil.lua b/tests/phase2/builtins/oracle/ceil.lua new file mode 100644 index 000000000..230c224d4 --- /dev/null +++ b/tests/phase2/builtins/oracle/ceil.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(math.ceil(9007199254740993))) +print(t(math.ceil(-2.5))) +print(t(math.ceil(1e300))) diff --git a/tests/phase2/builtins/oracle/ceil.lua.out b/tests/phase2/builtins/oracle/ceil.lua.out new file mode 100644 index 000000000..b89cdfdc2 --- /dev/null +++ b/tests/phase2/builtins/oracle/ceil.lua.out @@ -0,0 +1,3 @@ +9007199254740993 integer +-2 integer +1e+300 float diff --git a/tests/phase2/builtins/oracle/char_at.lua b/tests/phase2/builtins/oracle/char_at.lua new file mode 100644 index 000000000..9959ceb1e --- /dev/null +++ b/tests/phase2/builtins/oracle/char_at.lua @@ -0,0 +1,4 @@ +-- char_at of [s, i] is 0-based; Lua string.sub(s, i + 1, i + 1) +print(string.sub("hello", 2, 2)) +print(pcall(string.sub, "hello", 2.5, 2.5)) +print(string.sub("hello", 2.0, 2.0), string.sub("hello", -1, -1)) diff --git a/tests/phase2/builtins/oracle/char_at.lua.out b/tests/phase2/builtins/oracle/char_at.lua.out new file mode 100644 index 000000000..f395348bb --- /dev/null +++ b/tests/phase2/builtins/oracle/char_at.lua.out @@ -0,0 +1,3 @@ +e +false bad argument #2 to 'string.sub' (number has no integer representation) +e o diff --git a/tests/phase2/builtins/oracle/chr.lua b/tests/phase2/builtins/oracle/chr.lua new file mode 100644 index 000000000..28cd1aad8 --- /dev/null +++ b/tests/phase2/builtins/oracle/chr.lua @@ -0,0 +1,4 @@ +print(string.char(65)) +print(pcall(string.char, 65.5)) +print(pcall(string.char, 256)) +print(string.char(66.0)) diff --git a/tests/phase2/builtins/oracle/chr.lua.out b/tests/phase2/builtins/oracle/chr.lua.out new file mode 100644 index 000000000..c6b1f2653 --- /dev/null +++ b/tests/phase2/builtins/oracle/chr.lua.out @@ -0,0 +1,4 @@ +A +false bad argument #1 to 'string.char' (number has no integer representation) +false bad argument #1 to 'string.char' (value out of range) +B diff --git a/tests/phase2/builtins/oracle/cos.lua b/tests/phase2/builtins/oracle/cos.lua new file mode 100644 index 000000000..51293ef21 --- /dev/null +++ b/tests/phase2/builtins/oracle/cos.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({0, 1}) do print(t(math.cos(v))) end diff --git a/tests/phase2/builtins/oracle/cos.lua.out b/tests/phase2/builtins/oracle/cos.lua.out new file mode 100644 index 000000000..c3ad48f15 --- /dev/null +++ b/tests/phase2/builtins/oracle/cos.lua.out @@ -0,0 +1,2 @@ +1.0 float 1 +0.54030230586814 float 0.54030230586813977 diff --git a/tests/phase2/builtins/oracle/divide.lua b/tests/phase2/builtins/oracle/divide.lua new file mode 100644 index 000000000..1c62c1938 --- /dev/null +++ b/tests/phase2/builtins/oracle/divide.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(7 / 2)) +print(t(6 / 3)) diff --git a/tests/phase2/builtins/oracle/divide.lua.out b/tests/phase2/builtins/oracle/divide.lua.out new file mode 100644 index 000000000..4a0408cfd --- /dev/null +++ b/tests/phase2/builtins/oracle/divide.lua.out @@ -0,0 +1,2 @@ +3.5 float 3.5 +2.0 float 2 diff --git a/tests/phase2/builtins/oracle/exp.lua b/tests/phase2/builtins/oracle/exp.lua new file mode 100644 index 000000000..7eabb60d6 --- /dev/null +++ b/tests/phase2/builtins/oracle/exp.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(math.exp(0))) +print(t(math.exp(1))) diff --git a/tests/phase2/builtins/oracle/exp.lua.out b/tests/phase2/builtins/oracle/exp.lua.out new file mode 100644 index 000000000..420f302e4 --- /dev/null +++ b/tests/phase2/builtins/oracle/exp.lua.out @@ -0,0 +1,2 @@ +1.0 float 1 +2.718281828459 float 2.7182818284590451 diff --git a/tests/phase2/builtins/oracle/floor.lua b/tests/phase2/builtins/oracle/floor.lua new file mode 100644 index 000000000..720bda4b6 --- /dev/null +++ b/tests/phase2/builtins/oracle/floor.lua @@ -0,0 +1,4 @@ +local function t(x) return x, math.type(x) end +print(t(math.floor(9007199254740993))) +print(t(math.floor(-2.5))) +print(t(math.floor(1e300))) diff --git a/tests/phase2/builtins/oracle/floor.lua.out b/tests/phase2/builtins/oracle/floor.lua.out new file mode 100644 index 000000000..9edada8c7 --- /dev/null +++ b/tests/phase2/builtins/oracle/floor.lua.out @@ -0,0 +1,3 @@ +9007199254740993 integer +-3 integer +1e+300 float diff --git a/tests/phase2/builtins/oracle/len.lua b/tests/phase2/builtins/oracle/len.lua new file mode 100644 index 000000000..3e7ae9e5c --- /dev/null +++ b/tests/phase2/builtins/oracle/len.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x) end +print(t(#{1, 2})) +print(t(#"hello")) diff --git a/tests/phase2/builtins/oracle/len.lua.out b/tests/phase2/builtins/oracle/len.lua.out new file mode 100644 index 000000000..3725cdf62 --- /dev/null +++ b/tests/phase2/builtins/oracle/len.lua.out @@ -0,0 +1,2 @@ +2 integer +5 integer diff --git a/tests/phase2/builtins/oracle/list_insert_at.lua b/tests/phase2/builtins/oracle/list_insert_at.lua new file mode 100644 index 000000000..3e3d939ee --- /dev/null +++ b/tests/phase2/builtins/oracle/list_insert_at.lua @@ -0,0 +1,5 @@ +-- list_insert_at of [xs, i, v] is 0-based; Lua table.insert(t, i + 1, v) +local t = {1, 2, 3} +table.insert(t, 2, 9); print(table.concat(t, ",")) +print(pcall(table.insert, t, 2.5, 8)) +table.insert(t, 1.0, 7); print(table.concat(t, ",")) diff --git a/tests/phase2/builtins/oracle/list_insert_at.lua.out b/tests/phase2/builtins/oracle/list_insert_at.lua.out new file mode 100644 index 000000000..abba2bfbb --- /dev/null +++ b/tests/phase2/builtins/oracle/list_insert_at.lua.out @@ -0,0 +1,3 @@ +1,9,2,3 +false bad argument #2 to 'table.insert' (number has no integer representation) +7,1,9,2,3 diff --git a/tests/phase2/builtins/oracle/list_remove_at.lua b/tests/phase2/builtins/oracle/list_remove_at.lua new file mode 100644 index 000000000..f0ebf07b9 --- /dev/null +++ b/tests/phase2/builtins/oracle/list_remove_at.lua @@ -0,0 +1,5 @@ +-- list_remove_at of [xs, i] is 0-based; Lua table.remove(t, i + 1) +local t = {1, 2, 3, 4} +table.remove(t, 2); print(table.concat(t, ",")) +print(pcall(table.remove, t, 1.5)) +table.remove(t, 1.0); print(table.concat(t, ",")) diff --git a/tests/phase2/builtins/oracle/list_remove_at.lua.out b/tests/phase2/builtins/oracle/list_remove_at.lua.out new file mode 100644 index 000000000..3d1e998c1 --- /dev/null +++ b/tests/phase2/builtins/oracle/list_remove_at.lua.out @@ -0,0 +1,3 @@ +1,3,4 +false bad argument #2 to 'table.remove' (number has no integer representation) +3,4 diff --git a/tests/phase2/builtins/oracle/log.lua b/tests/phase2/builtins/oracle/log.lua new file mode 100644 index 000000000..db2927fe0 --- /dev/null +++ b/tests/phase2/builtins/oracle/log.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(math.log(1))) +print(t(math.log(8))) diff --git a/tests/phase2/builtins/oracle/log.lua.out b/tests/phase2/builtins/oracle/log.lua.out new file mode 100644 index 000000000..4cdd98208 --- /dev/null +++ b/tests/phase2/builtins/oracle/log.lua.out @@ -0,0 +1,2 @@ +0.0 float 0 +2.0794415416798 float 2.0794415416798357 diff --git a/tests/phase2/builtins/oracle/max.lua b/tests/phase2/builtins/oracle/max.lua new file mode 100644 index 000000000..47d5446ee --- /dev/null +++ b/tests/phase2/builtins/oracle/max.lua @@ -0,0 +1,7 @@ +local function t(x) return x, math.type(x) end +print(t(math.max(9007199254740993, 9007199254740992))) +print(t(math.max(3, 2.5))) +print(t(math.max(2, 2.5))) +print(t(math.max(7))) +print(t(math.max(3, 3.0))) +print(t(math.max(3.0, 3))) diff --git a/tests/phase2/builtins/oracle/max.lua.out b/tests/phase2/builtins/oracle/max.lua.out new file mode 100644 index 000000000..37d56fbb3 --- /dev/null +++ b/tests/phase2/builtins/oracle/max.lua.out @@ -0,0 +1,6 @@ +9007199254740993 integer +3 integer +2.5 float +7 integer +3 integer +3.0 float diff --git a/tests/phase2/builtins/oracle/min.lua b/tests/phase2/builtins/oracle/min.lua new file mode 100644 index 000000000..69033bd3d --- /dev/null +++ b/tests/phase2/builtins/oracle/min.lua @@ -0,0 +1,7 @@ +local function t(x) return x, math.type(x) end +print(t(math.min(9007199254740993, 9007199254740994))) +print(t(math.min(3, 3.5))) +print(t(math.min(4, 3.5))) +print(t(math.min(7))) +print(t(math.min(3, 3.0))) +print(t(math.min(3.0, 3))) diff --git a/tests/phase2/builtins/oracle/min.lua.out b/tests/phase2/builtins/oracle/min.lua.out new file mode 100644 index 000000000..3438e4eeb --- /dev/null +++ b/tests/phase2/builtins/oracle/min.lua.out @@ -0,0 +1,6 @@ +9007199254740993 integer +3 integer +3.5 float +7 integer +3 integer +3.0 float diff --git a/tests/phase2/builtins/oracle/n9-double-rounding.rs.out b/tests/phase2/builtins/oracle/n9-double-rounding.rs.out new file mode 100644 index 000000000..970901761 --- /dev/null +++ b/tests/phase2/builtins/oracle/n9-double-rounding.rs.out @@ -0,0 +1 @@ +fround double-rounding float 43d0000000000000 diff --git a/tests/phase2/builtins/oracle/n9-double-rounding.tsv b/tests/phase2/builtins/oracle/n9-double-rounding.tsv new file mode 100644 index 000000000..fc5b81848 --- /dev/null +++ b/tests/phase2/builtins/oracle/n9-double-rounding.tsv @@ -0,0 +1 @@ +fround double-rounding i:4611686293305294849 diff --git a/tests/phase2/builtins/oracle/n9-map.rs.out b/tests/phase2/builtins/oracle/n9-map.rs.out new file mode 100644 index 000000000..223cc231b --- /dev/null +++ b/tests/phase2/builtins/oracle/n9-map.rs.out @@ -0,0 +1,9 @@ +i64_wrapping_add m0 int -9223372036854775808 +u8_wrapping_add m1 int 0 +u8_checked_add m2 none - +i8_saturating_sub m3 int -128 +u64_saturating_add m4 int -1 +u64_compare m5 int 1 +u64_to_string m6 string 18446744073709551615 +u64_div m7 int 9223372036854775807 +fround m8 float 3fb99999a0000000 diff --git a/tests/phase2/builtins/oracle/n9-map.tsv b/tests/phase2/builtins/oracle/n9-map.tsv new file mode 100644 index 000000000..b83cf873a --- /dev/null +++ b/tests/phase2/builtins/oracle/n9-map.tsv @@ -0,0 +1,9 @@ +i64_wrapping_add m0 9223372036854775807 1 +u8_wrapping_add m1 255 1 +u8_checked_add m2 200 100 +i8_saturating_sub m3 -100 100 +u64_saturating_add m4 -1 1 +u64_compare m5 -1 1 +u64_to_string m6 -1 +u64_div m7 -1 2 +fround m8 f:3fb999999999999a diff --git a/tests/phase2/builtins/oracle/n9.rs b/tests/phase2/builtins/oracle/n9.rs new file mode 100644 index 000000000..c683cf90e --- /dev/null +++ b/tests/phase2/builtins/oracle/n9.rs @@ -0,0 +1,96 @@ +use std::io::{self, BufRead}; +fn main() { + for line in io::stdin().lock().lines() { + let line=line.unwrap(); let f:Vec<&str>=line.split('\t').collect(); + let name=f[0]; let id=f[1]; + if name=="fround" { + let x: f64=if f[2].starts_with("i:") { f[2][2..].parse::().unwrap() as f64 } + else { f64::from_bits(u64::from_str_radix(&f[2][2..],16).unwrap()) }; + println!("{}\t{}\tfloat\t{:016x}",name,id,(x as f32 as f64).to_bits()); continue; + } + let a=f[2].parse::().unwrap(); let b=f.get(3).unwrap_or(&"0").parse::().unwrap(); + let (answer,kind)=match name { + "i8_wrapping_add" => (Some(((a as i8).wrapping_add(b as i8) as i64).to_string()), "int"), + "i8_wrapping_sub" => (Some(((a as i8).wrapping_sub(b as i8) as i64).to_string()), "int"), + "i8_wrapping_mul" => (Some(((a as i8).wrapping_mul(b as i8) as i64).to_string()), "int"), + "i8_checked_add" => ((a as i8).checked_add(b as i8).map(|x| (x as i64).to_string()), "int"), + "i8_checked_sub" => ((a as i8).checked_sub(b as i8).map(|x| (x as i64).to_string()), "int"), + "i8_checked_mul" => ((a as i8).checked_mul(b as i8).map(|x| (x as i64).to_string()), "int"), + "i8_saturating_add" => (Some(((a as i8).saturating_add(b as i8) as i64).to_string()), "int"), + "i8_saturating_sub" => (Some(((a as i8).saturating_sub(b as i8) as i64).to_string()), "int"), + "i8_saturating_mul" => (Some(((a as i8).saturating_mul(b as i8) as i64).to_string()), "int"), + "i16_wrapping_add" => (Some(((a as i16).wrapping_add(b as i16) as i64).to_string()), "int"), + "i16_wrapping_sub" => (Some(((a as i16).wrapping_sub(b as i16) as i64).to_string()), "int"), + "i16_wrapping_mul" => (Some(((a as i16).wrapping_mul(b as i16) as i64).to_string()), "int"), + "i16_checked_add" => ((a as i16).checked_add(b as i16).map(|x| (x as i64).to_string()), "int"), + "i16_checked_sub" => ((a as i16).checked_sub(b as i16).map(|x| (x as i64).to_string()), "int"), + "i16_checked_mul" => ((a as i16).checked_mul(b as i16).map(|x| (x as i64).to_string()), "int"), + "i16_saturating_add" => (Some(((a as i16).saturating_add(b as i16) as i64).to_string()), "int"), + "i16_saturating_sub" => (Some(((a as i16).saturating_sub(b as i16) as i64).to_string()), "int"), + "i16_saturating_mul" => (Some(((a as i16).saturating_mul(b as i16) as i64).to_string()), "int"), + "i32_wrapping_add" => (Some(((a as i32).wrapping_add(b as i32) as i64).to_string()), "int"), + "i32_wrapping_sub" => (Some(((a as i32).wrapping_sub(b as i32) as i64).to_string()), "int"), + "i32_wrapping_mul" => (Some(((a as i32).wrapping_mul(b as i32) as i64).to_string()), "int"), + "i32_checked_add" => ((a as i32).checked_add(b as i32).map(|x| (x as i64).to_string()), "int"), + "i32_checked_sub" => ((a as i32).checked_sub(b as i32).map(|x| (x as i64).to_string()), "int"), + "i32_checked_mul" => ((a as i32).checked_mul(b as i32).map(|x| (x as i64).to_string()), "int"), + "i32_saturating_add" => (Some(((a as i32).saturating_add(b as i32) as i64).to_string()), "int"), + "i32_saturating_sub" => (Some(((a as i32).saturating_sub(b as i32) as i64).to_string()), "int"), + "i32_saturating_mul" => (Some(((a as i32).saturating_mul(b as i32) as i64).to_string()), "int"), + "i64_wrapping_add" => (Some(((a as i64).wrapping_add(b as i64) as i64).to_string()), "int"), + "i64_wrapping_sub" => (Some(((a as i64).wrapping_sub(b as i64) as i64).to_string()), "int"), + "i64_wrapping_mul" => (Some(((a as i64).wrapping_mul(b as i64) as i64).to_string()), "int"), + "i64_checked_add" => ((a as i64).checked_add(b as i64).map(|x| (x as i64).to_string()), "int"), + "i64_checked_sub" => ((a as i64).checked_sub(b as i64).map(|x| (x as i64).to_string()), "int"), + "i64_checked_mul" => ((a as i64).checked_mul(b as i64).map(|x| (x as i64).to_string()), "int"), + "i64_saturating_add" => (Some(((a as i64).saturating_add(b as i64) as i64).to_string()), "int"), + "i64_saturating_sub" => (Some(((a as i64).saturating_sub(b as i64) as i64).to_string()), "int"), + "i64_saturating_mul" => (Some(((a as i64).saturating_mul(b as i64) as i64).to_string()), "int"), + "u8_wrapping_add" => (Some(((a as u8).wrapping_add(b as u8) as i64).to_string()), "int"), + "u8_wrapping_sub" => (Some(((a as u8).wrapping_sub(b as u8) as i64).to_string()), "int"), + "u8_wrapping_mul" => (Some(((a as u8).wrapping_mul(b as u8) as i64).to_string()), "int"), + "u8_checked_add" => ((a as u8).checked_add(b as u8).map(|x| (x as i64).to_string()), "int"), + "u8_checked_sub" => ((a as u8).checked_sub(b as u8).map(|x| (x as i64).to_string()), "int"), + "u8_checked_mul" => ((a as u8).checked_mul(b as u8).map(|x| (x as i64).to_string()), "int"), + "u8_saturating_add" => (Some(((a as u8).saturating_add(b as u8) as i64).to_string()), "int"), + "u8_saturating_sub" => (Some(((a as u8).saturating_sub(b as u8) as i64).to_string()), "int"), + "u8_saturating_mul" => (Some(((a as u8).saturating_mul(b as u8) as i64).to_string()), "int"), + "u16_wrapping_add" => (Some(((a as u16).wrapping_add(b as u16) as i64).to_string()), "int"), + "u16_wrapping_sub" => (Some(((a as u16).wrapping_sub(b as u16) as i64).to_string()), "int"), + "u16_wrapping_mul" => (Some(((a as u16).wrapping_mul(b as u16) as i64).to_string()), "int"), + "u16_checked_add" => ((a as u16).checked_add(b as u16).map(|x| (x as i64).to_string()), "int"), + "u16_checked_sub" => ((a as u16).checked_sub(b as u16).map(|x| (x as i64).to_string()), "int"), + "u16_checked_mul" => ((a as u16).checked_mul(b as u16).map(|x| (x as i64).to_string()), "int"), + "u16_saturating_add" => (Some(((a as u16).saturating_add(b as u16) as i64).to_string()), "int"), + "u16_saturating_sub" => (Some(((a as u16).saturating_sub(b as u16) as i64).to_string()), "int"), + "u16_saturating_mul" => (Some(((a as u16).saturating_mul(b as u16) as i64).to_string()), "int"), + "u32_wrapping_add" => (Some(((a as u32).wrapping_add(b as u32) as i64).to_string()), "int"), + "u32_wrapping_sub" => (Some(((a as u32).wrapping_sub(b as u32) as i64).to_string()), "int"), + "u32_wrapping_mul" => (Some(((a as u32).wrapping_mul(b as u32) as i64).to_string()), "int"), + "u32_checked_add" => ((a as u32).checked_add(b as u32).map(|x| (x as i64).to_string()), "int"), + "u32_checked_sub" => ((a as u32).checked_sub(b as u32).map(|x| (x as i64).to_string()), "int"), + "u32_checked_mul" => ((a as u32).checked_mul(b as u32).map(|x| (x as i64).to_string()), "int"), + "u32_saturating_add" => (Some(((a as u32).saturating_add(b as u32) as i64).to_string()), "int"), + "u32_saturating_sub" => (Some(((a as u32).saturating_sub(b as u32) as i64).to_string()), "int"), + "u32_saturating_mul" => (Some(((a as u32).saturating_mul(b as u32) as i64).to_string()), "int"), + "u64_wrapping_add" => (Some(((a as u64).wrapping_add(b as u64) as i64).to_string()), "int"), + "u64_wrapping_sub" => (Some(((a as u64).wrapping_sub(b as u64) as i64).to_string()), "int"), + "u64_wrapping_mul" => (Some(((a as u64).wrapping_mul(b as u64) as i64).to_string()), "int"), + "u64_checked_add" => ((a as u64).checked_add(b as u64).map(|x| (x as i64).to_string()), "int"), + "u64_checked_sub" => ((a as u64).checked_sub(b as u64).map(|x| (x as i64).to_string()), "int"), + "u64_checked_mul" => ((a as u64).checked_mul(b as u64).map(|x| (x as i64).to_string()), "int"), + "u64_saturating_add" => (Some(((a as u64).saturating_add(b as u64) as i64).to_string()), "int"), + "u64_saturating_sub" => (Some(((a as u64).saturating_sub(b as u64) as i64).to_string()), "int"), + "u64_saturating_mul" => (Some(((a as u64).saturating_mul(b as u64) as i64).to_string()), "int"), + "u64_compare" => (Some(((a as u64).cmp(&(b as u64)) as i8).to_string()), "int"), + "u64_div" => ((a as u64).checked_div(b as u64).map(|x| (x as i64).to_string()), "int"), + "u64_rem" => ((a as u64).checked_rem(b as u64).map(|x| (x as i64).to_string()), "int"), + "u64_to_string" => (Some((a as u64).to_string()), "string"), + _ => panic!("unknown op {}",name) + }; + match answer { + Some(value)=>println!("{}\t{}\t{}\t{}",name,id,kind,value), + None=>println!("{}\t{}\tnone\t-",name,id) + } + } +} diff --git a/tests/phase2/builtins/oracle/n9.rs.out b/tests/phase2/builtins/oracle/n9.rs.out new file mode 100644 index 000000000..f1fc57c88 --- /dev/null +++ b/tests/phase2/builtins/oracle/n9.rs.out @@ -0,0 +1,240 @@ +i8_wrapping_add 0 int 2 +i8_wrapping_add 1 int -128 +i8_wrapping_add 2 int 127 +i8_wrapping_sub 0 int 0 +i8_wrapping_sub 1 int 127 +i8_wrapping_sub 2 int -128 +i8_wrapping_mul 0 int 1 +i8_wrapping_mul 1 int -2 +i8_wrapping_mul 2 int -128 +i8_checked_add 0 int 2 +i8_checked_add 1 none - +i8_checked_add 2 none - +i8_checked_sub 0 int 0 +i8_checked_sub 1 none - +i8_checked_sub 2 none - +i8_checked_mul 0 int 1 +i8_checked_mul 1 none - +i8_checked_mul 2 none - +i8_saturating_add 0 int 2 +i8_saturating_add 1 int 127 +i8_saturating_add 2 int -128 +i8_saturating_sub 0 int 0 +i8_saturating_sub 1 int -128 +i8_saturating_sub 2 int 127 +i8_saturating_mul 0 int 1 +i8_saturating_mul 1 int 127 +i8_saturating_mul 2 int 127 +i16_wrapping_add 0 int 2 +i16_wrapping_add 1 int -32768 +i16_wrapping_add 2 int 32767 +i16_wrapping_sub 0 int 0 +i16_wrapping_sub 1 int 32767 +i16_wrapping_sub 2 int -32768 +i16_wrapping_mul 0 int 1 +i16_wrapping_mul 1 int -2 +i16_wrapping_mul 2 int -32768 +i16_checked_add 0 int 2 +i16_checked_add 1 none - +i16_checked_add 2 none - +i16_checked_sub 0 int 0 +i16_checked_sub 1 none - +i16_checked_sub 2 none - +i16_checked_mul 0 int 1 +i16_checked_mul 1 none - +i16_checked_mul 2 none - +i16_saturating_add 0 int 2 +i16_saturating_add 1 int 32767 +i16_saturating_add 2 int -32768 +i16_saturating_sub 0 int 0 +i16_saturating_sub 1 int -32768 +i16_saturating_sub 2 int 32767 +i16_saturating_mul 0 int 1 +i16_saturating_mul 1 int 32767 +i16_saturating_mul 2 int 32767 +i32_wrapping_add 0 int 2 +i32_wrapping_add 1 int -2147483648 +i32_wrapping_add 2 int 2147483647 +i32_wrapping_sub 0 int 0 +i32_wrapping_sub 1 int 2147483647 +i32_wrapping_sub 2 int -2147483648 +i32_wrapping_mul 0 int 1 +i32_wrapping_mul 1 int -2 +i32_wrapping_mul 2 int -2147483648 +i32_checked_add 0 int 2 +i32_checked_add 1 none - +i32_checked_add 2 none - +i32_checked_sub 0 int 0 +i32_checked_sub 1 none - +i32_checked_sub 2 none - +i32_checked_mul 0 int 1 +i32_checked_mul 1 none - +i32_checked_mul 2 none - +i32_saturating_add 0 int 2 +i32_saturating_add 1 int 2147483647 +i32_saturating_add 2 int -2147483648 +i32_saturating_sub 0 int 0 +i32_saturating_sub 1 int -2147483648 +i32_saturating_sub 2 int 2147483647 +i32_saturating_mul 0 int 1 +i32_saturating_mul 1 int 2147483647 +i32_saturating_mul 2 int 2147483647 +i64_wrapping_add 0 int 2 +i64_wrapping_add 1 int -9223372036854775808 +i64_wrapping_add 2 int 9223372036854775807 +i64_wrapping_sub 0 int 0 +i64_wrapping_sub 1 int 9223372036854775807 +i64_wrapping_sub 2 int -9223372036854775808 +i64_wrapping_mul 0 int 1 +i64_wrapping_mul 1 int -2 +i64_wrapping_mul 2 int -9223372036854775808 +i64_checked_add 0 int 2 +i64_checked_add 1 none - +i64_checked_add 2 none - +i64_checked_sub 0 int 0 +i64_checked_sub 1 none - +i64_checked_sub 2 none - +i64_checked_mul 0 int 1 +i64_checked_mul 1 none - +i64_checked_mul 2 none - +i64_saturating_add 0 int 2 +i64_saturating_add 1 int 9223372036854775807 +i64_saturating_add 2 int -9223372036854775808 +i64_saturating_sub 0 int 0 +i64_saturating_sub 1 int -9223372036854775808 +i64_saturating_sub 2 int 9223372036854775807 +i64_saturating_mul 0 int 1 +i64_saturating_mul 1 int 9223372036854775807 +i64_saturating_mul 2 int 9223372036854775807 +u8_wrapping_add 0 int 2 +u8_wrapping_add 1 int 0 +u8_wrapping_add 2 int 2 +u8_wrapping_sub 0 int 0 +u8_wrapping_sub 1 int 255 +u8_wrapping_sub 2 int 254 +u8_wrapping_mul 0 int 1 +u8_wrapping_mul 1 int 254 +u8_wrapping_mul 2 int 0 +u8_checked_add 0 int 2 +u8_checked_add 1 none - +u8_checked_add 2 int 2 +u8_checked_sub 0 int 0 +u8_checked_sub 1 none - +u8_checked_sub 2 none - +u8_checked_mul 0 int 1 +u8_checked_mul 1 none - +u8_checked_mul 2 int 0 +u8_saturating_add 0 int 2 +u8_saturating_add 1 int 255 +u8_saturating_add 2 int 2 +u8_saturating_sub 0 int 0 +u8_saturating_sub 1 int 0 +u8_saturating_sub 2 int 0 +u8_saturating_mul 0 int 1 +u8_saturating_mul 1 int 255 +u8_saturating_mul 2 int 0 +u16_wrapping_add 0 int 2 +u16_wrapping_add 1 int 0 +u16_wrapping_add 2 int 2 +u16_wrapping_sub 0 int 0 +u16_wrapping_sub 1 int 65535 +u16_wrapping_sub 2 int 65534 +u16_wrapping_mul 0 int 1 +u16_wrapping_mul 1 int 65534 +u16_wrapping_mul 2 int 0 +u16_checked_add 0 int 2 +u16_checked_add 1 none - +u16_checked_add 2 int 2 +u16_checked_sub 0 int 0 +u16_checked_sub 1 none - +u16_checked_sub 2 none - +u16_checked_mul 0 int 1 +u16_checked_mul 1 none - +u16_checked_mul 2 int 0 +u16_saturating_add 0 int 2 +u16_saturating_add 1 int 65535 +u16_saturating_add 2 int 2 +u16_saturating_sub 0 int 0 +u16_saturating_sub 1 int 0 +u16_saturating_sub 2 int 0 +u16_saturating_mul 0 int 1 +u16_saturating_mul 1 int 65535 +u16_saturating_mul 2 int 0 +u32_wrapping_add 0 int 2 +u32_wrapping_add 1 int 0 +u32_wrapping_add 2 int 2 +u32_wrapping_sub 0 int 0 +u32_wrapping_sub 1 int 4294967295 +u32_wrapping_sub 2 int 4294967294 +u32_wrapping_mul 0 int 1 +u32_wrapping_mul 1 int 4294967294 +u32_wrapping_mul 2 int 0 +u32_checked_add 0 int 2 +u32_checked_add 1 none - +u32_checked_add 2 int 2 +u32_checked_sub 0 int 0 +u32_checked_sub 1 none - +u32_checked_sub 2 none - +u32_checked_mul 0 int 1 +u32_checked_mul 1 none - +u32_checked_mul 2 int 0 +u32_saturating_add 0 int 2 +u32_saturating_add 1 int 4294967295 +u32_saturating_add 2 int 2 +u32_saturating_sub 0 int 0 +u32_saturating_sub 1 int 0 +u32_saturating_sub 2 int 0 +u32_saturating_mul 0 int 1 +u32_saturating_mul 1 int 4294967295 +u32_saturating_mul 2 int 0 +u64_wrapping_add 0 int 2 +u64_wrapping_add 1 int 0 +u64_wrapping_add 2 int 2 +u64_wrapping_sub 0 int 0 +u64_wrapping_sub 1 int -1 +u64_wrapping_sub 2 int -2 +u64_wrapping_mul 0 int 1 +u64_wrapping_mul 1 int -2 +u64_wrapping_mul 2 int 0 +u64_checked_add 0 int 2 +u64_checked_add 1 none - +u64_checked_add 2 int 2 +u64_checked_sub 0 int 0 +u64_checked_sub 1 none - +u64_checked_sub 2 none - +u64_checked_mul 0 int 1 +u64_checked_mul 1 none - +u64_checked_mul 2 int 0 +u64_saturating_add 0 int 2 +u64_saturating_add 1 int -1 +u64_saturating_add 2 int 2 +u64_saturating_sub 0 int 0 +u64_saturating_sub 1 int 0 +u64_saturating_sub 2 int 0 +u64_saturating_mul 0 int 1 +u64_saturating_mul 1 int -1 +u64_saturating_mul 2 int 0 +u64_compare 0 int 1 +u64_compare 1 int 1 +u64_compare 2 int 1 +u64_compare 3 int 1 +u64_compare 4 int -1 +u64_div 0 int 2 +u64_div 1 int -1 +u64_div 2 int 9223372036854775807 +u64_div 3 int 1 +u64_div 4 int 0 +u64_rem 0 int 0 +u64_rem 1 int 0 +u64_rem 2 int 1 +u64_rem 3 int 1 +u64_rem 4 int 1 +u64_to_string 0 string 1 +u64_to_string 1 string 18446744073709551615 +u64_to_string 2 string 9223372036854775808 +u64_to_string 3 string 9223372036854775807 +fround 0 float 3fb99999a0000000 +fround 1 float 7ff0000000000000 +fround 2 float fff0000000000000 +fround 3 float 4170000000000000 +fround 4 float 4340000000000000 diff --git a/tests/phase2/builtins/oracle/n9.tsv b/tests/phase2/builtins/oracle/n9.tsv new file mode 100644 index 000000000..802ab102d --- /dev/null +++ b/tests/phase2/builtins/oracle/n9.tsv @@ -0,0 +1,240 @@ +i8_wrapping_add 0 1 1 +i8_wrapping_add 1 127 1 +i8_wrapping_add 2 -128 -1 +i8_wrapping_sub 0 1 1 +i8_wrapping_sub 1 -128 1 +i8_wrapping_sub 2 127 -1 +i8_wrapping_mul 0 1 1 +i8_wrapping_mul 1 127 2 +i8_wrapping_mul 2 -128 -1 +i8_checked_add 0 1 1 +i8_checked_add 1 127 1 +i8_checked_add 2 -128 -1 +i8_checked_sub 0 1 1 +i8_checked_sub 1 -128 1 +i8_checked_sub 2 127 -1 +i8_checked_mul 0 1 1 +i8_checked_mul 1 127 2 +i8_checked_mul 2 -128 -1 +i8_saturating_add 0 1 1 +i8_saturating_add 1 127 1 +i8_saturating_add 2 -128 -1 +i8_saturating_sub 0 1 1 +i8_saturating_sub 1 -128 1 +i8_saturating_sub 2 127 -1 +i8_saturating_mul 0 1 1 +i8_saturating_mul 1 127 2 +i8_saturating_mul 2 -128 -1 +i16_wrapping_add 0 1 1 +i16_wrapping_add 1 32767 1 +i16_wrapping_add 2 -32768 -1 +i16_wrapping_sub 0 1 1 +i16_wrapping_sub 1 -32768 1 +i16_wrapping_sub 2 32767 -1 +i16_wrapping_mul 0 1 1 +i16_wrapping_mul 1 32767 2 +i16_wrapping_mul 2 -32768 -1 +i16_checked_add 0 1 1 +i16_checked_add 1 32767 1 +i16_checked_add 2 -32768 -1 +i16_checked_sub 0 1 1 +i16_checked_sub 1 -32768 1 +i16_checked_sub 2 32767 -1 +i16_checked_mul 0 1 1 +i16_checked_mul 1 32767 2 +i16_checked_mul 2 -32768 -1 +i16_saturating_add 0 1 1 +i16_saturating_add 1 32767 1 +i16_saturating_add 2 -32768 -1 +i16_saturating_sub 0 1 1 +i16_saturating_sub 1 -32768 1 +i16_saturating_sub 2 32767 -1 +i16_saturating_mul 0 1 1 +i16_saturating_mul 1 32767 2 +i16_saturating_mul 2 -32768 -1 +i32_wrapping_add 0 1 1 +i32_wrapping_add 1 2147483647 1 +i32_wrapping_add 2 -2147483648 -1 +i32_wrapping_sub 0 1 1 +i32_wrapping_sub 1 -2147483648 1 +i32_wrapping_sub 2 2147483647 -1 +i32_wrapping_mul 0 1 1 +i32_wrapping_mul 1 2147483647 2 +i32_wrapping_mul 2 -2147483648 -1 +i32_checked_add 0 1 1 +i32_checked_add 1 2147483647 1 +i32_checked_add 2 -2147483648 -1 +i32_checked_sub 0 1 1 +i32_checked_sub 1 -2147483648 1 +i32_checked_sub 2 2147483647 -1 +i32_checked_mul 0 1 1 +i32_checked_mul 1 2147483647 2 +i32_checked_mul 2 -2147483648 -1 +i32_saturating_add 0 1 1 +i32_saturating_add 1 2147483647 1 +i32_saturating_add 2 -2147483648 -1 +i32_saturating_sub 0 1 1 +i32_saturating_sub 1 -2147483648 1 +i32_saturating_sub 2 2147483647 -1 +i32_saturating_mul 0 1 1 +i32_saturating_mul 1 2147483647 2 +i32_saturating_mul 2 -2147483648 -1 +i64_wrapping_add 0 1 1 +i64_wrapping_add 1 9223372036854775807 1 +i64_wrapping_add 2 -9223372036854775808 -1 +i64_wrapping_sub 0 1 1 +i64_wrapping_sub 1 -9223372036854775808 1 +i64_wrapping_sub 2 9223372036854775807 -1 +i64_wrapping_mul 0 1 1 +i64_wrapping_mul 1 9223372036854775807 2 +i64_wrapping_mul 2 -9223372036854775808 -1 +i64_checked_add 0 1 1 +i64_checked_add 1 9223372036854775807 1 +i64_checked_add 2 -9223372036854775808 -1 +i64_checked_sub 0 1 1 +i64_checked_sub 1 -9223372036854775808 1 +i64_checked_sub 2 9223372036854775807 -1 +i64_checked_mul 0 1 1 +i64_checked_mul 1 9223372036854775807 2 +i64_checked_mul 2 -9223372036854775808 -1 +i64_saturating_add 0 1 1 +i64_saturating_add 1 9223372036854775807 1 +i64_saturating_add 2 -9223372036854775808 -1 +i64_saturating_sub 0 1 1 +i64_saturating_sub 1 -9223372036854775808 1 +i64_saturating_sub 2 9223372036854775807 -1 +i64_saturating_mul 0 1 1 +i64_saturating_mul 1 9223372036854775807 2 +i64_saturating_mul 2 -9223372036854775808 -1 +u8_wrapping_add 0 1 1 +u8_wrapping_add 1 255 1 +u8_wrapping_add 2 0 2 +u8_wrapping_sub 0 1 1 +u8_wrapping_sub 1 0 1 +u8_wrapping_sub 2 0 2 +u8_wrapping_mul 0 1 1 +u8_wrapping_mul 1 255 2 +u8_wrapping_mul 2 0 2 +u8_checked_add 0 1 1 +u8_checked_add 1 255 1 +u8_checked_add 2 0 2 +u8_checked_sub 0 1 1 +u8_checked_sub 1 0 1 +u8_checked_sub 2 0 2 +u8_checked_mul 0 1 1 +u8_checked_mul 1 255 2 +u8_checked_mul 2 0 2 +u8_saturating_add 0 1 1 +u8_saturating_add 1 255 1 +u8_saturating_add 2 0 2 +u8_saturating_sub 0 1 1 +u8_saturating_sub 1 0 1 +u8_saturating_sub 2 0 2 +u8_saturating_mul 0 1 1 +u8_saturating_mul 1 255 2 +u8_saturating_mul 2 0 2 +u16_wrapping_add 0 1 1 +u16_wrapping_add 1 65535 1 +u16_wrapping_add 2 0 2 +u16_wrapping_sub 0 1 1 +u16_wrapping_sub 1 0 1 +u16_wrapping_sub 2 0 2 +u16_wrapping_mul 0 1 1 +u16_wrapping_mul 1 65535 2 +u16_wrapping_mul 2 0 2 +u16_checked_add 0 1 1 +u16_checked_add 1 65535 1 +u16_checked_add 2 0 2 +u16_checked_sub 0 1 1 +u16_checked_sub 1 0 1 +u16_checked_sub 2 0 2 +u16_checked_mul 0 1 1 +u16_checked_mul 1 65535 2 +u16_checked_mul 2 0 2 +u16_saturating_add 0 1 1 +u16_saturating_add 1 65535 1 +u16_saturating_add 2 0 2 +u16_saturating_sub 0 1 1 +u16_saturating_sub 1 0 1 +u16_saturating_sub 2 0 2 +u16_saturating_mul 0 1 1 +u16_saturating_mul 1 65535 2 +u16_saturating_mul 2 0 2 +u32_wrapping_add 0 1 1 +u32_wrapping_add 1 4294967295 1 +u32_wrapping_add 2 0 2 +u32_wrapping_sub 0 1 1 +u32_wrapping_sub 1 0 1 +u32_wrapping_sub 2 0 2 +u32_wrapping_mul 0 1 1 +u32_wrapping_mul 1 4294967295 2 +u32_wrapping_mul 2 0 2 +u32_checked_add 0 1 1 +u32_checked_add 1 4294967295 1 +u32_checked_add 2 0 2 +u32_checked_sub 0 1 1 +u32_checked_sub 1 0 1 +u32_checked_sub 2 0 2 +u32_checked_mul 0 1 1 +u32_checked_mul 1 4294967295 2 +u32_checked_mul 2 0 2 +u32_saturating_add 0 1 1 +u32_saturating_add 1 4294967295 1 +u32_saturating_add 2 0 2 +u32_saturating_sub 0 1 1 +u32_saturating_sub 1 0 1 +u32_saturating_sub 2 0 2 +u32_saturating_mul 0 1 1 +u32_saturating_mul 1 4294967295 2 +u32_saturating_mul 2 0 2 +u64_wrapping_add 0 1 1 +u64_wrapping_add 1 -1 1 +u64_wrapping_add 2 0 2 +u64_wrapping_sub 0 1 1 +u64_wrapping_sub 1 0 1 +u64_wrapping_sub 2 0 2 +u64_wrapping_mul 0 1 1 +u64_wrapping_mul 1 -1 2 +u64_wrapping_mul 2 0 2 +u64_checked_add 0 1 1 +u64_checked_add 1 -1 1 +u64_checked_add 2 0 2 +u64_checked_sub 0 1 1 +u64_checked_sub 1 0 1 +u64_checked_sub 2 0 2 +u64_checked_mul 0 1 1 +u64_checked_mul 1 -1 2 +u64_checked_mul 2 0 2 +u64_saturating_add 0 1 1 +u64_saturating_add 1 -1 1 +u64_saturating_add 2 0 2 +u64_saturating_sub 0 1 1 +u64_saturating_sub 1 0 1 +u64_saturating_sub 2 0 2 +u64_saturating_mul 0 1 1 +u64_saturating_mul 1 -1 2 +u64_saturating_mul 2 0 2 +u64_compare 0 4 2 +u64_compare 1 -1 1 +u64_compare 2 -1 2 +u64_compare 3 -9223372036854775808 9223372036854775807 +u64_compare 4 1 -1 +u64_div 0 4 2 +u64_div 1 -1 1 +u64_div 2 -1 2 +u64_div 3 -9223372036854775808 9223372036854775807 +u64_div 4 1 -1 +u64_rem 0 4 2 +u64_rem 1 -1 1 +u64_rem 2 -1 2 +u64_rem 3 -9223372036854775808 9223372036854775807 +u64_rem 4 1 -1 +u64_to_string 0 1 +u64_to_string 1 -1 +u64_to_string 2 -9223372036854775808 +u64_to_string 3 9223372036854775807 +fround 0 f:3fb999999999999a +fround 1 f:48078287f49c4a1d +fround 2 f:c8078287f49c4a1d +fround 3 i:16777217 +fround 4 i:9007199254740993 diff --git a/tests/phase2/builtins/oracle/num.lua b/tests/phase2/builtins/oracle/num.lua new file mode 100644 index 000000000..c90f4b783 --- /dev/null +++ b/tests/phase2/builtins/oracle/num.lua @@ -0,0 +1,4 @@ +for _, s in ipairs({"3", "3.0", "9007199254740993", "9223372036854775808", " 12 ", "+5", "-0x10", "0x1F", "0X1f", "0xFFFFFFFFFFFFFFFF", ".5", "1.", "1e5", "12abc", "0b101", "0o17", "1_000", "", " ", "inf", "nan", "1.2.3", "0x", "0x1p4", "-9223372036854775808"}) do + local v = tonumber(s) + print(string.format("%q", s), v, math.type(v)) +end diff --git a/tests/phase2/builtins/oracle/num.lua.out b/tests/phase2/builtins/oracle/num.lua.out new file mode 100644 index 000000000..326215990 --- /dev/null +++ b/tests/phase2/builtins/oracle/num.lua.out @@ -0,0 +1,25 @@ +"3" 3 integer +"3.0" 3.0 float +"9007199254740993" 9007199254740993 integer +"9223372036854775808" 9.2233720368548e+18 float +" 12 " 12 integer +"+5" 5 integer +"-0x10" -16 integer +"0x1F" 31 integer +"0X1f" 31 integer +"0xFFFFFFFFFFFFFFFF" -1 integer +".5" 0.5 float +"1." 1.0 float +"1e5" 100000.0 float +"12abc" nil nil +"0b101" nil nil +"0o17" nil nil +"1_000" nil nil +"" nil nil +" " nil nil +"inf" nil nil +"nan" nil nil +"1.2.3" nil nil +"0x" nil nil +"0x1p4" 16.0 float +"-9223372036854775808" -9223372036854775808 integer diff --git a/tests/phase2/builtins/oracle/ord.lua b/tests/phase2/builtins/oracle/ord.lua new file mode 100644 index 000000000..502b9cee8 --- /dev/null +++ b/tests/phase2/builtins/oracle/ord.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x) end +print(t(string.byte("A"))) +print(select("#", string.byte(""))) diff --git a/tests/phase2/builtins/oracle/ord.lua.out b/tests/phase2/builtins/oracle/ord.lua.out new file mode 100644 index 000000000..0f684a34f --- /dev/null +++ b/tests/phase2/builtins/oracle/ord.lua.out @@ -0,0 +1,2 @@ +65 integer +0 diff --git a/tests/phase2/builtins/oracle/pi.lua b/tests/phase2/builtins/oracle/pi.lua new file mode 100644 index 000000000..009c0c0fd --- /dev/null +++ b/tests/phase2/builtins/oracle/pi.lua @@ -0,0 +1 @@ +print(math.pi, math.type(math.pi), string.format("%.17g", math.pi)) diff --git a/tests/phase2/builtins/oracle/pi.lua.out b/tests/phase2/builtins/oracle/pi.lua.out new file mode 100644 index 000000000..b017684c9 --- /dev/null +++ b/tests/phase2/builtins/oracle/pi.lua.out @@ -0,0 +1 @@ +3.1415926535898 float 3.1415926535897931 diff --git a/tests/phase2/builtins/oracle/pow.lua b/tests/phase2/builtins/oracle/pow.lua new file mode 100644 index 000000000..bf8e58f58 --- /dev/null +++ b/tests/phase2/builtins/oracle/pow.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(2 ^ 10)) +print(t(2 ^ 0.5)) diff --git a/tests/phase2/builtins/oracle/pow.lua.out b/tests/phase2/builtins/oracle/pow.lua.out new file mode 100644 index 000000000..86da51777 --- /dev/null +++ b/tests/phase2/builtins/oracle/pow.lua.out @@ -0,0 +1,2 @@ +1024.0 float 1024 +1.4142135623731 float 1.4142135623730951 diff --git a/tests/phase2/builtins/oracle/random.lua b/tests/phase2/builtins/oracle/random.lua new file mode 100644 index 000000000..5d21f2482 --- /dev/null +++ b/tests/phase2/builtins/oracle/random.lua @@ -0,0 +1,3 @@ +math.randomseed(7) +local r = math.random() +print(math.type(r), r >= 0, r < 1) diff --git a/tests/phase2/builtins/oracle/random.lua.out b/tests/phase2/builtins/oracle/random.lua.out new file mode 100644 index 000000000..faec3d335 --- /dev/null +++ b/tests/phase2/builtins/oracle/random.lua.out @@ -0,0 +1 @@ +float true true diff --git a/tests/phase2/builtins/oracle/random_int.lua b/tests/phase2/builtins/oracle/random_int.lua new file mode 100644 index 000000000..d5a348ef2 --- /dev/null +++ b/tests/phase2/builtins/oracle/random_int.lua @@ -0,0 +1,6 @@ +local function t(x) return x, math.type(x) end +math.randomseed(7) +print(t(math.random(5, 5))) +print(t(math.random(9007199254740993, 9007199254740993))) +print(pcall(math.random, 1.5, 3)) +print(t(math.random(2.0, 2.0))) diff --git a/tests/phase2/builtins/oracle/random_int.lua.out b/tests/phase2/builtins/oracle/random_int.lua.out new file mode 100644 index 000000000..ae98dd1e5 --- /dev/null +++ b/tests/phase2/builtins/oracle/random_int.lua.out @@ -0,0 +1,4 @@ +5 integer +9007199254740993 integer +false bad argument #1 to 'math.random' (number has no integer representation) +2 integer diff --git a/tests/phase2/builtins/oracle/seed_random.lua b/tests/phase2/builtins/oracle/seed_random.lua new file mode 100644 index 000000000..a875a12b7 --- /dev/null +++ b/tests/phase2/builtins/oracle/seed_random.lua @@ -0,0 +1,7 @@ +math.randomseed(9007199254740993); local a = math.random(0, 1000000) +math.randomseed(9007199254740992); local b = math.random(0, 1000000) +print("seeds-2^53+1-and-2^53-differ", a ~= b) +print(pcall(math.randomseed, 1.5)) +math.randomseed(42.0); local x = math.random(0, 1000000) +math.randomseed(42); local y = math.random(0, 1000000) +print("integral-float-seed", x == y) diff --git a/tests/phase2/builtins/oracle/seed_random.lua.out b/tests/phase2/builtins/oracle/seed_random.lua.out new file mode 100644 index 000000000..534468cf6 --- /dev/null +++ b/tests/phase2/builtins/oracle/seed_random.lua.out @@ -0,0 +1,3 @@ +seeds-2^53+1-and-2^53-differ true +false bad argument #1 to 'math.randomseed' (number has no integer representation) +integral-float-seed true diff --git a/tests/phase2/builtins/oracle/sin.lua b/tests/phase2/builtins/oracle/sin.lua new file mode 100644 index 000000000..55b636d09 --- /dev/null +++ b/tests/phase2/builtins/oracle/sin.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({0, 1}) do print(t(math.sin(v))) end diff --git a/tests/phase2/builtins/oracle/sin.lua.out b/tests/phase2/builtins/oracle/sin.lua.out new file mode 100644 index 000000000..380044b61 --- /dev/null +++ b/tests/phase2/builtins/oracle/sin.lua.out @@ -0,0 +1,2 @@ +0.0 float 0 +0.8414709848079 float 0.8414709848078965 diff --git a/tests/phase2/builtins/oracle/sqrt.lua b/tests/phase2/builtins/oracle/sqrt.lua new file mode 100644 index 000000000..f53998328 --- /dev/null +++ b/tests/phase2/builtins/oracle/sqrt.lua @@ -0,0 +1,3 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +print(t(math.sqrt(4))) +print(t(math.sqrt(2))) diff --git a/tests/phase2/builtins/oracle/sqrt.lua.out b/tests/phase2/builtins/oracle/sqrt.lua.out new file mode 100644 index 000000000..d908807c4 --- /dev/null +++ b/tests/phase2/builtins/oracle/sqrt.lua.out @@ -0,0 +1,2 @@ +2.0 float 2 +1.4142135623731 float 1.4142135623730951 diff --git a/tests/phase2/builtins/oracle/str_from_bytes.lua b/tests/phase2/builtins/oracle/str_from_bytes.lua new file mode 100644 index 000000000..8708d8ba5 --- /dev/null +++ b/tests/phase2/builtins/oracle/str_from_bytes.lua @@ -0,0 +1,4 @@ +print(string.char(72, 105)) +print(pcall(string.char, 65.5)) +print(pcall(string.char, 328)) +print(string.char(72.0, 105.0)) diff --git a/tests/phase2/builtins/oracle/str_from_bytes.lua.out b/tests/phase2/builtins/oracle/str_from_bytes.lua.out new file mode 100644 index 000000000..7ad5baeb9 --- /dev/null +++ b/tests/phase2/builtins/oracle/str_from_bytes.lua.out @@ -0,0 +1,4 @@ +Hi +false bad argument #1 to 'string.char' (number has no integer representation) +false bad argument #1 to 'string.char' (value out of range) +Hi diff --git a/tests/phase2/builtins/oracle/substr.lua b/tests/phase2/builtins/oracle/substr.lua new file mode 100644 index 000000000..29519fe50 --- /dev/null +++ b/tests/phase2/builtins/oracle/substr.lua @@ -0,0 +1,5 @@ +-- substr of [s, start, len] is 0-based; Lua string.sub(s, start + 1, start + len) +print(string.sub("hello", 2, 3)) +print(pcall(string.sub, "hello", 2.5, 3)) +print(pcall(string.sub, "hello", 2, 3.5)) +print(string.sub("hello", 2.0, 3.0)) diff --git a/tests/phase2/builtins/oracle/substr.lua.out b/tests/phase2/builtins/oracle/substr.lua.out new file mode 100644 index 000000000..feacc0e44 --- /dev/null +++ b/tests/phase2/builtins/oracle/substr.lua.out @@ -0,0 +1,4 @@ +el +false bad argument #2 to 'string.sub' (number has no integer representation) +false bad argument #3 to 'string.sub' (number has no integer representation) +el diff --git a/tests/phase2/builtins/oracle/tan.lua b/tests/phase2/builtins/oracle/tan.lua new file mode 100644 index 000000000..8ad906e05 --- /dev/null +++ b/tests/phase2/builtins/oracle/tan.lua @@ -0,0 +1,2 @@ +local function t(x) return x, math.type(x), string.format("%.17g", x) end +for _, v in ipairs({0, 1}) do print(t(math.tan(v))) end diff --git a/tests/phase2/builtins/oracle/tan.lua.out b/tests/phase2/builtins/oracle/tan.lua.out new file mode 100644 index 000000000..94a8d4a7e --- /dev/null +++ b/tests/phase2/builtins/oracle/tan.lua.out @@ -0,0 +1,2 @@ +0.0 float 0 +1.5574077246549 float 1.5574077246549023 diff --git a/tests/phase2/builtins/plants.sh b/tests/phase2/builtins/plants.sh new file mode 100644 index 000000000..6b1d87143 --- /dev/null +++ b/tests/phase2/builtins/plants.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# plants.sh: plant one fault per check in a scratch copy of the bar (and of the +# tree's docs/design and src), run the check, and require its named FAIL line. +# Prints RED or SURVIVED per plant; exit 1 if any plant survives, a mutation +# does not apply, or a baseline is not clean. Run it on Xvfb (DISPLAY=:99). +set -u +HERE=$(cd "$(dirname "$0")" && pwd) +TREE=${EIGS_TREE:-$(cd "$HERE/../../.." && pwd)} +REPO=${EIGS_REPO:-$TREE} +PRE=${PRE_BIN:-$TREE/build/release/eigenscript} +WORK=$(mktemp -d "${TMPDIR:-/tmp}/r16plants.XXXXXX") || exit 2 +# The checker resolves its root from tests/phase2/builtins and reads the frozen +# pin with git. Keep both the real layout and object database in every plant. +T="$WORK/tree"; B="$T/tests/phase2/builtins"; D="$T/docs/design" +cleanup() { + local rc=$? + trap - EXIT + if [ -f "$T/.git" ]; then + git -C "$REPO" worktree remove --force "$T" > "$WORK/cleanup.log" 2>&1 || { + cat "$WORK/cleanup.log" >&2 + rc=1 + } + fi + rm -rf "$WORK" + exit "$rc" +} +trap cleanup EXIT +git -C "$REPO" worktree add --detach "$T" HEAD > "$WORK/worktree.log" 2>&1 || { + cat "$WORK/worktree.log" >&2 + exit 2 +} +mkdir -p "$WORK/bar0" "$WORK/tree0/docs/design" "$WORK/tree0/tools" || exit 2 +cp -r "$HERE/." "$WORK/bar0/" && cp "$TREE"/docs/design/phase2-numbers.md "$TREE"/docs/design/phase2-inventory.txt \ + "$WORK/tree0/docs/design/" && cp -r "$TREE/src" "$WORK/tree0/src" \ + && cp "$TREE/tools/phase2_inventory.py" "$WORK/tree0/tools/" \ + && cp "$HERE/../observation_hooks.c" "$WORK/shim0.c" \ + && cp "$HERE/../builtins_changes.md" "$WORK/changes0.md" \ + && cp "$HERE/../builtins_landed.txt" "$WORK/landed0.txt" || exit 2 +sub() { # sub : replace exactly one occurrence, or fail + python3 - "$1" "$2" "$3" <<'EOF' +import sys +p, old, new = sys.argv[1], sys.argv[2].encode().decode("unicode_escape"), sys.argv[3].encode().decode("unicode_escape") +s = open(p).read() +if s.count(old) != 1: sys.exit("sub: %d occurrences of %r in %s" % (s.count(old), old, p)) +open(p, "w").write(s.replace(old, new)) +EOF +} +table() { (cd "$B" && EIGS_TREE="$T" timeout 120 python3 make_table.py "$@" 2>&1); } +runner() { (cd "$B" && TMPDIR="$WORK" PROBE_STDERR_DIR="$WORK/stderr" PRE_BIN="$PRE" timeout 600 bash run_probes.sh "$@" 2>&1); } +mutate() { + case $1 in + P1) python3 -c "import sys;p='$D/phase2-numbers.md';l=open(p).read().split('\n');k=[x for x in l if x.startswith('| \`abs\` |')];assert len(k)==1;l.remove(k[0]);open(p,'w').write('\n'.join(l))" ;; + P2) sub "$D/phase2-numbers.md" '| `len` | int-preserving |' '| `len` | other |' ;; + P3) sub "$D/phase2-numbers.md" '' '| `p2plant` | other | - | - | planted |\n' ;; + P4) python3 -c "import re;p='$D/phase2-numbers.md';s=open(p).read();a,r=s.split('',1);m,e=r.split('',1);open(p,'w').write(a+'\n'+''+e)" ;; + P5) printf 'Value* builtin_abs(Value *arg) {\n return arg;\n}\n' >> "$T/src/builtins_buf.c" ;; + P6) python3 -c "import re;p='$D/phase2-inventory.txt';s=open(p).read();n=len(re.findall(r'(?m)^store_open\s+builtin_store_open\s+int-preserving',s));assert n==1,n;open(p,'w').write(re.sub(r'(?m)^(store_open\s+builtin_store_open\s+)int-preserving',r'\1other',s))" ;; + P7) sub "$D/phase2-numbers.md" '| `gfx_clear` | other | - | bounded |' '| `gfx_clear` | other | - | - |' ;; + P8) rm "$B/builtins/hex.tag" ;; + P9) : > "$B/builtins/p2stale.eigs" ;; + P10) sub "$B/rowmeta.tsv" '\nhex\t-\trelease' '\nhex\tR1.5\trelease' ;; + P11) sub "$B/builtins/abs.pre" 'rc=0' 'planted\nrc=0' ;; + P12) sub "$B/rowmeta.tsv" 'abs\tR1.2/R1.3\trelease' 'abs\t-\trelease' ;; + P13) cp "$B/builtins/hex.branch" "$B/builtins/hex.pre" ;; + P14) sub "$B/builtins/audio_stream_push.pre" 'hook-loaded pcm\n' '' ;; + P15) sub "$B/builtins/abs.pre" 'rc=0' 'hook-loaded mix\nrc=0' ;; + P16) printf '["capability", "value", "inflate: compiled without zlib support (rebuild with `make zlib`)"]\nrc=0\n' > "$B/builtins/inflate.pre" ;; + P17) printf 'PENDING-PROFILE server\n' > "$B/builtins/len.pre" ;; + P18) printf 'PENDING-PROFILE zlib\n' > "$B/builtins/inflate.pre" ;; + P19) sub "$B/rowmeta.tsv" 'abs\tR1.2/R1.3\trelease\n' '' ;; + P20) sub "$B/rowmeta.tsv" 'abs\tR1.2/R1.3\trelease' 'abs\tR1.2/R1.3\tgpu' ;; + P21) sub "$B/cases.tsv" 'gfx_poll\tgfx_poll\tevents' 'gfx_poll\tgfx_poll\tbogus' ;; + P22) sub "$B/../observation_hooks.c" 'hook_is("events")' 'hook_is("evnts")' ;; + P24) : > "$B/builtins/notes.txt" ;; + P25) sub "$B/rowmeta.tsv" '\nlen\t-\trelease' '\nlen\tmain\trelease' ;; + P26) rm "$B/builtins/_profiles/zlib.eigs" ;; + P27) sub "$B/cases.tsv" '\nlen\tlen\t-\t-\n' '\n' ;; + P28) : ;; + T1) printf 'print of (monotonic_ns of null)\n' >> "$B/builtins/len.eigs" ;; + G1) sub "$B/builtins.tsv" '\nlen\tbuiltins.c\tint-preserving\t-\t-\tb\t-\trelease\t-\t-\tyes\tyes\n' '\n' ;; + G2) printf 'p2stale\tbuiltins.c\tother\t-\t-\tc\t-\trelease\t-\t-\tno\tno\n' >> "$B/builtins.tsv" ;; + G3) sub "$B/builtins.tsv" '\nlen\tbuiltins.c\tint-preserving\t' '\nlen\tbuiltins.c\tother\t' ;; + G4) sub "$B/builtins/random_hex.eigs" '"stdout-line":3' '"stdout-line":2' ;; + F1) sub "$B/builtins/len.branch" 'rc=0' 'planted\nrc=0' ;; + F2) python3 - "$T/tests/phase2/builtins_changes.md" <<'EOF' +import sys +p = sys.argv[1] +s = open(p).read() +pin = 'eee02d9e7cd24809addb54e3c2df45f401134d5c279a7971a87fea26848781bc' +old = '| `tests/phase2/builtins/builtins/min.branch` | `' + pin + '` | R1.7-D6 |' +assert s.count(old) == 1 +open(p, 'w').write(s.replace(old, old.replace(pin, '0' * 64))) +EOF + ;; + F3) git -C "$T" show 0a73adb0612f8612d75393891ae2ba2e1c803663:tests/phase2/builtins/builtins/min.branch > "$B/builtins/min.branch" ;; + F4) sub "$B/builtins/sign_extend.branch" '["overflow", "value"]' '["overflow", "type_mismatch"]' ;; + R1) sub "$B/run_probes.sh" 'out="$HERE/builtins/$2.$3"' 'out="$2.$3"' ;; + R2|R3) sub "$B/run_probes.sh" '(LD_PRELOAD="$SHIM" ' '(' ;; + R4) sub "$B/builtins/recv_timeout.eigs" 'send of [c, 7]\n' '' ;; + R5) : ;; + R7|R8) sub "$B/run_probes.sh" 'R16_HOOK="$hook"' 'R16_HOOK="$hook" R16_PLANT=silence' ;; + *) return 1 ;; + esac +} +check() { + case $1 in + P18) table --final ;; + P28) (cd "$B" && env -u EIGS_TREE EIGS_REPO="$REPO" EIGS_REV=4ac31e67 timeout 120 python3 make_table.py 2>&1) ;; + T1) (cd "$B" && TMPDIR="$WORK" PROBE_STDERR_DIR="$WORK/stderr" PRE_BIN="$PRE" timeout 300 bash determinism.sh len 2>&1) ;; + P*|G*|F*) table ;; + R1) runner abs ;; + R2) runner audio_stream_push ;; + R3) runner audio_stream_push >/dev/null; table ;; + R4) runner recv_timeout ;; + R5) (cd "$B" && PRE_BIN="$PRE" PRE_BIN_ZLIB="$PRE" TAG_BIN_ZLIB="$PRE" TMPDIR="$WORK" timeout 120 bash run_probes.sh inflate 2>&1) ;; + R7|R8) c=audio_stream_push; [ "$1" = R8 ] && c=audio_play + runner "$c" >/dev/null + cmp -s "$B/builtins/$c.pre" "$HERE/builtins/$c.pre" || echo "FAIL: $c.pre differs from the recorded .pre: $(grep -E 'SDL_QueueAudio|mixed' "$B/builtins/$c.pre" | tr '\n' ' ')" ;; + esac +} +# S10-D2 retired P23/R6 with the sole stdin case; remaining IDs stay fixed. +# id | the check that must go red | the FAIL line it must print (regex) +PLANTS='P1|section-6 vs inventory membership|in the inventory, not in section 6: abs$ +P2|section-6 vs inventory fields|vs inventory .* for len$ +P3|stale section-6 row|in section 6, not in the inventory: p2plant$ +P4|examined == len(table) > 0|examined=0 table=0 +P5|one defining file per row|abs: C function defined in \[.builtins.c., .builtins_buf.c.\] +P6|inventory field edit|for store_open$ +P7|q7 column edit|for gfx_clear$ +P8|case files vs cases.tsv (missing record)|in cases.tsv, not in builtins/\*\.tag: hex$ +P9|case files vs cases.tsv (stray probe)|in builtins/\*\.eigs, not in cases.tsv: p2stale$ +P10|closed_in needs .pre == .branch (A2)|hex: closed_in .R1\.5. but changes_vs_pre is yes +P11|closed_in needs .pre == .branch after a .pre edit (A2)|abs: closed_in .R1\.2/R1\.3. but changes_vs_pre is yes +P12|vacuity guard: closed_in removed|vacuous: abs is a +P13|vacuity guard: .branch equal to .pre|vacuous: hex is a +P14|hook record without its shim (table)|audio_stream_push\.pre holds \[\], not one .hook-loaded pcm. line +P15|hook-loaded line on a non-hook case|abs\.pre holds .hook-loaded mix. but the case has no hook +P16|capability transcript on a non-release row (A3)|inflate\.pre is a capability-unavailable transcript +P17|PENDING-PROFILE on a release row|len\.pre: .PENDING-PROFILE server. on a release row +P18|--final refuses pending records|inflate: \.pre is PENDING-PROFILE zlib \(--final\) +P19|rowmeta vs table membership|in section 6, not in rowmeta\.tsv: abs$ +P20|profile vocabulary|rowmeta\.tsv: abs has profile .gpu. +P21|cases hooks vs shim hooks|in the cases. hooks, not in the shim.s hooks: bogus$ +P22|shim hooks vs cases hooks|in the cases. hooks, not in the shim.s hooks: events$ +P24|stray file in builtins/|builtins/notes\.txt is neither a case file nor a fixture dir +P25|closed_in only on guarded rows|len: closed_in .main. on a row the vacuity guard does not cover +P26|profile witnesses vs profiles|in the non-release profiles, not in builtins/_profiles: zlib$ +P27|primary case per row|in section 6, not in the primary cases: len$ +P28|population pinned to 1fd4422c: the pre-#1841 revision lacks read_text|in rowmeta\.tsv, not in section 6: read_text$ +T1|determinism: a printed time (the R1.6a-D2 class)|^UNSTABLE len\.pre$ +G1|builtins.tsv missing row|in section 6, not in builtins\.tsv: len$ +G2|builtins.tsv stale row|in builtins\.tsv, not in section 6: p2stale$ +G3|builtins.tsv class edit|len: builtins\.tsv class .other. != derived .int-preserving. +G4|integral-float success must be a result|random_hex: integral-float success stdout line 2 is an error or non-result +F1|frozen bar rejects an unlisted edit|len\.branch: unlisted frozen bar difference +F2|frozen bar rejects a stale SHA|min\.branch: stale amendment R1\.7-D6: SHA-256=0000 +F3|frozen bar rejects a restored pin|min\.branch: stale amendment R1\.7-D6: +F4|frozen bar rejects an edited amended line|sign_extend\.branch: stale amendment R1\.6b-D1: +R1|runner: empty stdout with rc 0|FAIL: abs\.pre printed nothing with rc 0 +R2|runner: hook case without the shim|FAIL: audio_stream_push\.pre ran without its hook pcm +R3|table: hook case recorded without the shim|audio_stream_push\.pre holds \[\], not one .hook-loaded pcm. line +R4|runner: a probe that blocks (rc 124)|FAIL: recv_timeout\.pre timed out \(rc 124\) +R5|runner: profile witness refuses a release binary|FAIL: .* is not a zlib build +R7|pcm hook observes the delivered PCM (silence)|FAIL: audio_stream_push\.pre differs from the recorded \.pre: SDL_QueueAudio 3: 0 0 0 +R8|mix hook observes the mixed PCM (silence)|FAIL: audio_play\.pre differs from the recorded \.pre: \["mixed", false\]' +fresh() { + # Preserve the linked worktree's .git. Restore only the owned fixture + # population, including amendments so one mutation cannot bless another. + rm -rf "$B" "$T/src" "$D" + mkdir -p "$T/tests/phase2" "$D" "$T/tools" || return + cp -r "$WORK/bar0" "$B" && cp -r "$WORK/tree0/src" "$T/src" \ + && cp "$WORK/tree0/docs/design/"* "$D/" \ + && cp "$WORK/tree0/tools/phase2_inventory.py" "$T/tools/" \ + && cp "$WORK/shim0.c" "$T/tests/phase2/observation_hooks.c" \ + && cp "$WORK/changes0.md" "$T/tests/phase2/builtins_changes.md" \ + && cp "$WORK/landed0.txt" "$T/tests/phase2/builtins_landed.txt" +} +bad=0 +fresh || exit 2; out=$(table); rc=$? +if [ $rc -ne 0 ] || grep -q '^FAIL' <<<"$out"; then echo "BASELINE make_table not clean (rc $rc): $out"; bad=1; else echo "BASELINE make_table clean: $(grep '^guard:' <<<"$out")"; fi +fresh || exit 2; out=$(runner abs audio_stream_push gfx_poll.empty read_bytes_buf inflate); rc=$? +if [ $rc -ne 0 ] || grep -q '^FAIL' <<<"$out"; then echo "BASELINE runner not clean (rc $rc): $out"; bad=1; else echo "BASELINE runner clean: $(tail -1 <<<"$out")"; fi +n=0; red=0 +while IFS='|' read -r id what want; do + n=$((n + 1)); fresh || exit 2 + if ! mutate "$id" >"$WORK/mutate.log" 2>&1; then echo "BROKEN $id ($what): the mutation did not apply: $(cat "$WORK/mutate.log")"; bad=1; continue; fi + out=$(check "$id") + line=$(grep -E -m1 -- "$want" <<<"$out") + if [ -n "$line" ]; then red=$((red + 1)); echo "RED $id ($what): $line"; else echo "SURVIVED $id ($what)"; bad=1; fi +done <<<"$PLANTS" +want_n=$(grep -c . <<<"$PLANTS") +echo "plants examined=$n table=$want_n red=$red survived=$((n - red))" +[ "$n" -eq "$want_n" ] && [ "$n" -gt 0 ] && [ "$red" -eq "$n" ] && [ "$bad" -eq 0 ] || exit 1 diff --git a/tests/phase2/builtins/rowmeta.tsv b/tests/phase2/builtins/rowmeta.tsv new file mode 100644 index 000000000..917dc8556 --- /dev/null +++ b/tests/phase2/builtins/rowmeta.tsv @@ -0,0 +1,310 @@ +builtin closed_in profile +abs R1.2/R1.3 release +acos - release +add - release +append - release +asin - release +assert - release +atan - release +atan2 - release +audio_capture_open - release +audio_envelope - release +audio_gain - release +audio_mix - release +audio_music_play - release +audio_music_volume - release +audio_noise main #1767 release +audio_open - release +audio_pause - release +audio_play - release +audio_play_loop - release +audio_queue_size - release +audio_saw main #1767 release +audio_sine main #1767 release +audio_square main #1767 release +audio_stop - release +audio_stream_open - release +audio_stream_push - release +audio_stream_queued - release +audio_sweep - release +audio_volume - release +bit_and R1.5 release +bit_not R1.5 release +bit_or R1.5 release +bit_shl R1.5 release +bit_shr R1.5 release +bit_xor R1.5 release +buf_copy - release +buf_deinterleave - release +buf_dot - release +buf_fill - release +buf_from_list - release +buf_from_pcm16le - release +buf_get - release +buf_len - release +buf_mix - release +buf_peak - release +buf_resample_linear - release +buf_scale_range - release +buf_set - release +buf_to_pcm16le - release +buffer - release +build_corpus - release +ceil - release +channel - release +channel_closed - release +char_at - release +chr main release +clock_unix - release +close_channel - release +copy_into - release +cos - release +db_execute - db +db_query_json - db +db_query_value - db +dispatch main release +divide - release +dot - release +eigen_eval_loss - server +eigen_generate - server +eigen_model_load - server +exec_capture - release +exit - release +exp - release +f64_from_bytes - release +f64_to_bytes - release +fill - release +floor - release +gather - release +get_at - release +get_observer_thresholds - release +gfx_circle - release +gfx_clear - release +gfx_clip - release +gfx_delay - release +gfx_fb - release +gfx_line - release +gfx_open - release +gfx_point - release +gfx_poll - release +gfx_read - release +gfx_rect - release +gfx_rrect - release +gfx_text - release +gfx_text_height - release +gfx_text_width - release +gfx_ticks - release +hex - release +http_early_bind - server +http_post - server +http_route - server +http_route_authed - server +http_serve - server +http_static - server +index_of - release +inflate - zlib +join - release +json_build R1.7 release +json_decode - release +json_encode R1.7 release +json_path - release +leaky_relu - release +len - release +list_contains - release +list_index_of - release +list_insert_at - release +list_remove_at - release +list_slice - release +list_truncate - release +log - release +log_softmax - release +matmul - release +matmul_at - release +matmul_bt - release +max - release +mean - release +min - release +model_load_weights - server +monotonic_ms - release +monotonic_ns - release +multiply - release +native_train_step_builtin - server +nearest_in_range - release +nearest_in_range_all - release +negative - release +net_accept - server +net_close - server +net_dial - server +net_listen - server +net_port - server +net_recv - server +net_send - server +norm - release +num - release +num_copy - release +num_type - release +numerical_grad - release +numerical_grad_cols - release +numerical_grad_rows - release +observe - release +ord - release +pi - release +pow - release +ppu_render_frame - release +print - release +random - release +random_hex - release +random_int - release +random_normal - release +range - release +read_bytes - release +read_bytes_buf - release +read_text - release +record_history - release +recv - release +recv_timeout - release +relu - release +reshape - release +round - release +sandbox_run - release +scan_int_tokens - release +scan_ints - release +scatter_add - release +seed_random - release +send - release +set_at - release +set_exit_code R1.6-S2 release +sgd_update - release +sgd_update_cols - release +sgd_update_rows - release +shape - release +shared_delete - server +shared_incr - server +shared_size - server +sign_extend - release +sin - release +softmax - release +sort R1.2/R1.3 release +sort_by R1.2/R1.3 release +spawn - release +sqrt - release +state_at - release +store_close - release +store_collections - release +store_count - release +store_delete - release +store_drop - release +store_get - release +store_open - release +store_put - release +store_query - release +store_update - release +str - release +str_from_bytes - release +stream_open - release +stream_write - release +substr - release +subtract - release +sum - release +tan - release +task_alive - release +task_detach - release +task_join - release +task_kill - release +task_sched_seed - release +task_self - release +task_send - release +task_sleep - release +task_spawn - release +tensor_load - release +tensor_save - release +text_builder_append - release +text_builder_append_line - release +thread_join - release +throw - release +token_name - release +try_recv - release +type - release +usleep - release +vm_run_bytecode - release +write - release +write_bytes - release +zeros - release +zeros_like - release +i8_wrapping_add - release +i8_wrapping_sub - release +i8_wrapping_mul - release +i8_checked_add - release +i8_checked_sub - release +i8_checked_mul - release +i8_saturating_add - release +i8_saturating_sub - release +i8_saturating_mul - release +i16_wrapping_add - release +i16_wrapping_sub - release +i16_wrapping_mul - release +i16_checked_add - release +i16_checked_sub - release +i16_checked_mul - release +i16_saturating_add - release +i16_saturating_sub - release +i16_saturating_mul - release +i32_wrapping_add - release +i32_wrapping_sub - release +i32_wrapping_mul - release +i32_checked_add - release +i32_checked_sub - release +i32_checked_mul - release +i32_saturating_add - release +i32_saturating_sub - release +i32_saturating_mul - release +i64_wrapping_add - release +i64_wrapping_sub - release +i64_wrapping_mul - release +i64_checked_add - release +i64_checked_sub - release +i64_checked_mul - release +i64_saturating_add - release +i64_saturating_sub - release +i64_saturating_mul - release +u8_wrapping_add - release +u8_wrapping_sub - release +u8_wrapping_mul - release +u8_checked_add - release +u8_checked_sub - release +u8_checked_mul - release +u8_saturating_add - release +u8_saturating_sub - release +u8_saturating_mul - release +u16_wrapping_add - release +u16_wrapping_sub - release +u16_wrapping_mul - release +u16_checked_add - release +u16_checked_sub - release +u16_checked_mul - release +u16_saturating_add - release +u16_saturating_sub - release +u16_saturating_mul - release +u32_wrapping_add - release +u32_wrapping_sub - release +u32_wrapping_mul - release +u32_checked_add - release +u32_checked_sub - release +u32_checked_mul - release +u32_saturating_add - release +u32_saturating_sub - release +u32_saturating_mul - release +u64_wrapping_add - release +u64_wrapping_sub - release +u64_wrapping_mul - release +u64_checked_add - release +u64_checked_sub - release +u64_checked_mul - release +u64_saturating_add - release +u64_saturating_sub - release +u64_saturating_mul - release +u64_compare - release +u64_div - release +u64_rem - release +u64_to_string - release +fround - release diff --git a/tests/phase2/builtins/run_probes.sh b/tests/phase2/builtins/run_probes.sh new file mode 100755 index 000000000..64ac0b4cd --- /dev/null +++ b/tests/phase2/builtins/run_probes.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# run_probes.sh [--kind pre|tag] [case...]: record observations by +# RUNNING builtins/.eigs on the pre (R1.5 tip) and tag (v0.46.0) binaries. +# Each record is the probe's stdout plus a last line rc= (rc=124 is a +# timeout). stderr is kept beside the scratch copy, never compared. .branch +# files are NEVER written here. No argument: every case in cases.tsv. +# cases.tsv case, builtin, hook, stdin rowmeta.tsv builtin, closed_in, profile +# A non-release row (profile server|db|zlib) records PENDING-PROFILE unless +# PRE_BIN_ and TAG_BIN_ name matched builds (ruling A3); each +# such binary must first print profile-ok from builtins/_profiles/.eigs. +# --check [case...] compares observations without +# modifying records. R16_LANDED is supplied by make_table.py's shared parser; +# BRANCH_BIN_ and TAG_BIN_ name capability builds. +# DATABASE_URL passes through for db rows. B6's one-request loopback capture +# fixture is started for each http_post process, before the binary starts. +# A hook case runs with ../observation_hooks.c, built here, as +# LD_PRELOAD, and must log "hook-loaded " (ruling A4). +# Exit 2 on any instrument failure. +set -u +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$HERE/../../.." && pwd) +PRE=${PRE_BIN:-$ROOT/build/release/eigenscript} +TAG=${TAG_BIN:-} +CHECK=0 +KINDS=(pre tag) +if [ "${1:-}" = --check ]; then + [ "$#" -ge 3 ] || { echo 'FAIL: --check needs tag and branch binaries'; exit 2; } + CHECK=1; TAG=$2; BRANCH=$3; shift 3 +elif [ "${1:-}" = --kind ]; then + case "${2:-}" in pre|tag) KINDS=("$2"); shift 2 ;; *) echo 'FAIL: --kind must be pre or tag'; exit 2 ;; esac +fi +START_DIR=$PWD +absolute_binary() { case "$1" in /*) printf '%s\n' "$1" ;; *) printf '%s/%s\n' "$START_DIR" "$1" ;; esac; } +PRE=$(absolute_binary "$PRE") +[ -z "$TAG" ] || TAG=$(absolute_binary "$TAG") +if [ "$CHECK" -eq 1 ]; then BRANCH=$(absolute_binary "$BRANCH"); fi +WORK=${TMPDIR:-/tmp} +ERRDIR=${PROBE_STDERR_DIR:-$WORK/r16bar-stderr} +if [ "$CHECK" -eq 1 ]; then binaries=("$TAG" "$BRANCH") +else binaries=(); for k in "${KINDS[@]}"; do if [ "$k" = pre ]; then binaries+=("$PRE"); else binaries+=("$TAG"); fi; done; fi +for b in "${binaries[@]}"; do [ -x "$b" ] || { echo "FAIL: binary not executable: $b"; exit 2; }; done +mkdir -p "$ERRDIR" || exit 2 +SCRATCH=$(mktemp -d "$WORK/r16bar.XXXXXX") || exit 2 +SHIM="$SCRATCH/observation-hooks.so" +CAPTURE_PID= +cleanup() { [ -z "$CAPTURE_PID" ] || { kill "$CAPTURE_PID" 2>/dev/null || :; wait "$CAPTURE_PID" 2>/dev/null || :; }; rm -rf "$SCRATCH"; } +trap cleanup EXIT +gcc -shared -fPIC -O0 -Wall -Wextra -Werror "$HERE/../observation_hooks.c" -ldl -o "$SHIM" \ + || { echo "FAIL: the observation shim did not build"; exit 2; } +declare -A HOOK STDIN BUILTIN PROFILE CLOSED ROUND CHECKED +while IFS=$'\t' read -r c b h s; do BUILTIN[$c]=$b; HOOK[$c]=$h; STDIN[$c]=$s; done < <(tail -n +2 "$HERE/cases.tsv") +while IFS=$'\t' read -r b ci p; do PROFILE[$b]=$p; CLOSED[$b]=$ci; done < <(tail -n +2 "$HERE/rowmeta.tsv") +while IFS=$'\t' read -r b file class loss q7 round rest; do ROUND[$b]=$round; done < <(tail -n +2 "$HERE/builtins.tsv") +[ ${#BUILTIN[@]} -gt 0 ] && [ ${#PROFILE[@]} -gt 0 ] || { echo "FAIL: empty cases.tsv or rowmeta.tsv"; exit 2; } +cd "$HERE/builtins" || exit 2 +names=("$@") +[ ${#names[@]} -gt 0 ] || names=($(printf '%s\n' "${!BUILTIN[@]}" | LC_ALL=C sort)) +bad=0; pending=0; hooked=0 +FIXENV=() +[ -z "${DATABASE_URL:-}" ] || FIXENV+=("DATABASE_URL=$DATABASE_URL") + +profile_ok() { # profile_ok : the binary carries the profile's capabilities + local tmp out rc key="$1|$2" + [ "${CHECKED[$key]:-}" != yes ] || return 0 + tmp=$(mktemp -d "$WORK/r16profile.XXXXXX") || exit 2 + out=$( ( ulimit -v 1500000; cd "$tmp" && exec env -i PATH=/usr/bin:/bin HOME="$tmp" "${FIXENV[@]}" \ + timeout 60 "$1" "$HERE/builtins/_profiles/$2.eigs" /dev/null ) ) + rc=$? + rm -rf "$tmp" + if [ "$rc" -eq 0 ] && grep -qx 'profile-ok' <<<"$out"; then CHECKED[$key]=yes; return 0; fi + return 1 +} +run_one() { # run_one + local c=$2 kind=$3 out="$HERE/builtins/$2.$3" tmp rc hook=${HOOK[$2]} in=/dev/null expected capture_rc=0 + if [ "$CHECK" -eq 1 ]; then out="$SCRATCH/$2.$3"; fi + rm -f "$out" # a stale record must never survive a run that did not write one + tmp=$(mktemp -d "$WORK/r16probe.XXXXXX") || exit 2 + [ "${STDIN[$c]}" = "-" ] || in="$HERE/builtins/_stdin/${STDIN[$c]}" + [ -r "$in" ] || { echo "FAIL: $c: no stdin fixture $in"; bad=$((bad + 1)); rm -rf "$tmp"; return; } + local -a hookenv=() # set after timeout, so only the probed binary loads the shim + [ "$hook" = "-" ] || hookenv=(LD_PRELOAD="$SHIM" R16_HOOK="$hook" R16_HOOK_LOG="$tmp/hook.log") + local -a captureenv=() + if [ "${BUILTIN[$c]}" = http_post ]; then + python3 "$HERE/http_capture.py" "$tmp/capture.url" "$tmp/capture.request" >"$tmp/capture.stdout" 2>"$tmp/capture.stderr" & + CAPTURE_PID=$! + for ((attempt=0; attempt<100; attempt++)); do [ ! -s "$tmp/capture.url" ] || break; sleep 0.02; done + if [ ! -s "$tmp/capture.url" ]; then + echo "FAIL: $c.$kind capture fixture did not start"; cat "$tmp/capture.stderr"; bad=$((bad + 1)) + kill "$CAPTURE_PID" 2>/dev/null || :; wait "$CAPTURE_PID" 2>/dev/null || :; CAPTURE_PID= + rm -rf "$tmp"; return + fi + captureenv=("R16_HTTP_CAPTURE_URL=$(cat "$tmp/capture.url")") + fi + # The suite's headless gfx/audio conditions (SDL dummy drivers), the local + # display, the bitmap font for deterministic text metrics, a memory cap. + ( ulimit -v 1500000; cd "$tmp" && exec env -i PATH=/usr/bin:/bin HOME="$tmp" \ + DISPLAY=:99 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ + EIGS_GFX_FONT=/nonexistent/eigs-no-font.ttf EIGS_PROBE_TMP="$tmp" "${FIXENV[@]}" "${captureenv[@]}" \ + timeout 60 env "${hookenv[@]}" "$1" "$HERE/builtins/$c.eigs" <"$in" >"$out" 2>"$ERRDIR/$c.$kind.stderr" ) + rc=$? + # Every probe prints at least one line, so an empty stdout with rc 0 is an + # instrument failure (it was one: a relative path written after the cd). + if [ ! -s "$out" ] && [ "$rc" -eq 0 ]; then echo "FAIL: $c.$kind printed nothing with rc 0"; bad=$((bad + 1)); fi + # No probe may block on any binary (values are queued first), so a timeout is a named failure. + if [ "$rc" -eq 124 ]; then echo "FAIL: $c.$kind timed out (rc 124)"; bad=$((bad + 1)); fi + if [ "$hook" != "-" ]; then + if head -1 "$tmp/hook.log" 2>/dev/null | grep -qx "hook-loaded $hook"; then hooked=$((hooked + 1)) + else echo "FAIL: $c.$kind ran without its hook $hook (no hook-loaded line)"; bad=$((bad + 1)); fi + fi + echo "rc=$rc" >> "$out" + # Optional evidence export from this exact gate run; never an expectation. + if [ "$CHECK" -eq 1 ] && [ -n "${PROBE_OBSERVATIONS_DIR:-}" ]; then + mkdir -p "$PROBE_OBSERVATIONS_DIR" || exit 2 + cp "$out" "$PROBE_OBSERVATIONS_DIR/$c.$kind" || exit 2 + fi + if [ -n "$CAPTURE_PID" ]; then + # A failed request must not leave a fixture waiting for its one client. + if [ ! -f "$tmp/capture.request" ]; then kill "$CAPTURE_PID" 2>/dev/null || :; fi + wait "$CAPTURE_PID"; capture_rc=$?; CAPTURE_PID= + if [ "$capture_rc" -ne 0 ]; then + echo "FAIL: $c.$kind capture fixture failed (rc $capture_rc)"; cat "$tmp/capture.stderr" + if [ -f "$tmp/capture.request" ]; then echo "HTTP request:"; cat "$tmp/capture.request"; echo; fi + bad=$((bad + 1)) + fi + fi + if [ "$CHECK" -eq 1 ]; then + expected=tag + if [ "$kind" = branch ]; then + expected=pre + case " ${R16_LANDED:-} " in *" R1.6${ROUND[${BUILTIN[$c]}]} "*) expected=branch ;; esac + # D5: an R1.7 closure is live as soon as R1.7 is declared, + # independently of the row's original R1.6 migration group. + if [ "${CLOSED[${BUILTIN[$c]}]}" = R1.7 ]; then + case " ${R16_LANDED:-} " in *" R1.7 "*) expected=branch ;; esac + fi + fi + if ! cmp -s "$out" "$HERE/builtins/$c.$expected"; then + echo "FAIL: ${BUILTIN[$c]}: $c.$kind differs from .$expected" + diff -u "$HERE/builtins/$c.$expected" "$out" || : + if [ -f "$tmp/capture.request" ]; then echo "HTTP request:"; cat "$tmp/capture.request"; echo; fi + bad=$((bad + 1)) + fi + fi + rm -rf "$tmp" +} +ran=0 +for c in "${names[@]}"; do + [ -n "${BUILTIN[$c]:-}" ] || { echo "FAIL: $c is not a case in cases.tsv"; exit 2; } + [ -f "$c.eigs" ] || { echo "FAIL: no probe builtins/$c.eigs"; exit 2; } + p=${PROFILE[${BUILTIN[$c]}]:-} + if [ "$CHECK" -eq 1 ]; then kinds=(tag branch); else kinds=("${KINDS[@]}"); fi + for k in "${kinds[@]}"; do + case "$k" in pre) binary=$PRE ;; tag) binary=$TAG ;; branch) binary=$BRANCH ;; esac + case "$p" in + release) run_one "$binary" "$c" "$k" ;; + server|db|zlib) + u=${p^^}; var=${k^^}_BIN_$u + if [ -n "${!var:-}" ]; then binary=$(absolute_binary "${!var}"); + elif [ "$CHECK" -ne 1 ]; then + printf 'PENDING-PROFILE %s\n' "$p" > "$c.$k"; pending=$((pending + 1)); continue + fi + [ -x "$binary" ] || { echo "FAIL: binary not executable: $binary"; exit 2; } + profile_ok "$binary" "$p" || { echo "FAIL: $binary is not a $p build (builtins/_profiles/$p.eigs did not print profile-ok)"; exit 2; } + run_one "$binary" "$c" "$k" ;; + *) echo "FAIL: $c: unknown profile '$p'"; exit 2 ;; + esac + done + ran=$((ran + 1)) +done +echo "ran=$ran requested=${#names[@]} empty_or_hookless=$bad hook_runs=$hooked pending_profile=$pending" +[ "$ran" -eq "${#names[@]}" ] && [ "$bad" -eq 0 ] || exit 2 diff --git a/tests/phase2/builtins_changes.md b/tests/phase2/builtins_changes.md new file mode 100644 index 000000000..76422de96 --- /dev/null +++ b/tests/phase2/builtins_changes.md @@ -0,0 +1,715 @@ +# Builtin bar changes + +## Author-applied rulings, second import + +The first import d2437d6d is unchanged. This second import copies the author-applied rulings byte for byte. The orchestrator ruled: + +> R1.6a-D1: read_text gets a row: class other, round c, profile release, no R1.6 change, and no over-cap REPLAY case (open bug #1890). Its pre/tag were recorded before #1841; re-record pre on 1fd4422c because #1668 changed it upstream; changes_vs_pre then reads no. + +> R1.6a-D2: net_port compares instead of printing an ephemeral port; net_listen, net_dial and net_accept likewise avoid socket handles. exec_capture prints the TYPE of its answer because pre-R1.6 (time_t)1e300 overflow made it nondeterministic. Re-record the changed probes on matched builds. + +| File | Ruling | +|---|---| +| `BAR.md` | R1.6a-D1 and R1.6a-D2 | +| `RULINGS.draft.md` | R1.6a-D1 and R1.6a-D2 | +| `builtins/exec_capture.eigs` | R1.6a-D2 | +| `builtins/exec_capture.pre` | R1.6a-D2 | +| `builtins/exec_capture.tag` | R1.6a-D2 | +| `builtins/net_accept.eigs` | R1.6a-D2 | +| `builtins/net_dial.eigs` | R1.6a-D2 | +| `builtins/net_listen.eigs` | R1.6a-D2 | +| `builtins/net_port.eigs` | R1.6a-D2 | +| `builtins/read_text.branch` | R1.6a-D1 | +| `builtins/read_text.eigs` | R1.6a-D1 | +| `builtins/read_text.pre` | R1.6a-D1 | +| `builtins/read_text.tag` | R1.6a-D1 | +| `builtins.tsv` | R1.6a-D1 and R1.6a-D2 | +| `cases.tsv` | R1.6a-D1 and R1.6a-D2 | +| `determinism.sh` | R1.6a-D2 | +| `make_table.py` | R1.6a-D1 and R1.6a-D2 | +| `plants.sh` | R1.6a-D1 and R1.6a-D2 | +| `rowmeta.tsv` | R1.6a-D1 and R1.6a-D2 | + +## R1.6a implementation after the author-applied rulings + +No `.branch` expectation is changed. The five D2 deterministic probe bodies +and the author's determinism.sh remain unchanged; exercise metadata is appended +to existing first comment lines, preserving all executable source line numbers. +All 37 author plants remain, with the four implementation controls named G1-G4. +P18 explicitly plants a pending record so that it remains live after recording. +Normal table checking reads the current tree. P28 explicitly archives 4ac31e67 +and still fails on its missing read_text population, using the shared inventory +parser from that revision. The default checker does not rewrite the TSV. + +| File | Change and authority | +|---|---| +| `tests/phase2/builtins/make_table.py` | Shared inventory parser; check-only TSV membership/class validation, explicit --write, anchored class consistency, declared landed rounds and retained explicit-revision P28; brief 2, RULINGS A1-A5, R1.6a-D1/D2. | +| `tests/phase2/builtins/run_probes.sh` | Same runner for recording and branch checks; --kind and --check, moved shim and automatic B6 capture fixture; brief 2.3, RULINGS A3-A5/B6. | +| `tests/phase2/builtins/plants.sh` | Preserve P1-P28/T1/R1-R8; keep P18 live and add G1-G4 TSV/class calibration; brief 3, principle 5, R1.6a-D1/D2. | +| `tests/phase2/builtins/http_capture.py` | One loopback HTTP request; echo exact X-Number bytes and preserve request on failure; RULINGS B6. | +| `tests/phase2/builtins/builtins/audio_pause.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/zeros.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/random_int.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/list_slice.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_kill.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_open.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/eigen_generate.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_capture_open.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/sign_extend.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gather.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_rrect.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_music_play.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/set_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_or.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_shr.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/exec_capture.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_sweep.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_rect.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_join.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/close_channel.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_port.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_close.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/state_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/chr.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_music_volume.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/proc_read_buf.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_stream_open.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_clear.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_shl.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/hex.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_fb.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_send.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/http_early_bind.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/write_bytes.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_xor.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_get.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_noise.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_stop.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_text_width.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_clip.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/native_train_step_builtin.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_open.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/http_serve.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/inflate.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_send.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/reshape.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buffer.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_collections.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_read.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_square.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/channel_closed.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/list_truncate.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_sine.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_not.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/seed_random.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_envelope.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_detach.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/token_name.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_count.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/str_from_bytes.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/dispatch.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_text.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_dial.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_to_pcm16le.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/get_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_drop.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/sandbox_run.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_stream_push.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/ppu_render_frame.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/thread_join.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/stream_open.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_get.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_point.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/substr.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_delete.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/read_bytes_buf.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_delay.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/store_close.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/build_corpus.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_saw.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/recv.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/usleep.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/copy_into.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_listen.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_line.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_deinterleave.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_copy.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_volume.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_text_height.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/f64_from_bytes.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_play_loop.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_set.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_accept.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_sched_seed.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/buf_from_pcm16le.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/random_hex.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/exit.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/fill.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/net_recv.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/char_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/task_alive.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/gfx_circle.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/recv_timeout.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/send.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/try_recv.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/bit_and.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/audio_play.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/list_insert_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/list_remove_at.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tests/phase2/builtins/builtins/vm_run_bytecode.eigs` | Identify the existing integral-float success stdout line (or exit status) and Q7 exercises; R1.6a brief 2.2, blind-review F3, RULINGS B1-B5/C1-C10. | +| `tools/phase2_builtins.sh` | Dispatch the authoritative table checker/runner; brief 2 and principle 5. | +| `tests/phase2/builtins_landed.txt` | Explicit landed-sub-round declaration; brief §9.4 amendment (c). | +| `tests/phase2/observation_hooks.c` | Move the author shim byte-for-byte to its sole repository target; RULINGS A4 and BAR handoff. | +| `tests/phase2/builtins/builtins/_hooks/observation_hooks.c` | Remove the previous shim location after the byte-identical move; RULINGS A4. | + +## Recorded observations after the rulings + +RULINGS A3/B6 and BAR handoff authorize replacing the 25 profile placeholders with matched make server, make zlib and make server-db observations of 4ac31e67 and v0.46.0, with PostgreSQL. R1.6a-D2 authorizes re-recording the five revised probes; R1.6a-D1 / #1668 authorizes read_text.pre on 1fd4422c. All .pre records were then observed on the in-scope 1fd4422c builds and determinism.sh re-observed matched 1fd4422c/tag three times. Only read_text differs between the old and current baselines. No .branch changes. + +| Recorded file | Authority | +|---|---| +| `builtins/db_execute.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/db_execute.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/db_query_json.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/db_query_json.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/db_query_value.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/db_query_value.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_eval_loss.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_eval_loss.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_generate.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_generate.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_model_load.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/eigen_model_load.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/exec_capture.pre` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/exec_capture.tag` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/http_early_bind.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_early_bind.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_post.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_post.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_route.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_route.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_route_authed.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_route_authed.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_serve.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_serve.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_static.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/http_static.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/inflate.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/inflate.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/model_load_weights.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/model_load_weights.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/native_train_step_builtin.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/native_train_step_builtin.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_accept.pre` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_accept.tag` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_close.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_close.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_dial.pre` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_dial.tag` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_listen.pre` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_listen.tag` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_port.pre` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_port.tag` | R1.6a-D2; RULINGS A3/B6 and BAR handoff | +| `builtins/net_recv.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_recv.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_send.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/net_send.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_delete.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_delete.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_incr.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_incr.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_size.pre` | RULINGS A3/B6 and BAR handoff | +| `builtins/shared_size.tag` | RULINGS A3/B6 and BAR handoff | +| `builtins/read_text.pre` | R1.6a-D1 / #1668: current baseline 1fd4422c | +| `builtins.tsv` | Derived changes columns refreshed by the sole checker after authorized recordings; R1.6a-D1/D2 and RULINGS A1/A3 | + +## Frozen .branch amendments + +The shared table checker compares every `.branch` path and byte against the +pinned author-ruled import in `make_table.py`. Only orchestrator rulings may +amend that bar. Add one row per changed path, with its complete repository path, +the new bytes' lowercase SHA-256, ruling id and reason. An authorized deletion +uses `DELETED` in the SHA-256 column. Unchanged or stale amendments fail. +Historical import records above are already included in the pin. + +| Path | SHA-256 | Ruling | Reason | +|---|---|---|---| +| `tests/phase2/builtins/builtins/max.branch` | `1ca8c1c885cd796015304677d87b4782642d0a79026bd63fd28b67632b25ed9c` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/min.branch` | `eee02d9e7cd24809addb54e3c2df45f401134d5c279a7971a87fea26848781bc` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sign_extend.branch` | `bfc48781e0e0ab7eb480899c9975ccdbb810c36cbd2b297fd62231b41ee03bdf` | R1.6b-D1 | the subtraction is checked int64 arithmetic under rule (A): `sign_extend of [-9223372036854775553, 8]` raises `value` (#1894) | +| `tests/phase2/builtins/builtins/set_exit_code.branch` | `6e6f540c7b879ca35185d74c3840327c3f14ef37c5eb74c5d60531aef340620b` | R1.6-S2 | new int-requiring round-c builtin; main already rejects fractions and values outside 0..255; matched main and merged output agree | + +| `tests/phase2/builtins/builtins/fround.branch` | `0eef9c22f27663cb7b9cffcc4704039adacba202777e3b4eb254c5462d851118` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/i16_checked_add.branch` | `0d1db8055bba756521c06186282a8e52dea9a69497170c24307c4963f642c52d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_checked_mul.branch` | `e3364392ccc01620ab16d1f686af6e6b32c23fff34c307e87953f9e3ed9752c1` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_saturating_add.branch` | `6b035f5465db3f45f1418185a495ec931982d0f86f711febb0ea8db7374b9dfc` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_saturating_mul.branch` | `0febd6e720fa27afe0f310e5a65a1211282721173f8bdc6fdf17eb5b3107a87c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_saturating_sub.branch` | `0a092d049b2e5398489a1de775a0a16224eeb9bd1682bc671f45b43a5e5c8149` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_wrapping_add.branch` | `7e22d2083657f9924f9aba86ae4b6117527149530059200231d9b6b77bc950d0` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_wrapping_mul.branch` | `6e81c2cc8fa19ffbcfaf4191eb9744edd0d8f1ae4f3472a21ac330ad9bd3e954` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i16_wrapping_sub.branch` | `98a7b4a0d47b7f67eea180af8895a15667753a93fc2149a8c6ab9e80a260dc37` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_checked_add.branch` | `0d1db8055bba756521c06186282a8e52dea9a69497170c24307c4963f642c52d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_checked_mul.branch` | `e3364392ccc01620ab16d1f686af6e6b32c23fff34c307e87953f9e3ed9752c1` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_saturating_add.branch` | `2572384f77eeff69d3cc3a0b59c9f7b22dcb15ae3cf88c51f1496bee4077310f` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_saturating_mul.branch` | `0e736cdfa52e267cf5ce42823980e12b6ea2f71aacb5a3895bcd1c693bf6eeda` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_saturating_sub.branch` | `0dc9dce3bc6fd11f47a5722c4014fd95cdfd5c5368e8c1f8244107b4b4fa2c69` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_wrapping_add.branch` | `a8ffdc1d47645b8b337ce3f61683ca12e1f510a9954ed7a225101df8ef5caefa` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_wrapping_mul.branch` | `dae6aca4e1951a14146168a9e770c20880f1bca0a3d4a725fac999f1dcb68b66` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i32_wrapping_sub.branch` | `57dcfce2cc49056cfd5fa8445772c69cbaefc9589d994b32f8f80d9d3675e0d2` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_checked_add.branch` | `5e0363e573dae39234b80d31ae6107b60f6ea16385b994c7d96a2fd8b0ec0eb2` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_checked_mul.branch` | `4ceeef9b3b74172c9cb80a74d88766e7cc94d013ab6aea9ea4fc4419180a3613` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_checked_sub.branch` | `feca116df0e225630c8507d10d1c70a9842581b7c93432257042e0430002fe17` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_saturating_add.branch` | `d0af407460555f1215daca9e46b2e33e61daa5c6cecc6ff5eeefa36652fc8b91` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_saturating_mul.branch` | `5b9f38b6498e4e70bf98fd9eefb884bacab98ef89787a9c5e7deee369f1c2746` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_saturating_sub.branch` | `c18e190adab6ab52d6d15d22ec8f41ea1aab74e619229aef15deaecbb40adff0` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_wrapping_add.branch` | `6d67bf2d16362b4d150ea67f3be55879975c0a509a880084092afb7f11f19033` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_wrapping_mul.branch` | `480b829133ec0479736254d9f2451b85bd885abf6f754264a003af3d4b8ad120` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i64_wrapping_sub.branch` | `362647afce351af193cd556572bd40dd23ea3bab88525a2516d3da95a8296ee2` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_checked_add.branch` | `0d1db8055bba756521c06186282a8e52dea9a69497170c24307c4963f642c52d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_checked_mul.branch` | `e3364392ccc01620ab16d1f686af6e6b32c23fff34c307e87953f9e3ed9752c1` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_saturating_add.branch` | `bbaaa6e6a0f01240d5886bb5bfe8820f07d807da7a63e4265747a8b9c0f8b062` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_saturating_mul.branch` | `3f186a952f7b2f7145ec2b570d2254988d209e2634efc71f57b5b4cb95231094` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_saturating_sub.branch` | `0f2a53fa0c690ea451d134b54a9ebd4dfc22fe7b1d1e7629eb4ab0627dc4af96` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_wrapping_add.branch` | `f341a27c110eafc22f067bc59894177092207ded479b047add487cec4d4bfcac` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_wrapping_mul.branch` | `ef437e51787f6e26733aec215b06c95ffc4d0951e84a8fe116f10462ee8305cf` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/i8_wrapping_sub.branch` | `72c257561dd7188fcb87c412cd5c8eec03f3a1d3c4ea85d45e8d988899d534d8` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_checked_add.branch` | `07559104e693662984ec9c4b4800ea919e8a2c9eeb6fababb15ef72d09da999c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_checked_mul.branch` | `afaa117cff8ee24629cdd9c87dffd6784960e00de6f5a7747df592c208a9dc2b` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_saturating_add.branch` | `44384bee6cdcec41195ca4455b996d350f058293d40ee790b76c10caf068d910` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_saturating_mul.branch` | `4e12ed15428cbbabeff4066b5c73916184f9e35bb1e8b2eec1f36a045ae7d02b` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_saturating_sub.branch` | `209468fecd6822d2ccb329bf95a21b842fad859b481fc9864ef5a7bdd62e995e` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_wrapping_add.branch` | `dc311d0bac5980796200806862c231f38cba5bbae9ed68addba3fdf6631ee469` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_wrapping_mul.branch` | `63fdaa6bd49c73bf07639bef4d630b6d63bddab490edb46cc40dbaaa151a8b73` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u16_wrapping_sub.branch` | `0d697502cd63149fc6ec33913dc1cd8b91e17d68f787c865d8c7af8308c759e6` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_checked_add.branch` | `07559104e693662984ec9c4b4800ea919e8a2c9eeb6fababb15ef72d09da999c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_checked_mul.branch` | `afaa117cff8ee24629cdd9c87dffd6784960e00de6f5a7747df592c208a9dc2b` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_saturating_add.branch` | `18ea283a52dd090a3ab9f4d6513ef37fa7527aa10707264b1c1370d262cb5734` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_saturating_mul.branch` | `3ac8fbaca67cc8e3202e33861096657a43f4dd6a29979dd399a41a6ef7ae24a9` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_saturating_sub.branch` | `209468fecd6822d2ccb329bf95a21b842fad859b481fc9864ef5a7bdd62e995e` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_wrapping_add.branch` | `dc311d0bac5980796200806862c231f38cba5bbae9ed68addba3fdf6631ee469` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_wrapping_mul.branch` | `393238e5aa7084fa222aab2477c5841b506133d1f40ff57367a0f3dc5682a146` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u32_wrapping_sub.branch` | `82b539b692c6198addeca7f9e90e6b0cca35d6264bb9d3d7e109e2774d59c56d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_checked_add.branch` | `862b3c5f81afcf7ede2398f0e549fb0d1bb96fdb230028775f038739be862421` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_checked_mul.branch` | `fdfbeb2e9c9b5c511b9d07124610df9d11d96c43ed995dd3b8940aed988e4689` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_checked_sub.branch` | `feca116df0e225630c8507d10d1c70a9842581b7c93432257042e0430002fe17` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_compare.branch` | `957356d1ec2e6a7ac931dd78f8d96ef5528e129ce3006a657b8c5f6d44954fad` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_div.branch` | `0b8e9ea05c3e1fe1f40fe04797207534bb0b76c2eddac3ec3a5a96a189949bcc` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_rem.branch` | `c251912fc484ad1da3363a23750edea74cefd9430c565829ba016bd37a607419` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_saturating_add.branch` | `b5544aa426e8f99bebc1aacd8173bd4e790929cf45856fe1fd2010e13d73c787` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_saturating_mul.branch` | `f60d80adccc7e5e3570cc3d47639755e01fd7dd471dc9983bc6aa420f730c76e` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_saturating_sub.branch` | `385d6f2e1651a6c2b71e514df0455f028060ef10274b695fec8c2f34929b9f60` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_to_string.branch` | `1a73fea2beeb610e651dbe0bc0d089e6c45eb60b9bd09c97980caa70a7e4987d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_wrapping_add.branch` | `d10cd314a93b160387b82573c1c918f7f6a470a7210430aa80b65926fecdf6cf` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_wrapping_mul.branch` | `67d21a30b5e6a5c96790cf60855c3ad1bb85ed7489f27fd403053e7c97d5cb93` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u64_wrapping_sub.branch` | `8c65ea11ad10c34268117f56b3d72a5cf1bc044186cbd4d058f00c70aeae3910` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_checked_add.branch` | `07559104e693662984ec9c4b4800ea919e8a2c9eeb6fababb15ef72d09da999c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_checked_mul.branch` | `afaa117cff8ee24629cdd9c87dffd6784960e00de6f5a7747df592c208a9dc2b` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_checked_sub.branch` | `83430b1d05a7910f3cb358019d96be745ae4ecff96738583cb1098c11c21fb2c` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_saturating_add.branch` | `99267c28b402c43f6f0b0235cd323f0993e245882bd35ecc288169807c48e000` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_saturating_mul.branch` | `b3e07c6535b21886115efc30fc1ffa04f40870af7edec7d1f210e9248ed023bf` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_saturating_sub.branch` | `209468fecd6822d2ccb329bf95a21b842fad859b481fc9864ef5a7bdd62e995e` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_wrapping_add.branch` | `dc311d0bac5980796200806862c231f38cba5bbae9ed68addba3fdf6631ee469` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_wrapping_mul.branch` | `60da423dcf7c733566c6dc8786eacdfca47f445b4c545a692a33b2c2fdbbfb7d` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/u8_wrapping_sub.branch` | `f6caf9ca2c493a27eced40c47c9a000e8d0ac95b70fd12de204823880b55e686` | R1.6g-D1 | new N9 builtin; Rust std plus the brief operand/shape rules | +| `tests/phase2/builtins/builtins/str.branch` | `0c399d98c18da266dc150e39e78e616faadc324cca6e7c6c62ceb270e867913f` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/json_encode.branch` | `2ae19e560a3ea73c416cce1162049d723037f5b529e06806bc605c33fcdc2133` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/json_build.branch` | `c2b8bacb39eb6eef288670e99b3ba01d46c7cab2f94f7c05f47724c1488d9cb1` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/acos.branch` | `5958e4ae7fcc01a39f99fdd66c0a9618a9fad69c6e1d470d948f9129c1f6f4cd` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/add.branch` | `fe0387ddfdd246a088888fd214fed1abb2503f3e06a3b57cc6ea610bc0b578ec` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/asin.branch` | `5958e4ae7fcc01a39f99fdd66c0a9618a9fad69c6e1d470d948f9129c1f6f4cd` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/atan.branch` | `f242d5c6ac68248384bda8aa073f2ff5d23bb74e6fced26ba5ad2ac391d98ee6` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/audio_gain.branch` | `b7839145f6b8b9f413311bb0f984dad297356c9d7407628ba2e93a2f6c317c19` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/audio_mix.branch` | `512a7308c426e7ed94676d4c96400d79920afab58c39da9ea68270d8906de059` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_copy.branch` | `9ea81eba48f5b1b9e1f9799c9d8e8f275a0c805a082190eb7a683003a5e4ebb2` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_deinterleave.branch` | `2c8c419b85c034fbd529fd396fb0854d2982be71c29358c171e162c2b9721808` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_dot.branch` | `f9b37d961e959be8c731c30a43a007defa5fa16d3e5e60260bb3c356dbc51c00` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_fill.branch` | `38e227a7b6ada697071d60d8460bab5de95d9a4cdedcae32ccbc987be5e1752c` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_from_list.branch` | `e4a3b6623dd3f422e5712cdaf9f6b32133970df10636edb40f0e2143cbb889e1` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_from_pcm16le.branch` | `036cc08edd6d3a419692c4b1a85ac42d9a891591f55bc5fb806c4d7c8628dff8` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_get.branch` | `077ba41e4a2f93aeb44f20cb2928a585d7bc05cc158087e54aec2d097592081e` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_mix.branch` | `c78a105cdb9774cbbae3417e4aacb73742cdace27313acc705733b80b109d75b` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_peak.branch` | `b69875ae508284a12270c6791980349f86ff7d6ae32f9dfb29a05abccdc8e970` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_resample_linear.branch` | `e37904ae42856ac5a70eaea9fb9e29dd6fd35c5a17a6b31a846bc1e25c23c22d` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_scale_range.branch` | `c8f7e9898eb8c72ffa1bbed04503e8b4867f112300c617db3829aa67981e1d18` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_set.branch` | `b8c58d0a2f26c3baa094a66d073b236ae89d5e12ae0330a4afe75c85728be0a9` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/buf_to_pcm16le.branch` | `63e6df418a4e5f7f819bef3ee88186fd6548d13c06a9073b803868961e01c4e4` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/copy_into.branch` | `a46c24a542bf109a46c4c71890100268ffcfe361b97d42781df51080c27cd7b4` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/cos.branch` | `cf5baf9404a28aab2b4f931c9273949f0072ba197c2bdc27fa58d3d21b54ef0f` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/divide.branch` | `a23f3e073d81fba8891829bb4b4457f39f3b7fb1017bf46a25ef07487d441352` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/dot.branch` | `02946f430ffc9d87a7939a1a2fc200d8367fbcb42feaf973ca0a784500c78966` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/exp.branch` | `a61898dfe0d5ce58736cacff6a0c5ca15e39b3f3aecd198990cb9fd611786479` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/f64_from_bytes.branch` | `731e864c5cb1cb1d39f14554a42eaa605d14019315acedcfacfc332b0fffb172` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/gather.branch` | `824cd2ba031d256ef19a25fe1eedae6cad151a9f7791cd52aa38c717ce599461` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/get_at.branch` | `447670d74eca66707e4805343956d16e27c37428081713849ca3c1c9d1f6889a` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/join.branch` | `4e2dd8e15a945d27912c461362bb70083cabdaf0b4e31a18eac4fa5c5980d557` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/json_decode.branch` | `c0057af18be5254f0f9d1131b02b0d0cbf3594f3d277a9ed153d80463ebfab12` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/leaky_relu.branch` | `ef90cedf931e7e04a61ae277ac5ac010bf33cfca9e5213ea0082221651f34896` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/log.branch` | `b02e0dada2cf9a63f5086fbf5e8501f85dba7cf73b6eda01029647865e8c2986` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/matmul.branch` | `14616b915a5fcfdb5de37ee29ba8d6c547c90e14f0139940b3e313afadab0865` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/matmul_at.branch` | `70bae5f918939bebaf25f312aade4592af6172b2eb587dea09d6f3076a38c050` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/matmul_bt.branch` | `ba1e66cb245a46a025f5d6ee5bf6dce760552982acef6a4ec2599848c6c73569` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/mean.branch` | `ce229cfb4718fca2a9b298d927c341ea796ed6f961db2c4360b25e802c1b5225` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/norm.branch` | `d044f83f1db49f9968c9db1f30f1beafb2b8d39e4c7f162cadcc1474ddaa84ba` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/num.branch` | `7fb58551b9d82163c6703111cf84f5de26bb191de8a66d7fc5102ef8b8688162` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/numerical_grad.branch` | `4500785a3c9519176df2ab02f77fcad440fbf11f26227b29cb52496fd640e005` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/numerical_grad_cols.branch` | `2743a0bc2cabf3858d33ccd68cfab234c86adedc9665a0dbfc477f96aeb7a849` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/numerical_grad_rows.branch` | `0f8312d184ac73b4eeaeec5dfc903859353e0a783dd350c32c73a2855f0e2a03` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/observe.branch` | `1e0ee913120a78b044cf7b567cda8b1672e18fdb5faaa1af94ebb6db1193813a` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/pow.branch` | `68ab07f20b3ee61e305f67fa87230fd2c8a7730bbd4954642fd45136b37b9081` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/ppu_render_frame.branch` | `79f74efe5a437cbb3097669f3da297fb55c36d5682be19c74ba615679d486a47` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/print.branch` | `d2eaac663b00862a70c3ecd6d5f29aaaf9b793021e433025fb403ac65d6d6f01` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/nearest_in_range_all.branch` | `fa6f30b2af6cebed9c244889683a356120916ba20cd5b364579b21010f50634e` | S10-D2 | Issue #1888 comment 6402855103 expressly assigns next main-to-phase2 sync: nearest OTHER active entity, only first index 1 to 0; source and type witnesses frozen | +| `tests/phase2/builtins/builtins/proc_read_buf.branch` | `DELETED` | S10-D2 | Main #1691 retires the zero-consumer builtin; explicit frozen-case retirement, prior bank hashes recorded below | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.branch` | `DELETED` | S10-D2 | Main #1691 retires the zero-consumer builtin; supersedes this path's R1.7-D6 text amendment, prior bank hashes recorded below | +| `tests/phase2/builtins/builtins/read_bytes_buf.branch` | `0b52c1d8294591447cc198a3e8eb027ff8990555c52bf12fbdf93c912428ca84` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/relu.branch` | `24ae0c94c3ef3adf70443d1c90ab71b51e6212e09a7794c2dfc6d44c1a934601` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/scatter_add.branch` | `6efe02323a3891ed6ea11712c44546913ea92f4d0a064f1c5ec8daaaedc8a92d` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/set_at.branch` | `c90f8b78b48c20fae416587af85cc8277342614fa8935a3019ac814e3ac946c1` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sgd_update.branch` | `42c640c625a2ea086262edc98062262e808ecfc56a0e700970575d9b4c5c10fd` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sgd_update_cols.branch` | `74b47ef7f49936afc57f2858c3ead68863dd07091681bb275499ad4698237cce` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sgd_update_rows.branch` | `9ab519ac1c069e9ba1b13cee49b34569bd3da3a72fdf6efe541e92d6e64a3379` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sin.branch` | `e85163cfbb1a56ed2e29fc5ae75307f2a914acde489f6053e8e16ad55401d4bd` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/sqrt.branch` | `d02ec73520e2db4aa636bed3abe146241425f4b02141efce7a13e0b38ad22639` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/stream_write.branch` | `33b3dae3eecb631329b1c727730d240df187e9c7b7e5bb4cbe737d0d561803e0` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/tan.branch` | `1390b34eb629f519c834cd599144878efae95d3d6a8e1f80a51694a1d051ebad` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/tensor_load.branch` | `ba6c76c1208e36214edb1aadaddf6bc0507f2163720ad681a11ca9d0d39c1fa3` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/tensor_save.branch` | `be16c42b1944c56fbfad22e242be1472004e60995d31735a08237d9ca4d0b975` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/text_builder_append.branch` | `264d7aac65f70767b435d80e1c3a301a2025121d6fa39503c01b0ef0cc7d6ad5` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/write.branch` | `d646570e76559c96769f89afa8d1160cde5e12bed99a3b36f2fb7d6bb7ca7b7b` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/zeros.branch` | `2530cd624a068d1882816dec67cdb9848c1011f5307c0c27fb514cc108b18160` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | +| `tests/phase2/builtins/builtins/zeros_like.branch` | `8d3e0cdb2aee733291b724abd3f038bd9cdf2a90288d24072b87838c538a130c` | R1.7-D6 | N6 Python repr only; source-bound kinds and per-line bit proof; prior numeric/value rulings retained | + +| `tests/phase2/builtins/builtins/read_text.branch` | `fd9b265f1a9a5e33c8f9d225b8c0befc1f78a536c43a28cf3cb0ccfbde996cd9` | S10-D3 | Main e0d49159 closes2076: same raise message naming the escape; only over-cap diagnostic text, original sizes/kinds/result/status unchanged | + +## R1.6b bar amendments + +The orchestrator ruled on R1.6b's two stops (issues #1894 and #1895): + +> R1.6b-D1: sign_extend's subtraction is checked int64 arithmetic, under #1758's rule (A), "overflow raises, no wraparound". So sign_extend of [-9223372036854775553, 8] raises value, naming sign_extend. + +> R1.6b-D2: an empty min/max raises value, in both call forms (min of [] with zero arguments, and min of ([]) with one empty list), and likewise for max. + +Each new case is appended after its probe's last line, so every existing +executable line number and stdout line number is unchanged (sign_extend's +integral-float success stays stdout line 5). The three probes' `.pre` and +`.tag` were re-recorded on the R1.5-tip and v0.46.0 release binaries; their +earlier lines are byte-identical to the previous records, and only the new +lines were added. + +| File | Ruling | +|---|---| +| `tests/phase2/builtins/BAR.md` | R1.6b-D1 and R1.6b-D2 | +| `tests/phase2/builtins/builtins/max.eigs` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/max.pre` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/max.tag` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/min.eigs` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/min.pre` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/min.tag` | R1.6b-D2 | +| `tests/phase2/builtins/builtins/sign_extend.eigs` | R1.6b-D1 | +| `tests/phase2/builtins/builtins/sign_extend.pre` | R1.6b-D1 | +| `tests/phase2/builtins/builtins/sign_extend.tag` | R1.6b-D1 | +| `tests/phase2/builtins_changes.md` | R1.6b-D1 and R1.6b-D2 | + + +## R1.6b-D3 — http_post historical LOSS and staging + +The orchestrator ruled on #1758, comment 6320170738: `http_post` is a LOSS +row and lands in R1.6b. Its pre-R1.6 header path rounds 9007199254740993 to +9007199254740992 (B6); §6's `-` loss mark was an error. The shared N14 JSON +decoder fixes that loss. The class remains `other`; `round_of` assigns every +LOSS row to b. Planned counts become b 46 and f 19. + +The probe, `.pre`, `.tag` and `.branch` are unchanged. The frozen `.branch` +pin and its three D1/D2 SHA-256 amendments above are unchanged. + +| File | Ruling and change | +|---|---| +| `docs/design/phase2-numbers.md` | R1.6b-D3: correct §6 loss/note and b/f counts | +| `docs/design/phase2-inventory.txt` | R1.6b-D3: mirror the corrected http_post LOSS classification | +| `tests/phase2/builtins/make_table.py` | R1.6b-D3: planned b/f counts 46/19 | +| `tests/phase2/builtins/builtins.tsv` | R1.6b-D3: generator assigns http_post to b | +| `tests/phase2/builtins_changes.md` | R1.6b-D3: record ruling and complete amendment scope | + +## Main sync #5 — R1.6-S1 and R1.6-S2 + +R1.6-S1 (re-scoped): take main's rm deletion and remove_file bridge. rm +had no frozen table row, so no row is removed. The N5.store-key-fraction-raises +MAP cleanup now calls remove_file; MAP source files have no SHA pin, and its +assertion/output remain unchanged. + +R1.6-S2: set_exit_code is int-requiring, round c, release profile, closed_in +R1.6-S2 because main already has its final fractional/range refusal. Its .pre +is the merged output; .branch is independently recorded on exact main; they +match. Its .tag is recorded on matched v0.46.0, which lacks the builtin. +The new frozen amendment above authorizes only the added .branch. The pin +remains unchanged; current totals are 233 rows, c=77, and 235 cases. + +The probe header identifies fractional and integral-float-success exercises +and rc=3. The checker now accepts that process-status witness for exit and +set_exit_code only, keeping the precise called symbol and recorded rc check. + +Files: docs/design/phase2-numbers.md; tests/phase2/builtins/{BAR.md,make_table.py, +rowmeta.tsv,cases.tsv,builtins.tsv,builtins/set_exit_code.eigs, +builtins/set_exit_code.pre,builtins/set_exit_code.branch,builtins/set_exit_code.tag}; +tests/phase2/rows/N5.store-key-fraction-raises.eigs; this amendment record. + +## R1.6e-D1 — audio channels (pre-registration) + +Ruling #1922 comment 6355101247: audio_open, audio_capture_open and +audio_stream_open require N5 channels in 1..255 before SDL/device/tape +side effects. Zero is excluded because SDL substitutes its default. Frequency +remains C int. Counts within the channel domain pass to SDL without a duplicated +support table. MAP N5.audio-channel-domain pre-registers 0, 256, 257, -1, 1.5 +refusal and 255/1 reader success before migration. No frozen expectation changes. +The three design notes are corrected with their behavior migration commit. + + +## R1.6e — legacy audio suite expectation + +The ordinary `tests/test_audio.eigs` assertion for `audio_play_loop` with +`1e20` loops now catches `value`: N5 and the existing §6 C-int range require +refusal before the mixer, in both strict modes. The former zero stand-in +assertion predates the numeric readers. The in-range legacy 10000-loop +ceiling stays unchanged. No frozen probe or expectation is amended. + +## R1.6f-D1 — shared store handle reader (pre-registration) + +#1924 comment 6367017285 assigns get_store_why → store_arg to f for all nine callers: store_close, store_collections, store_count, store_drop, store_get, store_put, store_delete, store_query, store_update. Every caller refuses non-exact, non-finite or out-of-range numeric id/generation fields with value before store access. MAP N5.store-handle-fraction-raises pre-registers both fractional fields in every caller before migration. No frozen .branch changes; already-b rows retain their expectations. + +## R1.6g-D1 — N9 pre-registration + +The R1.6g brief authorizes 77 new Rust-derived branch expectations and five MAP rows before builtins_int.c exists. No existing .branch changes. Table membership follows when the builtins exist. Oracle inputs, sources and verbatim results are in oracle/n9*. + +## R1.7-D2 float text amendments + +R1.7-D2 authorizes only the N6 Python `repr` re-render of numeric float payloads. +Seven frozen probes were examined: str, fround, json_encode, json_build, +json_path, min and max. Six are amended; json_path already has the N6 spelling +`2.5` and stays byte-identical. The checker compares each line with the +pre-N6 commit b6f9db3879be5578de787ad6cf0d235b809f0e7c: numeric positions, +bit-identical doubles, exact integer spellings, and every non-float byte. +Source-bound runtime kind witnesses disambiguate old integral float spellings; +old text such as `3` cannot itself establish a kind. For json_path the numeric +payload has the JSON input's lexical kind, before raw-text extraction. + +The historical R1.6b-D2 ruling still establishes min/max empty-call errors +(#1895), and R1.6g-D1 still establishes fround's N9 semantics. The accepted +rows above now pin their D2-rendered bytes; the prior byte hashes and probe-source +hashes remain recorded in `r17_float_text.json`. No earlier value or kind ruling +is superseded. + +Run `python3 tools/phase2_float_amend.py --baseline-binary BASE --binary TIP` +to check the delivered amendments and their source-bound kind witnesses. +Its one-time `--self-test` calibration rejects changed values, kinds, integers, +non-float text, positions and missing/extra tokens or files. + +## R1.7-D5 — table metadata after the D2 re-render + +Ruling R1.7-D5 on #1758 scopes A2 to earlier closures and records the final +float text deferred by the design to R1.7. In both rowmeta.tsv and the derived +builtins.tsv, json_build and json_encode change closed_in from R1.2/R1.3 to +R1.7. In builtins.tsv, changes_vs_pre changes from no to yes for json_build, +json_encode and str. These are all five changed table cells; no other cell +changes. The other D2 rows already derive yes; json_path was not amended. + +The R1.7 closure check reuses phase2_float_amend.py's source-bound kind +metadata and check_pair on each .pre/.branch pair: same line/token positions, +bit-identical doubles, identical integer spellings and non-float bytes, and +the complete Python repr re-render. It refuses an R1.7 closure without D2 +kind provenance. Every other closed row retains A2's .pre == .branch check. +No expectation or probe source changes in this amendment. + +builtins_landed.txt now declares R1.7, which make_table.py's shared parser +accepts. run_probes.sh checks a row closed in R1.7 against .branch whenever +R1.7 is declared, independently of the row's original R1.6 migration group. +The public tools/phase2_builtins.sh entry point retains all profile, hook, +stdin, frozen-bar, membership and accounting checks. This follows principle +5, Checks are anchored outside the system, narrow, and few: the D2 comparator +has one implementation, and the two fault plants are one-time calibration. + +Continuation 4's plants run through that public entry point in isolated +clones. json_build.pre changes a non-float key (a to z) while leaving the +frozen .branch hash intact; D5 must reject it. abs.pre changes an integer +spelling to a float spelling with its derived flag synchronized to yes; +the pre-R1.6 A2 closure must still reject it. Each plant must produce only +its named closure diagnostic, rather than a neighboring metadata/hash error. +Checkpoint ownership and exact run receipts are in the continuation handoff. + +## R1.7-D6 — derived float re-render population + +The expanded proof prints each amendment's SHA. Flush the parent checker's +stdout before starting the probe runner, so these records and the child's +`ran=` summary remain complete lines. The final profile receipts require +exactly one complete runner summary; the earlier spliced logs are retained +as calibration evidence, superseded by the final reruns. + +The population is the union of the gate's live comparisons over release, server, server-db and zlib. Every frozen row is examined on each profile. D6 adds 62 rows to the six already amended under D2; the cumulative population is 68. The unchanged json_path control is outside the amendment set. Exact per-profile observations, commands and hashes: continue5-population.json in the R1.7 handoff. + +The checker retains D2's per-line, bit-identical double comparison, integer spelling and all other bytes. All numeric tokens have source-bound kinds executed on the pre-N6 binary and all four candidate profiles. Numeric-looking strings echoed by num are source-bound string witnesses and remain byte-identical; only num's numeric results are re-rendered. The original frozen programs are unchanged; the R17-D2-KINDS marker and witnesses are appended only to temporary copies, using the registered stdin fixture where present. + +Metadata is derived after amendment. Previously closed amended rows close in R1.7 under D5; every other pre-R1.6 closure retains A2 pre == branch. The existing R1.7 closures are unchanged in this continuation. Derived changes_vs_pre cells and all prior amendment records are detailed below. + +| Path | SHA-256 | Profiles | Metadata | +|---|---|---|---| +| `tests/phase2/builtins/builtins/acos.branch` | `5958e4ae7fcc01a39f99fdd66c0a9618a9fad69c6e1d470d948f9129c1f6f4cd` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/add.branch` | `fe0387ddfdd246a088888fd214fed1abb2503f3e06a3b57cc6ea610bc0b578ec` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/asin.branch` | `5958e4ae7fcc01a39f99fdd66c0a9618a9fad69c6e1d470d948f9129c1f6f4cd` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/atan.branch` | `f242d5c6ac68248384bda8aa073f2ff5d23bb74e6fced26ba5ad2ac391d98ee6` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/audio_gain.branch` | `b7839145f6b8b9f413311bb0f984dad297356c9d7407628ba2e93a2f6c317c19` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/audio_mix.branch` | `512a7308c426e7ed94676d4c96400d79920afab58c39da9ea68270d8906de059` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/buf_copy.branch` | `9ea81eba48f5b1b9e1f9799c9d8e8f275a0c805a082190eb7a683003a5e4ebb2` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_deinterleave.branch` | `2c8c419b85c034fbd529fd396fb0854d2982be71c29358c171e162c2b9721808` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_dot.branch` | `f9b37d961e959be8c731c30a43a007defa5fa16d3e5e60260bb3c356dbc51c00` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_fill.branch` | `38e227a7b6ada697071d60d8460bab5de95d9a4cdedcae32ccbc987be5e1752c` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_from_list.branch` | `e4a3b6623dd3f422e5712cdaf9f6b32133970df10636edb40f0e2143cbb889e1` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/buf_from_pcm16le.branch` | `036cc08edd6d3a419692c4b1a85ac42d9a891591f55bc5fb806c4d7c8628dff8` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_get.branch` | `077ba41e4a2f93aeb44f20cb2928a585d7bc05cc158087e54aec2d097592081e` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_mix.branch` | `c78a105cdb9774cbbae3417e4aacb73742cdace27313acc705733b80b109d75b` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_peak.branch` | `b69875ae508284a12270c6791980349f86ff7d6ae32f9dfb29a05abccdc8e970` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_resample_linear.branch` | `e37904ae42856ac5a70eaea9fb9e29dd6fd35c5a17a6b31a846bc1e25c23c22d` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_scale_range.branch` | `c8f7e9898eb8c72ffa1bbed04503e8b4867f112300c617db3829aa67981e1d18` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_set.branch` | `b8c58d0a2f26c3baa094a66d073b236ae89d5e12ae0330a4afe75c85728be0a9` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/buf_to_pcm16le.branch` | `63e6df418a4e5f7f819bef3ee88186fd6548d13c06a9073b803868961e01c4e4` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/copy_into.branch` | `a46c24a542bf109a46c4c71890100268ffcfe361b97d42781df51080c27cd7b4` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/cos.branch` | `cf5baf9404a28aab2b4f931c9273949f0072ba197c2bdc27fa58d3d21b54ef0f` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/divide.branch` | `a23f3e073d81fba8891829bb4b4457f39f3b7fb1017bf46a25ef07487d441352` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/dot.branch` | `02946f430ffc9d87a7939a1a2fc200d8367fbcb42feaf973ca0a784500c78966` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/exp.branch` | `a61898dfe0d5ce58736cacff6a0c5ca15e39b3f3aecd198990cb9fd611786479` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/f64_from_bytes.branch` | `731e864c5cb1cb1d39f14554a42eaa605d14019315acedcfacfc332b0fffb172` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/fround.branch` | `0eef9c22f27663cb7b9cffcc4704039adacba202777e3b4eb254c5462d851118` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/gather.branch` | `824cd2ba031d256ef19a25fe1eedae6cad151a9f7791cd52aa38c717ce599461` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/get_at.branch` | `447670d74eca66707e4805343956d16e27c37428081713849ca3c1c9d1f6889a` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/join.branch` | `4e2dd8e15a945d27912c461362bb70083cabdaf0b4e31a18eac4fa5c5980d557` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/json_build.branch` | `c2b8bacb39eb6eef288670e99b3ba01d46c7cab2f94f7c05f47724c1488d9cb1` | release, server, server-db, zlib | closed_in R1.7; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/json_decode.branch` | `c0057af18be5254f0f9d1131b02b0d0cbf3594f3d277a9ed153d80463ebfab12` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/json_encode.branch` | `2ae19e560a3ea73c416cce1162049d723037f5b529e06806bc605c33fcdc2133` | release, server, server-db, zlib | closed_in R1.7; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/leaky_relu.branch` | `ef90cedf931e7e04a61ae277ac5ac010bf33cfca9e5213ea0082221651f34896` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/log.branch` | `b02e0dada2cf9a63f5086fbf5e8501f85dba7cf73b6eda01029647865e8c2986` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/matmul.branch` | `14616b915a5fcfdb5de37ee29ba8d6c547c90e14f0139940b3e313afadab0865` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/matmul_at.branch` | `70bae5f918939bebaf25f312aade4592af6172b2eb587dea09d6f3076a38c050` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/matmul_bt.branch` | `ba1e66cb245a46a025f5d6ee5bf6dce760552982acef6a4ec2599848c6c73569` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/max.branch` | `1ca8c1c885cd796015304677d87b4782642d0a79026bd63fd28b67632b25ed9c` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/mean.branch` | `ce229cfb4718fca2a9b298d927c341ea796ed6f961db2c4360b25e802c1b5225` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/min.branch` | `eee02d9e7cd24809addb54e3c2df45f401134d5c279a7971a87fea26848781bc` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/norm.branch` | `d044f83f1db49f9968c9db1f30f1beafb2b8d39e4c7f162cadcc1474ddaa84ba` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/num.branch` | `7fb58551b9d82163c6703111cf84f5de26bb191de8a66d7fc5102ef8b8688162` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/numerical_grad.branch` | `4500785a3c9519176df2ab02f77fcad440fbf11f26227b29cb52496fd640e005` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/numerical_grad_cols.branch` | `2743a0bc2cabf3858d33ccd68cfab234c86adedc9665a0dbfc477f96aeb7a849` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/numerical_grad_rows.branch` | `0f8312d184ac73b4eeaeec5dfc903859353e0a783dd350c32c73a2855f0e2a03` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/observe.branch` | `1e0ee913120a78b044cf7b567cda8b1672e18fdb5faaa1af94ebb6db1193813a` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/pow.branch` | `68ab07f20b3ee61e305f67fa87230fd2c8a7730bbd4954642fd45136b37b9081` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/ppu_render_frame.branch` | `79f74efe5a437cbb3097669f3da297fb55c36d5682be19c74ba615679d486a47` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/print.branch` | `d2eaac663b00862a70c3ecd6d5f29aaaf9b793021e433025fb403ac65d6d6f01` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.branch` | `f786f6b5395f54fa5f9c39c32f18ebc2d0fcda2099395156af684f37783cfe3b` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/read_bytes_buf.branch` | `0b52c1d8294591447cc198a3e8eb027ff8990555c52bf12fbdf93c912428ca84` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/relu.branch` | `24ae0c94c3ef3adf70443d1c90ab71b51e6212e09a7794c2dfc6d44c1a934601` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/scatter_add.branch` | `6efe02323a3891ed6ea11712c44546913ea92f4d0a064f1c5ec8daaaedc8a92d` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/set_at.branch` | `c90f8b78b48c20fae416587af85cc8277342614fa8935a3019ac814e3ac946c1` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/sgd_update.branch` | `42c640c625a2ea086262edc98062262e808ecfc56a0e700970575d9b4c5c10fd` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/sgd_update_cols.branch` | `74b47ef7f49936afc57f2858c3ead68863dd07091681bb275499ad4698237cce` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/sgd_update_rows.branch` | `9ab519ac1c069e9ba1b13cee49b34569bd3da3a72fdf6efe541e92d6e64a3379` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/sin.branch` | `e85163cfbb1a56ed2e29fc5ae75307f2a914acde489f6053e8e16ad55401d4bd` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/sqrt.branch` | `d02ec73520e2db4aa636bed3abe146241425f4b02141efce7a13e0b38ad22639` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/str.branch` | `0c399d98c18da266dc150e39e78e616faadc324cca6e7c6c62ceb270e867913f` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/stream_write.branch` | `33b3dae3eecb631329b1c727730d240df187e9c7b7e5bb4cbe737d0d561803e0` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/tan.branch` | `1390b34eb629f519c834cd599144878efae95d3d6a8e1f80a51694a1d051ebad` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/tensor_load.branch` | `ba6c76c1208e36214edb1aadaddf6bc0507f2163720ad681a11ca9d0d39c1fa3` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/tensor_save.branch` | `be16c42b1944c56fbfad22e242be1472004e60995d31735a08237d9ca4d0b975` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/text_builder_append.branch` | `264d7aac65f70767b435d80e1c3a301a2025121d6fa39503c01b0ef0cc7d6ad5` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/write.branch` | `d646570e76559c96769f89afa8d1160cde5e12bed99a3b36f2fb7d6bb7ca7b7b` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | +| `tests/phase2/builtins/builtins/zeros.branch` | `2530cd624a068d1882816dec67cdb9848c1011f5307c0c27fb514cc108b18160` | release, server, server-db, zlib | closed_in -; changes_vs_pre yes -> yes | +| `tests/phase2/builtins/builtins/zeros_like.branch` | `8d3e0cdb2aee733291b724abd3f038bd9cdf2a90288d24072b87838c538a130c` | release, server, server-db, zlib | closed_in -; changes_vs_pre no -> yes | + +Prior accepted-amendment records (preserved provenance): + +- | `tests/phase2/builtins/builtins/fround.branch` | `0eef9c22f27663cb7b9cffcc4704039adacba202777e3b4eb254c5462d851118` | R1.7-D2 | N6 Python repr float text only; prior R1.6g-D1 fround numeric semantics | +- | `tests/phase2/builtins/builtins/json_build.branch` | `c2b8bacb39eb6eef288670e99b3ba01d46c7cab2f94f7c05f47724c1488d9cb1` | R1.7-D2 | N6 Python repr float text only | +- | `tests/phase2/builtins/builtins/json_encode.branch` | `2ae19e560a3ea73c416cce1162049d723037f5b529e06806bc605c33fcdc2133` | R1.7-D2 | N6 Python repr float text only | +- | `tests/phase2/builtins/builtins/max.branch` | `1ca8c1c885cd796015304677d87b4782642d0a79026bd63fd28b67632b25ed9c` | R1.7-D2 | N6 Python repr float text only; prior R1.6b-D2 empty-call errors (#1895) | +- | `tests/phase2/builtins/builtins/min.branch` | `eee02d9e7cd24809addb54e3c2df45f401134d5c279a7971a87fea26848781bc` | R1.7-D2 | N6 Python repr float text only; prior R1.6b-D2 empty-call errors (#1895) | +- | `tests/phase2/builtins/builtins/str.branch` | `0c399d98c18da266dc150e39e78e616faadc324cca6e7c6c62ceb270e867913f` | R1.7-D2 | N6 Python repr float text only | + +One-time public-entry plants: json_build.pre a -> z must fail the D5 non-float comparator; abs.pre integer -> float spelling (with derived flag synchronized) must fail A2; acos' derived candidate digit 0.0 -> 1.0 must fail D2's double-bit comparator naming acos. The last plant is checked through phase2_builtins.sh's population-proof preflight, rather than a stale frozen hash. This follows principle 5, Checks are anchored outside the system, narrow, and few. + +## S10-D2 — explicit frozen retirement of `proc_read_buf` (2026-10-28) + +Main #1691 (`5029856a`) removes `proc_read_buf` under #1677's zero-consumer +rule after removal of `proc_spawn`. The orchestrator's direct PR #1776 ruling +S10-D2 authorizes these retirements under R1.7-D2 and supersedes A5/C10's +retired-builtin witnesses. The prior accepted bank is +`ed09f908603a414f77ead78e241fa10aff7f2f38`; each path below is removed, +with its exact last accepted bytes identified by SHA-256. + +| Retired path | Prior accepted SHA-256 | Authority | +| --- | --- | --- | +| `tests/phase2/builtins/builtins/_stdin/hi` | `3639efcd08abb273b1619e82e78c29a7df02c1051b1820e99fc395dcaa3326b8` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.branch` | `76b9f9203919cf0f5b50c95f7ba849c0f66dc9f9d46910ecc966a1308ec409a9` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.eigs` | `e14e9ac3f0a8421380d4f52db2778bd28c7567f8cb17358d386f94f65bd80221` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.branch` | `f786f6b5395f54fa5f9c39c32f18ebc2d0fcda2099395156af684f37783cfe3b` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.eigs` | `7af30b36bd60d04b0ff0e6bd5d690fa15db493dd25c88cd56813304d0be6cce3` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.pre` | `6a4eac6578eaf7ddde381600841f2c7f83ed104a443ead1da21ce8824ccf1c7a` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.hi.tag` | `320688535eda1817e2f08d56e868a37f4c3b1e7c41430b87a61b151f55a6f1d4` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.pre` | `6f490d81a564a89f7ba09b44f3f218c14edbd4997d5d8855603739e96a0e5e42` | S10-D2 retirement | +| `tests/phase2/builtins/builtins/proc_read_buf.tag` | `6f490d81a564a89f7ba09b44f3f218c14edbd4997d5d8855603739e96a0e5e42` | S10-D2 retirement | + +The two `.branch` amendment rows above now read `DELETED | S10-D2`. +The `proc_read_buf.hi.branch` deletion supersedes its R1.7-D6 amendment +(`f786f6b5395f54fa5f9c39c32f18ebc2d0fcda2099395156af684f37783cfe3b`), +whose historical derivation table remains intact. `r17_float_text.json` +removes only that retired path from the active re-render population (68 to +67) and records both retirements with their exact bank anchor and prior +source/branch hashes. The original four-profile derivation digest remains +historical provenance; its scope is explicit. The float amendment checker +accepts only these two missing paths after checking the bank hashes, +absence of both source/branch files, and the matching `DELETED` amendments. +Every other frozen path and byte retains the previous checks. + +The table/case metadata removes the `proc_read_buf` row and both cases: +309 builtin rows, 310 cases, round c=76; other round memberships stay fixed. +P23 (stdin-fixture membership) and R6 (missing stdin fixture) are retired +because the removed `proc_read_buf.hi` case was the only stdin-fixture user. +The fixture membership predicate still compares both directions and treats +an absent fixture directory as the empty set. Remaining plant IDs are fixed. + +Existing `builtins/read_bytes_buf.eigs` and `.branch` preserve A5/C10's +host-byte-source f64 intent without any frozen-byte edit: the buffer read +pins `[3, 1.0, "float"]`, then `[3, "float"]` observes `d[2]` after an +integral-float cap. This distinguishes a runtime int-kind fault even when +numeric values agree. Its `.branch` SHA-256 remains +`0b52c1d8294591447cc198a3e8eb027ff8990555c52bf12fbdf93c912428ca84`. +No new case or changelog fragment is added for this pure merge. Checker +calibrations and the one-time real typing fault are scheduled in the +sync10 drivers; this amendment note does not claim they ran. + +### S10-D2 sibling sweep: recorded #1888 nearest-other decision + +[Issue #1888 comment 6402855103](https://github.com/InauguralSystems/EigenScript/issues/1888#issuecomment-6402855103) +expressly rules that each entity receives its nearest OTHER active entity, +that the entity itself is never a candidate, and that the next main-to-phase2 +sync takes it. Main's #1888/#2010 implements that decision. BAR.md had already +filed the old self-neighbor behavior under #1888; no numeric ruling required +that incidental self index. S10-D2 step 3 applies the recorded-decision rule. + +Only `builtins/nearest_in_range_all.branch` changes: `[1, "int", "float", "float"]` +becomes `[0, "int", "float", "float"]`; `rc=0`, source, scenario and all kind +witnesses remain byte-identical. Bank source SHA-256 is +`abf6d29d7dff1a56f1baed7c89dbf6e68025898c760ff598ce6da21626f5afa5`; +prior branch SHA-256 is +`b10fe4c4d96d69ca28c3216cb3b82b8db1232808558240afcd320eb943bee6d8`; +approved replacement SHA-256 is +`fa6f30b2af6cebed9c244889683a356120916ba20cd5b364579b21010f50634e`. +The exact SHA amendment above is separate from the N6 population. The checker +validates this single path, source hash and exact one-field transformation +against the same immutable R1.8 bank before installing its approved baseline; +it does not treat arbitrary numeric differences as float re-renders. + +## S10-D3 — explicit read_text diagnostic amendment (2026-10-29) + +Main `e0d491598ee19108802bf25c99c5b59d23039117` closes [#2076](https://github.com/InauguralSystems/EigenScript/issues/2076). Its Done-when requires "the same raise message naming the escape" for all three capped readers, from one shared helper. The orchestrator explicitly authorizes only the over-cap diagnostic line in `builtins/read_text.branch`; the case source, .pre/.tag, sizes, kinds, result rows and rc stay unchanged. This is a builtin self-test diagnostic, with no Phase2 number rule of its own. + +Prior bank SHA-256 `460914ed8657b39b51c90f6401e02e920a30b85ff4c97587884650006193e2ae`; approved S10-D3 SHA-256 `fd9b265f1a9a5e33c8f9d225b8c0befc1f78a536c43a28cf3cb0ccfbde996cd9`. The existing exact frozen-amendment mechanism pins the new bytes. diff --git a/tests/phase2/builtins_landed.txt b/tests/phase2/builtins_landed.txt new file mode 100644 index 000000000..7c7288c24 --- /dev/null +++ b/tests/phase2/builtins_landed.txt @@ -0,0 +1,10 @@ +# Completed builtin rounds, one full name per line. +# R1.6a migrates no builtin rows. +R1.6b +R1.6c +R1.6d +R1.6e +R1.6f +R1.6g +R1.6z +R1.7 diff --git a/tests/phase2/calib/MAP.md b/tests/phase2/calib/MAP.md new file mode 100644 index 000000000..a426e9e2f --- /dev/null +++ b/tests/phase2/calib/MAP.md @@ -0,0 +1,67 @@ +# tests/phase2/calib/MAP.md: phase2_check.sh --calibrate fixtures (design 9.2) + +Every row ends in a line that fails on the v0.46.0 tag (#1740: `5.5 & 1` +raises `value` on the branch, the tag truncates), so an honest row has +tag_fail == branch_pass. `-plant` rows are the negative witness plants of +design 9.1: the claimed op does not run as claimed, so each must be VACUOUS. + +### N1 +- `cal.verdict` [R1.3] a straight-line row: #1740 raises `value` on a fractional bitwise operand, the tag truncates + -> see cal.verdict.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) +- `cal.fn-keep` [R1.3] keep runs natively to its return on its 60th call + -> see cal.fn-keep.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: F witness: fn keep ops: SET_LOCAL +- `cal.fn-keep-plant` [R1.3] plant: an unsupported op (LIST) ahead of the claimed one + -> see cal.fn-keep-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: F witness: fn keep ops: SET_LOCAL +- `cal.fn-keep-10calls-plant` [R1.3] plant: 10 calls instead of 60, so the JIT never compiles keep + -> see cal.fn-keep-10calls-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: F witness: fn keep ops: SET_LOCAL + +### N3 +- `cal.leaf-sq` [R1.3] the leaf computes sq: no entries normally, 1 under trace + -> see cal.leaf-sq.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: L witness: leaf sq ops: MUL +- `cal.leaf-sq-plant` [R1.3] plant: two statements, not leaf-eligible + -> see cal.leaf-sq-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: L witness: leaf sq ops: MUL +- `cal.fnstop-cat` [R1.3] the ADD guard bails on two strings + -> see cal.fnstop-cat.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: F witness: fn-stop cat ADD ops: ADD +- `cal.fnstop-cat-plant` [R1.3] plant: two floats, so ADD runs natively to the return + -> see cal.fnstop-cat-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: F witness: fn-stop cat ADD ops: ADD +- `cal.osrstop-g` [R1.3] every OSR entry hands back at the string ADD + -> see cal.osrstop-g.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: O witness: osr-stop g ADD ops: ADD +- `cal.osrstop-g-plant` [R1.3] plant: a float ADD, which runs natively to the sentinel + -> see cal.osrstop-g-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: O witness: osr-stop g ADD ops: ADD + +### N5 +- `cal.leafgen-nth` [R1.3] the leaf INDEX_GET bails on a string, the generic call indexes + -> see cal.leafgen-nth.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: L witness: leaf-gen nth ops: INDEX_GET +- `cal.leafgen-nth-plant` [R1.3] plant: a list, which the leaf computes itself + -> see cal.leafgen-nth-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: L witness: leaf-gen nth ops: INDEX_GET +- `cal.osr-fill` [R1.3] the OSR thunk runs the body natively to the return sentinel + -> see cal.osr-fill.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: O witness: osr fill ops: INDEX_SET +- `cal.osr-fill-plant` [R1.3] plant: a string concat ahead of the claimed op, so INDEX_SET never runs natively + -> see cal.osr-fill-plant.out + tag: the last line prints 1 (read: v0.46.0 truncates a bitwise operand) + shape: O witness: osr fill ops: INDEX_SET diff --git a/tests/phase2/calib/cal.fn-keep-10calls-plant.eigs b/tests/phase2/calib/cal.fn-keep-10calls-plant.eigs new file mode 100644 index 000000000..f92c03f74 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep-10calls-plant.eigs @@ -0,0 +1,20 @@ +# ROW: cal.fn-keep-10calls-plant +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn keep +# OPS: SET_LOCAL +# plant: 10 calls instead of 60, so the JIT never compiles keep +define keep(q) as: + t is q + return t +k is 0 +r is 0 +loop while k < 10: + r is keep of 2.5 + k is k + 1 +print of r +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.fn-keep-10calls-plant.out b/tests/phase2/calib/cal.fn-keep-10calls-plant.out new file mode 100644 index 000000000..f972948d1 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep-10calls-plant.out @@ -0,0 +1,2 @@ +2.5 +value diff --git a/tests/phase2/calib/cal.fn-keep-plant.eigs b/tests/phase2/calib/cal.fn-keep-plant.eigs new file mode 100644 index 000000000..642f97213 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep-plant.eigs @@ -0,0 +1,21 @@ +# ROW: cal.fn-keep-plant +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn keep +# OPS: SET_LOCAL +# plant: an unsupported op (LIST) ahead of the claimed one +define keep(q) as: + zz is [q] + t is q + return t +k is 0 +r is 0 +loop while k < 60: + r is keep of 2.5 + k is k + 1 +print of r +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.fn-keep-plant.out b/tests/phase2/calib/cal.fn-keep-plant.out new file mode 100644 index 000000000..f972948d1 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep-plant.out @@ -0,0 +1,2 @@ +2.5 +value diff --git a/tests/phase2/calib/cal.fn-keep.eigs b/tests/phase2/calib/cal.fn-keep.eigs new file mode 100644 index 000000000..15c2579e3 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep.eigs @@ -0,0 +1,20 @@ +# ROW: cal.fn-keep +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn keep +# OPS: SET_LOCAL +# keep runs natively to its return on its 60th call +define keep(q) as: + t is q + return t +k is 0 +r is 0 +loop while k < 60: + r is keep of 2.5 + k is k + 1 +print of r +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.fn-keep.out b/tests/phase2/calib/cal.fn-keep.out new file mode 100644 index 000000000..f972948d1 --- /dev/null +++ b/tests/phase2/calib/cal.fn-keep.out @@ -0,0 +1,2 @@ +2.5 +value diff --git a/tests/phase2/calib/cal.fnstop-cat-plant.eigs b/tests/phase2/calib/cal.fnstop-cat-plant.eigs new file mode 100644 index 000000000..ee7f53551 --- /dev/null +++ b/tests/phase2/calib/cal.fnstop-cat-plant.eigs @@ -0,0 +1,20 @@ +# ROW: cal.fnstop-cat-plant +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop cat ADD +# OPS: ADD +# plant: two floats, so ADD runs natively to the return +define cat(a, b) as: + t is a + b + return t +k is 0 +r is 0 +loop while k < 60: + r is cat of [1.5, 2.5] + k is k + 1 +print of r +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.fnstop-cat-plant.out b/tests/phase2/calib/cal.fnstop-cat-plant.out new file mode 100644 index 000000000..25d453785 --- /dev/null +++ b/tests/phase2/calib/cal.fnstop-cat-plant.out @@ -0,0 +1,2 @@ +4.0 +value diff --git a/tests/phase2/calib/cal.fnstop-cat.eigs b/tests/phase2/calib/cal.fnstop-cat.eigs new file mode 100644 index 000000000..61b9574b0 --- /dev/null +++ b/tests/phase2/calib/cal.fnstop-cat.eigs @@ -0,0 +1,20 @@ +# ROW: cal.fnstop-cat +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop cat ADD +# OPS: ADD +# the ADD guard bails on two strings +define cat(a, b) as: + t is a + b + return t +k is 0 +r is 0 +loop while k < 60: + r is cat of ["x", "y"] + k is k + 1 +print of r +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.fnstop-cat.out b/tests/phase2/calib/cal.fnstop-cat.out new file mode 100644 index 000000000..3fcbc97f9 --- /dev/null +++ b/tests/phase2/calib/cal.fnstop-cat.out @@ -0,0 +1,2 @@ +xy +value diff --git a/tests/phase2/calib/cal.leaf-sq-plant.eigs b/tests/phase2/calib/cal.leaf-sq-plant.eigs new file mode 100644 index 000000000..9b0b138ba --- /dev/null +++ b/tests/phase2/calib/cal.leaf-sq-plant.eigs @@ -0,0 +1,15 @@ +# ROW: cal.leaf-sq-plant +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf sq +# OPS: MUL +# plant: two statements, not leaf-eligible +define sq(x) as: + y is x * x + return y +print of (sq of 1.5) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.leaf-sq-plant.out b/tests/phase2/calib/cal.leaf-sq-plant.out new file mode 100644 index 000000000..24e0d191c --- /dev/null +++ b/tests/phase2/calib/cal.leaf-sq-plant.out @@ -0,0 +1,2 @@ +2.25 +value diff --git a/tests/phase2/calib/cal.leaf-sq.eigs b/tests/phase2/calib/cal.leaf-sq.eigs new file mode 100644 index 000000000..f62879539 --- /dev/null +++ b/tests/phase2/calib/cal.leaf-sq.eigs @@ -0,0 +1,14 @@ +# ROW: cal.leaf-sq +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf sq +# OPS: MUL +# the leaf computes sq: no entries normally, 1 under trace +define sq(x) as: + return x * x +print of (sq of 1.5) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.leaf-sq.out b/tests/phase2/calib/cal.leaf-sq.out new file mode 100644 index 000000000..24e0d191c --- /dev/null +++ b/tests/phase2/calib/cal.leaf-sq.out @@ -0,0 +1,2 @@ +2.25 +value diff --git a/tests/phase2/calib/cal.leafgen-nth-plant.eigs b/tests/phase2/calib/cal.leafgen-nth-plant.eigs new file mode 100644 index 000000000..3fbdcba52 --- /dev/null +++ b/tests/phase2/calib/cal.leafgen-nth-plant.eigs @@ -0,0 +1,14 @@ +# ROW: cal.leafgen-nth-plant +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen nth +# OPS: INDEX_GET +# plant: a list, which the leaf computes itself +define nth(xs, i) as: + return xs[i] +print of (nth of [[10, 20], 0.0]) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.leafgen-nth-plant.out b/tests/phase2/calib/cal.leafgen-nth-plant.out new file mode 100644 index 000000000..776f504dc --- /dev/null +++ b/tests/phase2/calib/cal.leafgen-nth-plant.out @@ -0,0 +1,2 @@ +10 +value diff --git a/tests/phase2/calib/cal.leafgen-nth.eigs b/tests/phase2/calib/cal.leafgen-nth.eigs new file mode 100644 index 000000000..924109996 --- /dev/null +++ b/tests/phase2/calib/cal.leafgen-nth.eigs @@ -0,0 +1,14 @@ +# ROW: cal.leafgen-nth +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen nth +# OPS: INDEX_GET +# the leaf INDEX_GET bails on a string, the generic call indexes +define nth(xs, i) as: + return xs[i] +print of (nth of ["abc", 0.0]) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.leafgen-nth.out b/tests/phase2/calib/cal.leafgen-nth.out new file mode 100644 index 000000000..89f2f84b9 --- /dev/null +++ b/tests/phase2/calib/cal.leafgen-nth.out @@ -0,0 +1,2 @@ +a +value diff --git a/tests/phase2/calib/cal.osr-fill-plant.eigs b/tests/phase2/calib/cal.osr-fill-plant.eigs new file mode 100644 index 000000000..56edba6db --- /dev/null +++ b/tests/phase2/calib/cal.osr-fill-plant.eigs @@ -0,0 +1,22 @@ +# ROW: cal.osr-fill-plant +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr fill +# OPS: INDEX_SET +# plant: a string concat ahead of the claimed op, so INDEX_SET never runs natively +define fill(big, n) as: + xs is [0, 0, 0, 0] + s is "" + i is 0.0 + loop while true: + s is s + "a" + xs[i] is big + i is i + 1.0 + if i >= n: + return xs +print of (fill of [9007199254740993, 4.0]) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.osr-fill-plant.out b/tests/phase2/calib/cal.osr-fill-plant.out new file mode 100644 index 000000000..bc522ed18 --- /dev/null +++ b/tests/phase2/calib/cal.osr-fill-plant.out @@ -0,0 +1,2 @@ +[9007199254740993, 9007199254740993, 9007199254740993, 9007199254740993] +value diff --git a/tests/phase2/calib/cal.osr-fill.eigs b/tests/phase2/calib/cal.osr-fill.eigs new file mode 100644 index 000000000..4435ed2a1 --- /dev/null +++ b/tests/phase2/calib/cal.osr-fill.eigs @@ -0,0 +1,20 @@ +# ROW: cal.osr-fill +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr fill +# OPS: INDEX_SET +# the OSR thunk runs the body natively to the return sentinel +define fill(big, n) as: + xs is [0, 0, 0, 0] + i is 0.0 + loop while true: + xs[i] is big + i is i + 1.0 + if i >= n: + return xs +print of (fill of [9007199254740993, 4.0]) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.osr-fill.out b/tests/phase2/calib/cal.osr-fill.out new file mode 100644 index 000000000..bc522ed18 --- /dev/null +++ b/tests/phase2/calib/cal.osr-fill.out @@ -0,0 +1,2 @@ +[9007199254740993, 9007199254740993, 9007199254740993, 9007199254740993] +value diff --git a/tests/phase2/calib/cal.osrstop-g-plant.eigs b/tests/phase2/calib/cal.osrstop-g-plant.eigs new file mode 100644 index 000000000..c87994621 --- /dev/null +++ b/tests/phase2/calib/cal.osrstop-g-plant.eigs @@ -0,0 +1,20 @@ +# ROW: cal.osrstop-g-plant +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop g ADD +# OPS: ADD +# plant: a float ADD, which runs natively to the sentinel +define g(n) as: + s is 0.5 + i is 0.0 + loop while true: + s is s + 1.5 + i is i + 1.0 + if i >= n: + return s +print of (g of 4.0) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.osrstop-g-plant.out b/tests/phase2/calib/cal.osrstop-g-plant.out new file mode 100644 index 000000000..c70647f99 --- /dev/null +++ b/tests/phase2/calib/cal.osrstop-g-plant.out @@ -0,0 +1,2 @@ +6.5 +value diff --git a/tests/phase2/calib/cal.osrstop-g.eigs b/tests/phase2/calib/cal.osrstop-g.eigs new file mode 100644 index 000000000..85d066fa3 --- /dev/null +++ b/tests/phase2/calib/cal.osrstop-g.eigs @@ -0,0 +1,20 @@ +# ROW: cal.osrstop-g +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop g ADD +# OPS: ADD +# every OSR entry hands back at the string ADD +define g(n) as: + s is "" + i is 0.0 + loop while true: + s is s + "a" + i is i + 1.0 + if i >= n: + return s +print of (g of 4.0) +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.osrstop-g.out b/tests/phase2/calib/cal.osrstop-g.out new file mode 100644 index 000000000..0b351f4d9 --- /dev/null +++ b/tests/phase2/calib/cal.osrstop-g.out @@ -0,0 +1,2 @@ +aaaa +value diff --git a/tests/phase2/calib/cal.verdict.eigs b/tests/phase2/calib/cal.verdict.eigs new file mode 100644 index 000000000..1bf548c06 --- /dev/null +++ b/tests/phase2/calib/cal.verdict.eigs @@ -0,0 +1,11 @@ +# ROW: cal.verdict +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# a straight-line row: #1740 raises `value` on a fractional bitwise operand, the tag truncates +try: + print of (5.5 & 1) +catch e: + print of e.kind diff --git a/tests/phase2/calib/cal.verdict.out b/tests/phase2/calib/cal.verdict.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/calib/cal.verdict.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/const_dedup_bits.c b/tests/phase2/const_dedup_bits.c new file mode 100644 index 000000000..fa7b3578d --- /dev/null +++ b/tests/phase2/const_dedup_bits.c @@ -0,0 +1,28 @@ +/* R1.7-D4: exercise the constant pool itself, not NUM_ZERO/NEG. */ +#include "eigenscript_all.c" +static int checks, failures; +static void check(int ok, const char *label) { + ++checks; + if (!ok) { ++failures; printf("phase2-const-dedup-test: FAIL %s\n", label); } +} +int main(void) { + EigsState *st = eigs_open(); + if (!st) return 1; + EigsChunk *c = chunk_new("signed-zero"); + double pos = 0.0, neg = -0.0; + Value *p = make_float(pos), *n = make_float(neg); + int pi = chunk_add_constant(c, p), ni = chunk_add_constant(c, n); + check(pi != ni, "signed zeros have distinct indices"); + check(c->const_count == 2, "two bit patterns stored"); + check(c->constants[pi]->type == VAL_FLOAT, "positive kind"); + check(c->constants[ni]->type == VAL_FLOAT, "negative kind"); + double ps = VAL_F64_RAW(c->constants[pi]), ns = VAL_F64_RAW(c->constants[ni]); + check(memcmp(&ps, &pos, sizeof pos) == 0, "positive bits exact"); + check(memcmp(&ns, &neg, sizeof neg) == 0, "negative bits exact"); + check(chunk_add_constant(c, p) == pi, "same positive bits deduplicate"); + check(chunk_add_constant(c, n) == ni, "same negative bits deduplicate"); + val_decref(p); val_decref(n); chunk_free(c); + eigs_close(st); + printf("phase2-const-dedup-test: %s checks=%d failures=%d\n", failures ? "FAIL" : "PASS", checks, failures); + return failures ? 1 : 0; +} diff --git a/tests/phase2/controls/ctl.B.type-of.eigs b/tests/phase2/controls/ctl.B.type-of.eigs new file mode 100644 index 000000000..b510344d2 --- /dev/null +++ b/tests/phase2/controls/ctl.B.type-of.eigs @@ -0,0 +1,7 @@ +# ROW: ctl.B.type-of +# ITEMS: B +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [type of 3, type of 3.0, type of -2] diff --git a/tests/phase2/controls/ctl.B.type-of.out b/tests/phase2/controls/ctl.B.type-of.out new file mode 100644 index 000000000..4a404923f --- /dev/null +++ b/tests/phase2/controls/ctl.B.type-of.out @@ -0,0 +1 @@ +["num", "num", "num"] diff --git a/tests/phase2/controls/ctl.N1.fn-int-not.eigs b/tests/phase2/controls/ctl.N1.fn-int-not.eigs new file mode 100644 index 000000000..2fad284c1 --- /dev/null +++ b/tests/phase2/controls/ctl.N1.fn-int-not.eigs @@ -0,0 +1,15 @@ +# ROW: ctl.N1.fn-int-not +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop neg NOT +# OPS: NOT +define neg(a) as: + r is not a + return r +r is null +i is 0.0 +loop while i < 60.0: + r is neg of 0 + i is i + 1.0 +print of [neg of 0.0, neg of 5, neg of 0] diff --git a/tests/phase2/controls/ctl.N1.fn-int-not.out b/tests/phase2/controls/ctl.N1.fn-int-not.out new file mode 100644 index 000000000..c8069fd98 --- /dev/null +++ b/tests/phase2/controls/ctl.N1.fn-int-not.out @@ -0,0 +1 @@ +[true, false, true] diff --git a/tests/phase2/controls/ctl.N1.fn-int-truthiness.eigs b/tests/phase2/controls/ctl.N1.fn-int-truthiness.eigs new file mode 100644 index 000000000..80092fdf1 --- /dev/null +++ b/tests/phase2/controls/ctl.N1.fn-int-truthiness.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N1.fn-int-truthiness +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop truth JUMP_IF_FALSE +# OPS: JUMP_IF_FALSE +define truth(a) as: + if a: + return 1 + return 2 +r is null +i is 0.0 +loop while i < 60.0: + r is truth of 0 + i is i + 1.0 +print of [truth of 0.0, truth of 5, truth of 0] diff --git a/tests/phase2/controls/ctl.N1.fn-int-truthiness.out b/tests/phase2/controls/ctl.N1.fn-int-truthiness.out new file mode 100644 index 000000000..13e72c4e7 --- /dev/null +++ b/tests/phase2/controls/ctl.N1.fn-int-truthiness.out @@ -0,0 +1 @@ +[2, 1, 2] diff --git a/tests/phase2/controls/ctl.N2.float-eq.eigs b/tests/phase2/controls/ctl.N2.float-eq.eigs new file mode 100644 index 000000000..2818a515e --- /dev/null +++ b/tests/phase2/controls/ctl.N2.float-eq.eigs @@ -0,0 +1,7 @@ +# ROW: ctl.N2.float-eq +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [0.5 + 0.25 == 0.75, 1 == 1.0, 3 < 3.5] diff --git a/tests/phase2/controls/ctl.N2.float-eq.out b/tests/phase2/controls/ctl.N2.float-eq.out new file mode 100644 index 000000000..601ef4cf6 --- /dev/null +++ b/tests/phase2/controls/ctl.N2.float-eq.out @@ -0,0 +1 @@ +[true, true, true] diff --git a/tests/phase2/controls/ctl.N2.fn-float-compare.eigs b/tests/phase2/controls/ctl.N2.fn-float-compare.eigs new file mode 100644 index 000000000..02641fbd1 --- /dev/null +++ b/tests/phase2/controls/ctl.N2.fn-float-compare.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N2.fn-float-compare +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn ltf +# OPS: BINARY_LINE LT BINARY_LINE_END +define ltf(a, b) as: + t is a < ( + b) + return t +r is null +i is 0.0 +loop while i < 60.0: + r is ltf of [0.5, 0.75] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N2.fn-float-compare.out b/tests/phase2/controls/ctl.N2.fn-float-compare.out new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/phase2/controls/ctl.N2.fn-float-compare.out @@ -0,0 +1 @@ +true diff --git a/tests/phase2/controls/ctl.N3.leaf-float.eigs b/tests/phase2/controls/ctl.N3.leaf-float.eigs new file mode 100644 index 000000000..49c426800 --- /dev/null +++ b/tests/phase2/controls/ctl.N3.leaf-float.eigs @@ -0,0 +1,9 @@ +# ROW: ctl.N3.leaf-float +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf sqf +# OPS: MUL +define sqf(x) as: + return x * x +print of (sqf of 1.5) diff --git a/tests/phase2/controls/ctl.N3.leaf-float.out b/tests/phase2/controls/ctl.N3.leaf-float.out new file mode 100644 index 000000000..8bd2249ac --- /dev/null +++ b/tests/phase2/controls/ctl.N3.leaf-float.out @@ -0,0 +1 @@ +2.25 diff --git a/tests/phase2/controls/ctl.N3.osr-float.eigs b/tests/phase2/controls/ctl.N3.osr-float.eigs new file mode 100644 index 000000000..3263557d7 --- /dev/null +++ b/tests/phase2/controls/ctl.N3.osr-float.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N3.osr-float +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr totalf +# OPS: BINARY_LINE ADD BINARY_LINE_END CONST NUM_ONE GE JUMP_IF_FALSE SET_LOCAL POP +define totalf(n) as: + acc is 0.5 + i is 0.0 + loop while true: + acc is acc + ( + 1.5) + i is i + 1.0 + if i >= n: + return acc +print of (totalf of 100.0) diff --git a/tests/phase2/controls/ctl.N3.osr-float.out b/tests/phase2/controls/ctl.N3.osr-float.out new file mode 100644 index 000000000..1450a7edc --- /dev/null +++ b/tests/phase2/controls/ctl.N3.osr-float.out @@ -0,0 +1 @@ +150.5 diff --git a/tests/phase2/controls/ctl.N3.small-ints.eigs b/tests/phase2/controls/ctl.N3.small-ints.eigs new file mode 100644 index 000000000..8c8baf766 --- /dev/null +++ b/tests/phase2/controls/ctl.N3.small-ints.eigs @@ -0,0 +1,7 @@ +# ROW: ctl.N3.small-ints +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1 + 2, 10 - 3, 6 * 7, -4] diff --git a/tests/phase2/controls/ctl.N3.small-ints.out b/tests/phase2/controls/ctl.N3.small-ints.out new file mode 100644 index 000000000..4ab2659de --- /dev/null +++ b/tests/phase2/controls/ctl.N3.small-ints.out @@ -0,0 +1 @@ +[3, 7, 42, -4] diff --git a/tests/phase2/controls/ctl.N4.int-mod-zero-raises.eigs b/tests/phase2/controls/ctl.N4.int-mod-zero-raises.eigs new file mode 100644 index 000000000..087071ec1 --- /dev/null +++ b/tests/phase2/controls/ctl.N4.int-mod-zero-raises.eigs @@ -0,0 +1,10 @@ +# ROW: ctl.N4.int-mod-zero-raises +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + x is 7 % 0 +catch e: + print of e.kind diff --git a/tests/phase2/controls/ctl.N4.int-mod-zero-raises.out b/tests/phase2/controls/ctl.N4.int-mod-zero-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/controls/ctl.N4.int-mod-zero-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/controls/ctl.N5.leaf-float-index.eigs b/tests/phase2/controls/ctl.N5.leaf-float-index.eigs new file mode 100644 index 000000000..97f1d83d8 --- /dev/null +++ b/tests/phase2/controls/ctl.N5.leaf-float-index.eigs @@ -0,0 +1,9 @@ +# ROW: ctl.N5.leaf-float-index +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf nth +# OPS: INDEX_GET +define nth(xs, i) as: + return xs[i] +print of (nth of [[10, 20, 30], 1.0]) diff --git a/tests/phase2/controls/ctl.N5.leaf-float-index.out b/tests/phase2/controls/ctl.N5.leaf-float-index.out new file mode 100644 index 000000000..209e3ef4b --- /dev/null +++ b/tests/phase2/controls/ctl.N5.leaf-float-index.out @@ -0,0 +1 @@ +20 diff --git a/tests/phase2/controls/ctl.N6.small-ints.eigs b/tests/phase2/controls/ctl.N6.small-ints.eigs new file mode 100644 index 000000000..16504832e --- /dev/null +++ b/tests/phase2/controls/ctl.N6.small-ints.eigs @@ -0,0 +1,7 @@ +# ROW: ctl.N6.small-ints +# ITEMS: N6 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1, 2, 3, -4, 100000] diff --git a/tests/phase2/controls/ctl.N6.small-ints.out b/tests/phase2/controls/ctl.N6.small-ints.out new file mode 100644 index 000000000..347be4583 --- /dev/null +++ b/tests/phase2/controls/ctl.N6.small-ints.out @@ -0,0 +1 @@ +[1, 2, 3, -4, 100000] diff --git a/tests/phase2/controls/ctl.N8.refuse-band.eigs b/tests/phase2/controls/ctl.N8.refuse-band.eigs new file mode 100644 index 000000000..8cc796963 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-band.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-band +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f BAND +# OPS: BAND +define f(a, b) as: + warm is 0.5 + t is a & b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-band.out b/tests/phase2/controls/ctl.N8.refuse-band.out new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-band.out @@ -0,0 +1 @@ +2 diff --git a/tests/phase2/controls/ctl.N8.refuse-bnot.eigs b/tests/phase2/controls/ctl.N8.refuse-bnot.eigs new file mode 100644 index 000000000..801812693 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bnot.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-bnot +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f BNOT +# OPS: BNOT +define f(a, b) as: + warm is 0.5 + t is ~a + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-bnot.out b/tests/phase2/controls/ctl.N8.refuse-bnot.out new file mode 100644 index 000000000..17bdab103 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bnot.out @@ -0,0 +1 @@ +-7 diff --git a/tests/phase2/controls/ctl.N8.refuse-bor.eigs b/tests/phase2/controls/ctl.N8.refuse-bor.eigs new file mode 100644 index 000000000..a2ca16187 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bor.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-bor +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f BOR +# OPS: BOR +define f(a, b) as: + warm is 0.5 + t is a | b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-bor.out b/tests/phase2/controls/ctl.N8.refuse-bor.out new file mode 100644 index 000000000..7f8f011eb --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bor.out @@ -0,0 +1 @@ +7 diff --git a/tests/phase2/controls/ctl.N8.refuse-bxor.eigs b/tests/phase2/controls/ctl.N8.refuse-bxor.eigs new file mode 100644 index 000000000..8a970ca5c --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bxor.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-bxor +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f BXOR +# OPS: BXOR +define f(a, b) as: + warm is 0.5 + t is a ^ b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-bxor.out b/tests/phase2/controls/ctl.N8.refuse-bxor.out new file mode 100644 index 000000000..7ed6ff82d --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-bxor.out @@ -0,0 +1 @@ +5 diff --git a/tests/phase2/controls/ctl.N8.refuse-mod.eigs b/tests/phase2/controls/ctl.N8.refuse-mod.eigs new file mode 100644 index 000000000..4168193fc --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-mod.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-mod +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f MOD +# OPS: MOD +define f(a, b) as: + warm is 0.5 + t is a % b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of (r == 0) diff --git a/tests/phase2/controls/ctl.N8.refuse-mod.out b/tests/phase2/controls/ctl.N8.refuse-mod.out new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-mod.out @@ -0,0 +1 @@ +true diff --git a/tests/phase2/controls/ctl.N8.refuse-shl.eigs b/tests/phase2/controls/ctl.N8.refuse-shl.eigs new file mode 100644 index 000000000..32586e9fe --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-shl.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-shl +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f SHL +# OPS: SHL +define f(a, b) as: + warm is 0.5 + t is a << b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-shl.out b/tests/phase2/controls/ctl.N8.refuse-shl.out new file mode 100644 index 000000000..21e72e8ac --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-shl.out @@ -0,0 +1 @@ +48 diff --git a/tests/phase2/controls/ctl.N8.refuse-shr.eigs b/tests/phase2/controls/ctl.N8.refuse-shr.eigs new file mode 100644 index 000000000..e404f0f0b --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-shr.eigs @@ -0,0 +1,16 @@ +# ROW: ctl.N8.refuse-shr +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f SHR +# OPS: SHR +define f(a, b) as: + warm is 0.5 + t is a >> b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is f of [6.0, 3.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/controls/ctl.N8.refuse-shr.out b/tests/phase2/controls/ctl.N8.refuse-shr.out new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/tests/phase2/controls/ctl.N8.refuse-shr.out @@ -0,0 +1 @@ +0 diff --git a/tests/phase2/corpus_exclude.txt b/tests/phase2/corpus_exclude.txt new file mode 100644 index 000000000..017d4f587 --- /dev/null +++ b/tests/phase2/corpus_exclude.txt @@ -0,0 +1,5 @@ +# directory owner reason; sole harness-ownership list for corpus walkers. +# phase2_corpus_exclude.py filters each broad population before a walker narrows +# it (JIT/OBS, replay, phase2 corpus diff, valgrind --full, road fixtures). +# Every declaration must remove files; unused/empty directories fail. +tests/phase2/builtins/ tools/phase2_builtins.sh Owned by tools/phase2_builtins.sh: its probes require the bar fixtures, observation hooks, matched profiles and stdin; that gate checks every row. diff --git a/tests/phase2/corpus_nonzero.txt b/tests/phase2/corpus_nonzero.txt new file mode 100644 index 000000000..ee0b96b28 --- /dev/null +++ b/tests/phase2/corpus_nonzero.txt @@ -0,0 +1,59 @@ +# tests/phase2/corpus_nonzero.txt -- expected standalone nonzero corpus exits: +# the baseline code for unchanged files; for changed fixtures the exact +# candidate code when the baseline cannot meet it (still reported as DIFF). +# Every code has a reason (#1758; tools/phase2_corpus_diff.sh). One row per file: +# +# An exit nobody declared is UNDECLARED-NONZERO, and a row whose file now +# exits otherwise is STALE: both fail the differential. A file with run +# conditions (tests/suite_program_env.txt) must complete and cannot be listed. +tests/_runaway_guard_selftest.eigs 124 the suite runner's runaway-guard self-test fixture: it never terminates, by design +tests/bool_fuzz_gen.eigs 1 a generator: tests/test_bool_fuzz.sh runs it with the binary's --api JSON as its argument +tests/embed_roads/eigs_modules/entrywrapper/entrywrapper.eigs 1 a fixture of the embed-roads host (tools/embed_roads.py), which registers host_scope_clean +tests/embed_roads/entry.eigs 1 a fixture of the embed-roads host (tools/embed_roads.py), which registers host_scope_clean +tests/embed_roads/helper/functions.eigs 1 a fixture of the embed-roads host (tools/embed_roads.py), which registers host_scope_clean +tests/embed_roads/helper/peer.eigs 1 a fixture of the embed-roads host (tools/embed_roads.py), which registers host_scope_clean +tests/embed_roads/import_entry.eigs 1 a fixture of the embed-roads host (tools/embed_roads.py), which registers host_scope_clean +tests/gfx_asan_corpus/06_rejected.eigs 1 a deliberate rejected gfx call: tests/test_asan_gfx.sh expects the raise +tests/line_after_callback_halt.eigs 1 a deliberate error: the suite's line-after-return rows check its trace (#1434) +tests/line_after_return_fn.eigs 1 a deliberate error: the suite's line-after-return rows check its trace +tests/line_after_return_module.eigs 1 a deliberate error: the suite's line-after-return rows check its trace +tests/line_after_return_null.eigs 1 a deliberate error: the suite's line-after-return rows check its trace +tests/loader_mt_modules/lm_cyc_a.eigs 1 a circular-load fixture of tests/test_loader_mt.sh: loaded alone, it reports the cycle +tests/loader_mt_modules/lm_cyc_b.eigs 1 a circular-load fixture of tests/test_loader_mt.sh: loaded alone, it reports the cycle +tests/roads/assets/eval_loaded/caller.eigs 1 a fixture of tools/road_diff.py: it runs inside a generated project tree +tests/roads/assets/hardlinks/b/child.eigs 1 a fixture of tools/road_diff.py: it runs inside a generated project tree +tests/roads/assets/hardlinks/b/entry.eigs 1 a fixture of tools/road_diff.py: it runs inside a generated project tree +tests/roads/assets/no_project/cwd_only.eigs 1 a fixture of tools/road_diff.py: it throws if a load falls back to the cwd +tests/roads/assets/no_project/parent.eigs 1 a fixture of tools/road_diff.py: it throws if a load falls back to the parent +tests/roads/assets/observer/assets/observer/target.eigs 1 a fixture of tools/road_diff.py: it runs inside a generated project tree +tests/roads/assets/observer/entry.eigs 1 a fixture of tools/road_diff.py: it runs inside a generated project tree +tests/task_deadlock.eigs 1 a deliberate deadlock: the suite's check_task_exit expects exit 1 naming it (#483, #509) +tests/task_deadlock_worker_try.eigs 1 a deliberate deadlock inside a worker's try: the suite's check_task_exit expects exit 1 +tests/task_exit_detached_death.eigs 1 a deliberate worker death: the suite's check_task_exit expects exit 1 (#530) +tests/task_exit_unjoined_death.eigs 1 a deliberate worker death: the suite's check_task_exit expects exit 1 +tests/test_assert_fail.eigs 1 a deliberate assert failure: the suite requires it to fail +tests/test_db.eigs 1 needs the server-db build (the release build has no database capability) +tests/test_http.eigs 1 needs the server build (the release build has no HTTP capability) +tests/test_inflate.eigs 1 needs the zlib build (make zlib) +tests/test_net.eigs 1 needs the server build (the release build has no network capability) +tests/tsan_worker_exit.eigs 5 a deliberate exit 5 from a worker: tests/test_tsan.sh expects rc 5 +tests/ui_native_input.eigs 1 a fixture of tests/ui_native_input.py, which sends it native window events; alone it reaches its deadline +tests/task_exit_detach_dead.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detach_dead_nested.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detach_dead_tape.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detach_dead_worker.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detached_drain.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detached_join.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detached_self.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detached_tape.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/task_exit_detached_try.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1736) +tests/thread_exit_join_uncaught.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1745) +tests/thread_exit_unjoined_death.eigs 1 deliberate uncaught worker/task error: the suite expects exit 1 (#1745) +tests/task_exit_worker_unjoined_task.eigs 1 deliberate unjoined worker-task error: tests/sections/104c-worker-task-exit-1810.sh requires process exit 1 (#1810) + +# Sync10: exactly the five intentional new-main child exits; never exclusions. +tests/operator_line_fn.eigs 1 deliberate uncaught function-operator diagnostic introduced by main f200a54d (#1425/#2035); the program intentionally raises at its second + on line7 +tests/operator_line_module.eigs 1 deliberate uncaught module-operator diagnostic from main f200a54d (#1425/#2035); tests/sections/15l-binary-operator-line-1425.sh checks nonzero plus line8 header/excerpt/caret/traceback +tests/task_exit_other_try.eigs 1 deliberate unjoined task error from main 8061e5a0 (#1808/#2098); tests/run_all_tests.sh check_task_exit requires1 and at die while another task holds a try +tests/task_exit_cooperative_joinable.eigs 3 deliberate task exit3 from main 8061e5a0 (#1808/#2098); tests/sections/104b-task-death-fail-fast-1736.sh requires3 and forbids resumed/caught/end/synthesized-error markers +tests/task_exit_cooperative_detached.eigs 7 deliberate detached task exit7 from main 8061e5a0 (#1808/#2098); tests/sections/104b-task-death-fail-fast-1736.sh requires7 and forbids resumed/synthesized-error markers diff --git a/tests/phase2/cost_expect.txt b/tests/phase2/cost_expect.txt new file mode 100644 index 000000000..30367ba38 --- /dev/null +++ b/tests/phase2/cost_expect.txt @@ -0,0 +1,30 @@ +bench/alloc_heavy.eigs jit 50.4 48.7 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +64.3% (range-int control, measured at R1.3, against +64.0%) until R5; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=8199903452, R1.4 Ir=8415252793, measured main delta=+68.4121%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 range and hot len int results recover part of the existing generic mixed-operand/Value API cost; banked R1.6a Ir=8428313595, current Ir=7486166749, main Ir=4996461973, main delta=+49.82935504%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 7489398418->7502928686 (+13530268); fixed main Ir 4996664388->4996664388; K1 ratio component +0.270786007 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 50.1->50.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=4991202084, candidate Ir=7595628068, max 50.4->52.4; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=5102010183, candidate Ir=7573041665, raw delta=48.43250784237017662573332422%, max 52.4->48.7, recover_round=R5 retained +bench/alloc_heavy.eigs off 29.3 27.3 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=8229622729, R1.4 Ir=8442823382, measured main delta=+44.6791%, bound=ceil(delta+0.2,0.1); R1.6a remeasurement of the inherited main-sync short-string layout component (4ac31e67 to 1fd4422c): controls attribute +8000006 Ir to __strlen_sse2 with unchanged hot VM/allocation counts; untouched 1fd control Ir=8457703595 versus main Ir=5835080986 (+44.945779%, already above old max44.9), same-alias candidate versus 1fd +0.00188828%; current native-tool measurement main Ir=5835080986, candidate Ir=8457872415, delta=+44.948672%; bound=ceil(delta+0.2,0.1), retaining R5 recovery of the existing generic interpreter int cost, no new reader allowance; R1.6b measured component: N3 range and hot len int results recover part of the existing generic mixed-operand/Value API cost; banked R1.6a Ir=8457872415, current Ir=7514744462, main Ir=5835072842, main delta=+28.78578666%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 7517271405->7530651672 (+13380267); fixed main Ir 5835396506->5835396506; K1 ratio component +0.229294907 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 29.0->29.3; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=5829923183, candidate Ir=7625376672, max 29.3->31; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=5982470363, candidate Ir=7602789407, raw delta=27.08444748880404942509198687%, max 31->27.3, recover_round=R5 retained +bench/conservative_observed_loop.eigs jit 24.9 22.7 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +25.9% (range-int control, measured at R1.3, against +2.1%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=80746036, current Ir=99538839, main Ir=79049516, main delta=+25.91960588%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +685683 Ir (0.867 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 26.2 -> 27 by ceil(delta+0.2, 0.1); CI bench gate (bench/check_regression.sh, same jit row) measured +27.047% (Ir 76,743,802 -> 97,500,699 on the runner, against a local +26.72%): re-declared 27 -> 27.2: the only bound both gates accept (CI 27.047 <= 27.2; local 26.72 within the 0.5 STALE band). The two environments differ by 0.33 pp, which ceil(delta+0.2, 0.1) cannot cover on both sides; R1.6z measured against pre-round2ba66cc8: old Ir=100175815, tip Ir=99685893, net -489922 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -9999 Ir (vm_run_ex -9999 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +99 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +107 Ir; every function delta preserved); main dde78c5c Ir=79048062, tip Ir=99675601, local delta=26.09493323%, max 27.2->26.5; Lesson10/#1939 prior CI bench run40097626469 job120883125106: main Ir=76743802, pre-R1.6z Ir=97500699, delta=27.04700114%; runtime matches control2ba66cc8 (21c56bae changes inventory only). Applying measured R1.6z component -489922 Ir projects current CI delta=26.40861473% (inference, no fresh remote run). Formula max 26.3 would fail that projection; choose 26.5, the only common one-decimal bound, within local STALE band [26.09493323,26.59493323]. All rows remain flagged within3% of allowed Ir; projection assumes the local absolute Ir component transfers to the prior Ubuntu/build.sh compiler configuration; source identity does not prove this layout assumption; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 99678078->103173346 (+3495268); fixed main Ir 79050115->79050115; K1 ratio component +4.421584965 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 26.5->30.8; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=79050868, candidate Ir=98499065, max 30.8->24.9; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=76741663, candidate Ir=95853668, max 30.8->25.2; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=79577824, candidate Ir=97438803, raw delta=22.44466875595894655274816260%, max 25.2->22.7, recover_round=R5 retained +bench/conservative_observed_loop.eigs off 20.4 18.9 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +21.7% (range-int control, measured at R1.3, against +1.0%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=95920891, current Ir=115593684, main Ir=94884432, main delta=+21.82576379%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +685036 Ir (0.722 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 22.1 -> 22.7 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=116230547, tip Ir=115630461, net -600086 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -119999 Ir (vm_run_ex -119999 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual -65 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions -57 Ir; every function delta preserved); main dde78c5c Ir=94883464, tip Ir=115620966, local delta=21.85575982%, max 22.7->22.1; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 115622893->118623160 (+3000267); fixed main Ir 94885557->94885557; K1 ratio component +3.161984916 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 22.1->25.3; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=94886310, candidate Ir=114004570, max 25.3->20.4; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=92574368, candidate Ir=111576397, max 25.3->20.8; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=95410105, candidate Ir=113161109, raw delta=18.60495175013170774730831708%, max 20.8->18.9, recover_round=R5 retained +bench/conservative_unobserved_loop.eigs jit 31.2 27.9 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +32.6% (range-int control, measured at R1.3, against +2.7%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=64506354, current Ir=83299237, main Ir=62814824, main delta=+32.61079423%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +685660 Ir (1.092 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 32.9 -> 33.9 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=83937756, tip Ir=83452788, net -484968 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -4998 Ir (vm_run_ex -4998 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +52 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +60 Ir; every function delta preserved); main dde78c5c Ir=62814983, tip Ir=83442541, local delta=32.83859521%, max 33.9->33.1; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 83446735->86937002 (+3490267); fixed main Ir 62818687->62818687; K1 ratio component +5.556096707 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 33.1->38.6; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=62819429, candidate Ir=82267672, max 38.6->31.2; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=62970735, candidate Ir=82077560, max 38.6->30.6; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=64666915, candidate Ir=82523154, raw delta=27.61263468343897339157125402%, max 30.6->27.9, recover_round=R5 retained +bench/conservative_unobserved_loop.eigs off 24.5 22.2 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +26.1% (range-int control, measured at R1.3, against +1.1%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=79846282, current Ir=99519057, main Ir=78869757, main delta=+26.18151847%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +685769 Ir (0.869 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 26.4 -> 27.2 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=100157519, tip Ir=99617487, net -540032 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -59998 Ir (vm_run_ex -59998 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual -12 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions -4 Ir; every function delta preserved); main dde78c5c Ir=78870274, tip Ir=99607935, local delta=26.29338019%, max 27.2->26.5; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 99611590->102551856 (+2940266); fixed main Ir 78874258->78874258; K1 ratio component +3.727789110 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 26.5->30.3; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=78874968, candidate Ir=97993197, max 30.3->24.5; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=79023519, candidate Ir=97965340, max 30.3->24.2; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=80719305, candidate Ir=98410555, raw delta=21.91699990479353111377755297%, max 24.2->22.2, recover_round=R5 retained +bench/dict_ops.eigs jit 21.7 19.9 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +22.1% (range-int control, measured at R1.3, against +0.7%) until R5; R1.6b measured component: N3 range int production activates generic Value API/dictionary int paths, as the pre-registered range-int control predicts; banked R1.6a Ir=109524332, current Ir=132542922, main Ir=108597020, main delta=+22.05023858%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 132405409->133771370 (+1365961); fixed main Ir 108584551->108584551; K1 ratio component +1.257969930 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 22.3->23.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=108585334, candidate Ir=131917065, max 23.4->21.7; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=109275477, candidate Ir=132557456, max 23.4->21.6; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=111006041, candidate Ir=132809557, raw delta=19.64173823657038629095870557%, max 21.6->19.9, recover_round=R5 retained +bench/dict_ops.eigs off 20.9 19.1 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +21.4% (range-int control, measured at R1.3, against +0.7%) until R5; R1.6b measured component: N3 range int production activates generic Value API/dictionary int paths, as the pre-registered range-int control predicts; banked R1.6a Ir=113283097, current Ir=136386349, main Ir=112435644, main delta=+21.30170126%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 136247701->137426766 (+1179065); fixed main Ir 112402343->112402343; K1 ratio component +1.048968348 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 21.6->22.5; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=112403114, candidate Ir=135592123, max 22.5->20.9; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=113108434, candidate Ir=136310606, max 22.5->20.8; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=114841465, candidate Ir=136478035, raw delta=18.84038139011897836726482025%, max 20.8->19.1, recover_round=R5 retained +bench/observed_loop.eigs jit 44.4 39.2 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +47.6% (range-int control, measured at R1.3, against +4.0%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=44850891, current Ir=63643989, main Ir=43104945, main delta=+47.64892752%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +200959 Ir (0.466 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 47.9 -> 48.2 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=63796565, tip Ir=63311655, net -484910 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -5001 Ir (vm_run_ex -5001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +113 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +121 Ir; every function delta preserved); main dde78c5c Ir=43103521, tip Ir=63301644, local delta=46.85956630%, max 48.2->47.1; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 63303747->66794179 (+3490432); fixed main Ir 43105257->43105257; K1 ratio component +8.097462451 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 47.1->55.2; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=43106027, candidate Ir=62124925, max 55.2->44.4; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=43220458, candidate Ir=61891884, max 55.2->43.5; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=44721533, candidate Ir=62138219, raw delta=38.94474279314172884010930484%, max 43.5->39.2, recover_round=R5 retained +bench/observed_loop.eigs off 30.5 27.5 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +33.4% (range-int control, measured at R1.3, against +1.4%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=62500796, current Ir=82173713, main Ir=61524518, main delta=+33.56254656%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6z measured against pre-round2ba66cc8: old Ir=82326014, tip Ir=81785998, net -540016 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -60001 Ir (vm_run_ex -60001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +7 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +15 Ir; every function delta preserved); main dde78c5c Ir=61523651, tip Ir=81776713, local delta=32.91914844%, max 33.8->33.2; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 81778340->84718771 (+2940431); fixed main Ir 61525523->61525523; K1 ratio component +4.779205209 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 33.2->37.9; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=61526288, candidate Ir=80160122, max 37.9->30.5; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=61638062, candidate Ir=80089507, max 37.9->30.2; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=63138766, candidate Ir=80335478, raw delta=27.23637646006575421508871428%, max 30.2->27.5, recover_round=R5 retained +bench/scalar_loop.eigs jit 85.8 81.2 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone removes 57.8% of this excess (range-int control, measured at R1.3: +88.6% against +210.1%), R5 the rest; merge of 94657581 component (#1171): env_store_slot bumps the assign-count inside its hold, so the branch's interpreted int stores pay +424,992 Ir there against -225,073 in vm_run_ex (CAND +200,055 Ir, +0.20%) while main's JIT-compiled loop moves BASE -9,041; measured +210.10% at R1.3, +210.81% after the merge; R1.4 remeasurement narrows this stale bound on the existing generic int VM component; fresh6382b6a5 control Ir=99738384, R1.4 Ir=99697847, measured main delta=+210.5725%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 int range recovers the existing generic mixed-operand cost, as the pre-registered range-int control predicts; banked R1.6a Ir=99747054, current Ir=60503359, main Ir=32101836, main delta=+88.47320446%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +200647 Ir (0.625 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 88.7 -> 89.3 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=60675645, tip Ir=60315786, net -359859 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -40001 Ir (vm_run_ex -40001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -320022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +164 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +172 Ir; every function delta preserved); main dde78c5c Ir=32095387, tip Ir=60305770, local delta=87.89544429%, max 89.3->88.1; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 60307669->62618090 (+2310421); fixed main Ir 32097237->32097237; K1 ratio component +7.198192791 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 88.1->95.3; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=32097962, candidate Ir=59543786, max 95.3->85.8; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=32212214, candidate Ir=60705731, max 95.3->88.7; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=33211097, candidate Ir=60110085, raw delta=80.99397620018393249702049890%, max 88.7->81.2, recover_round=R5 retained +bench/scalar_loop.eigs off 40.6 38.6 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone removes 66.0% of this excess (range-int control, measured at R1.3: +43.1% against +126.8%), R5 the rest; merge of 94657581 component (#1171): env_store_slot +599,992 Ir (the assign-count bump moved inside its hold) against vm_run_ex -400,073 on the branch (CAND +200,037, +0.19%), while on main the same move saves (vm_run_ex -480,053, env_store_slot +399,995: BASE -78,939, -0.17%); measured +126.81% at R1.3, +127.64% after the merge; R1.4 remeasurement narrows this stale bound on the existing generic int VM component; fresh6382b6a5 control Ir=104109956, R1.4 Ir=104069767, measured main delta=+127.3221%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 int range recovers the existing generic mixed-operand cost, as the pre-registered range-int control predicts; banked R1.6a Ir=104118664, current Ir=65469785, main Ir=45780746, main delta=+43.00724807%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +200664 Ir (0.439 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 43.3 -> 43.7 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=65641792, tip Ir=65281914, net -359878 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -40001 Ir (vm_run_ex -40001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -320022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +145 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +153 Ir; every function delta preserved); main dde78c5c Ir=45739804, tip Ir=65272504, local delta=42.70394338%, max 43.7->43; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 65273973->67234393 (+1960420); fixed main Ir 45741868->45741868; K1 ratio component +4.285832839 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 43->47.2; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=45742580, candidate Ir=64195721, max 47.2->40.6; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=45854142, candidate Ir=65425111, max 47.2->42.9; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=46852629, candidate Ir=64829058, raw delta=38.36802626379834523266559919%, max 42.9->38.6, recover_round=R5 retained +bench/string_build.eigs jit 28.9 27.4 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +25.2% (range-int control, measured at R1.3, against +9.9%) until R5; R1.4 float MOD component: floor correction/result construction replaced truncating NUM_REUSE, adding make_num/freelist/release work; R5 must restore generic float result reuse in the off tier as well as native MOD; fresh6382b6a5 control Ir=82669260, R1.4 Ir=83813773, measured main delta=+11.3960%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 range-produced ints activate integer MOD and its non-pooled result allocation (the prior float-MOD component above is historical); banked R1.6a Ir=83852414, current Ir=97041191, main Ir=75239767, main delta=+28.97593237%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +199849 Ir (0.266 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 29.2 -> 29.5 by ceil(delta+0.2, 0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 97154887->97990134 (+835247); fixed main Ir 75235734->75235734; K1 ratio component +1.110173259 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 29.5->30.5; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=75236482, candidate Ir=96825844, max 30.5->28.9; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=75278705, candidate Ir=96513115, max 30.5->28.5; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=76061447, candidate Ir=96680798, raw delta=27.10880717270603595011806704%, max 28.5->27.4, recover_round=R5 retained +bench/string_build.eigs off 27 25.3 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +23.5% (range-int control, measured at R1.3, against +8.5%) until R5; R1.4 float MOD component: floor correction/result construction replaced truncating NUM_REUSE, adding make_num/freelist/release work; R5 must restore generic float result reuse in the off tier as well as native MOD; fresh6382b6a5 control Ir=85095023, R1.4 Ir=86239916, measured main delta=+9.9831%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 range-produced ints activate integer MOD and its non-pooled result allocation (the prior float-MOD component above is historical); banked R1.6a Ir=86278206, current Ir=99686896, main Ir=78411840, main delta=+27.13245347%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 99800439->100535685 (+735246); fixed main Ir 78398542->78398542; K1 ratio component +0.937831216 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 27.4->28.5; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=78399254, candidate Ir=99382012, max 28.5->27; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=78438785, candidate Ir=99106726, max 28.5->26.6; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=79381093, candidate Ir=99273992, raw delta=25.05999633943059967692810680%, max 26.6->25.3, recover_round=R5 retained +bench/unobserved_armed_loop.eigs jit 67.7 69.8 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 917950531->921550586 (+3600055); fixed main Ir 550569668->550569668; K1 ratio component +0.653878194 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 66.9->67.6; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=550570451, candidate Ir=922151363, max 67.6->67.7; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=550721987, candidate Ir=933717134, max 67.6->69.8 +bench/unobserved_armed_loop.eigs off 9.8 10.4 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; merge of 94657581 component (#1171): env_store_slot +3,000,000 Ir on both trees (600,000 stores; the assign-count bump moved inside its hold), which saves 2,400,031 in the branch's vm_run_ex (CAND +600,065, +0.06%) but 4,800,031 in main's (BASE -1,798,626, -0.21%); measured +8.85% at R1.3, +9.15% after the merge; R1.4 remeasurement narrows this stale bound on the existing generic int VM component; fresh6382b6a5 control Ir=930827459, R1.4 Ir=928427649, measured main delta=+8.7891%, bound=ceil(delta+0.2,0.1); R1.5 N8 shared VM code-generation component (this workload has no bitwise ops): vm_run_ex +1799999 Ir; R5 interpreter int/slot fast paths must recover this off-tier cost; fresh2d6587ee control Ir=929028193, R1.5 Ir=930826815, measured main delta=+9.0703%, bound=ceil(delta+0.2,0.1); main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 853419139->853412688 (-0.001%), candidate Ir 930826815->928420991 (-0.258%): the candidate got cheaper; main delta=+8.7892%; bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=929245584, tip Ir=930445331, net +1199747 Ir; shared VM dispatch/helper code-generation effect of bridge retirement +1200001 Ir (vm_run_ex +1200001 Ir); other-function residual -254 Ir (other runtime functions -13 Ir; external string/number-formatting/startup functions -241 Ir; every function delta preserved); main dde78c5c Ir=852818898, tip Ir=930435864, local delta=9.10122491%, max 9.0->9.4; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 930439142->934039197 (+3600055); fixed main Ir 852822637->852822637; K1 ratio component +0.422134081 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 9.4->9.8; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=852971454, candidate Ir=947392850, max 9.8->11.3; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=887789994, candidate Ir=978020905, raw delta=10.16354223519216640326315730%, max 11.3->10.4, recover_round=R5 retained +bench/unobserved_loop.eigs jit 44.4 39.2 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +47.6% (range-int control, measured at R1.3, against +4.0%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=44857870, current Ir=63651013, main Ir=43111802, main delta=+47.64173625%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +200945 Ir (0.466 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 47.9 -> 48.2 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=63804716, tip Ir=63319820, net -484896 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -5001 Ir (vm_run_ex -5001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +127 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +135 Ir; every function delta preserved); main dde78c5c Ir=43111629, tip Ir=63309643, local delta=46.85050059%, max 48.2->47.1; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 63313486->66803918 (+3490432); fixed main Ir 43115124->43115124; K1 ratio component +8.095609327 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 47.1->55.2; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=43115894, candidate Ir=62134618, max 55.2->44.4; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=43230122, candidate Ir=61901471, max 55.2->43.4; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=44731384, candidate Ir=62148492, raw delta=38.93710956942445599268737135%, max 43.4->39.2, recover_round=R5 retained +bench/unobserved_loop.eigs off 30.5 27.5 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +33.4% (range-int control, measured at R1.3, against +1.4%) until R5; R1.6b measured component: N3 range int production activates generic int allocation/observer paths, as the pre-registered range-int control predicts; banked R1.6a Ir=62507730, current Ir=82180474, main Ir=61531677, main delta=+33.55799485%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6z measured against pre-round2ba66cc8: old Ir=82334123, tip Ir=81794226, net -539897 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -60001 Ir (vm_run_ex -60001 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -480022 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); other-function residual +126 Ir (other runtime functions -8 Ir; external string/number-formatting/startup functions +134 Ir; every function delta preserved); main dde78c5c Ir=61531733, tip Ir=81784783, local delta=32.91480511%, max 33.8->33.2; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 81788209->84728640 (+2940431); fixed main Ir 61535450->61535450; K1 ratio component +4.778434220 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 33.2->37.9; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=61536207, candidate Ir=80169985, max 37.9->30.5; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=61647745, candidate Ir=80099108, max 37.9->30.2; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=63148608, candidate Ir=80345742, raw delta=27.23279981088419241165220934%, max 30.2->27.5, recover_round=R5 retained +tests/bench_buf_mix.eigs jit 322.7 317 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=2997588303, R1.4 Ir=3102110794, measured main delta=+311.4903%, bound=ceil(delta+0.2,0.1); R1.6d measured component: default interp benchmark ends with buf_peak scanning 441000 cells; buffer_read_num -> buffer_read_f64 adds finite-data validation, measured peak+reader Ir +7938000 (18/cell); fresh4e77a9de control Ir=3103006291, R1.6d Ir=3110933042, net candidate increase +7926751 Ir (residual -11249 Ir in startup/argument checks); main88a1c911 Ir=753849828, main delta=+312.67277997%, bound=ceil(delta+0.2,0.1); R5 retains int VM/Value API recovery with required finite refusal preserved; R1.6z measured against pre-round2ba66cc8: old Ir=3111202023, tip Ir=3178233059, net +67031036 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +48951663 Ir (eigs_float_arg +18524210 Ir, eigs_number_arg +38810652 Ir, eigs_num_arg_slow -8383199 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +15876000 Ir (buffer_read_f64 +21168000 Ir, jit_helper_index_get -5292000 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +2203008 Ir (vm_run_ex +2203008 Ir); constructor alias retirement -2 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +362 Ir (other runtime functions -27 Ir; external string/number-formatting/startup functions +389 Ir; every function delta preserved); main dde78c5c Ir=753857361, tip Ir=3178222580, local delta=321.59468680%, max 312.9->321.8; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 3178269058->3183563019 (+5293961); fixed main Ir 753876384->753876384; K1 ratio component +0.702231972 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 321.8->322.5; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=753877190, candidate Ir=3184444532, max 322.5->322.7; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=755247857, candidate Ir=3192002387, max 322.5->322.9; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=780748607, candidate Ir=3253616787, raw delta=316.7303992384836877461121106%, max 322.9->317, recover_round=R5 retained +tests/bench_buf_mix.eigs off 103.3 97.5 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=3014239251, R1.4 Ir=3118762031, measured main delta=+96.9936%, bound=ceil(delta+0.2,0.1); main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 1583179368->1576132391 (-0.445%), candidate Ir 3118762031->3118770881 (+0.000%): main sped up on this bench and the merged candidate did not share it; main delta=+97.8749%; bound=ceil(delta+0.2,0.1); R1.6d measured component: default interp benchmark ends with buf_peak scanning 441000 cells; buffer_read_num -> buffer_read_f64 adds finite-data validation, measured peak+reader Ir +7938000 (18/cell); fresh4e77a9de control Ir=3119656760, R1.6d Ir=3127584374, net candidate increase +7927614 Ir (residual -10386 Ir in startup/argument checks); main88a1c911 Ir=1576130830, main delta=+98.43431233%, bound=ceil(delta+0.2,0.1); R5 retains int VM/Value API recovery with required finite refusal preserved; R1.6z measured against pre-round2ba66cc8: old Ir=3127852086, tip Ir=3194883016, net +67030930 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +48951663 Ir (eigs_float_arg +18524210 Ir, eigs_number_arg +38810652 Ir, eigs_num_arg_slow -8383199 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +15876000 Ir (buffer_read_f64 +21168000 Ir, jit_helper_index_get -5292000 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +2203008 Ir (vm_run_ex +2203008 Ir); constructor alias retirement -2 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +256 Ir (other runtime functions -27 Ir; external string/number-formatting/startup functions +283 Ir; every function delta preserved); main dde78c5c Ir=1576138511, tip Ir=3194873687, local delta=102.70259655%, max 98.7->103; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 3194919701->3200213662 (+5293961); fixed main Ir 1576157659->1576157659; K1 ratio component +0.335877631 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 103->103.3; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=1577524667, candidate Ir=3210403255, max 103.3->103.8; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=1658977496, candidate Ir=3272017222, raw delta=97.23095882187904012412233469%, max 103.8->97.5, recover_round=R5 retained +tests/bench_buf_resample.eigs jit 197.8 191.9 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=14070973095, R1.4 Ir=14341781156, measured main delta=+196.6971%, bound=ceil(delta+0.2,0.1); R1.5 N8 shared VM code-generation component (this workload has no bitwise ops): vm_run_ex +16800000 Ir offset by eigs_num_arg -7200000 Ir; R5 int/slot fast paths must recover this shared VM cost as well as JIT/leaf int execution; fresh2d6587ee control Ir=14344087931, R1.5 Ir=14353686973, measured main delta=+196.9435%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 floor exact-number-reader and non-pooled int-result boundary (1200000 calls), plus retained generic index/slot work; banked R1.6a Ir=14353692804, current Ir=14590083369, main Ir=4833804762, main delta=+201.83435383%; bound=ceil(delta+0.2,0.1); R5 must recover this generic int producer/read-boundary work in the interpreter as well as JIT/leaf execution while preserving the semantic checks; R1.6z measured against pre-round2ba66cc8: old Ir=14578356573, tip Ir=14840872110, net +262515537 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +207811659 Ir (eigs_float_arg +126290530 Ir, eigs_number_arg +216479136 Ir, eigs_num_arg_slow -134958007 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +43200018 Ir (buffer_read_f64 +57600024 Ir, jit_helper_index_get -14400006 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +11503643 Ir (vm_run_ex +11503643 Ir); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +212 Ir (other runtime functions -26 Ir; external string/number-formatting/startup functions +238 Ir; every function delta preserved); main dde78c5c Ir=4831400200, tip Ir=14840861427, local delta=207.17516274%, max 202.1->207.4; bound=ceil(delta+0.2,0.1); sync9-attribution[6512aa34,dffac840]: #1975/6512aa34 main Ir 4831402543->4831421320, candidate Ir 14840889007->14840907787, ratio component -0.000805112 pp; #1976/dffac840 main Ir 4831421320->5132645470, candidate Ir 14840907787->15250127454, ratio component -10.054560647 pp; original gate main Ir=5132644700 candidate Ir=15250126363 delta=197.120242182358736033296830384538403758982187097423673218604%; residuals (pp, independently bounded by 0.2): precompiler_vs_historical_R17=-0.000021353, private_main_path=-0.000000868, private_candidate_path=-0.000006644, private_path_total=-0.000007512, repeated_supplied_main=-0.000045442, repeated_supplied_candidate=+0.000014612, repeated_supplied_total=-0.000030830; max 207.4->197.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-controls.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=5132645466, candidate Ir=15271045307, max 197.4->197.8, local output-equivalent vm.o ablation total +8295323 Ir (vm_run_ex 8295323 Ir), cost-over-controls.json sha256=b16f04b0ab8c0b5845723457c24c3ee2185d26c26ba815c0e84e82c3f83e55dd; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=5134018039, candidate Ir=15318941582, max 197.4->198.6, local output-equivalent vm.o ablation total +8295323 Ir (vm_run_ex 8295323 Ir), cost-over-controls.json sha256=b16f04b0ab8c0b5845723457c24c3ee2185d26c26ba815c0e84e82c3f83e55dd (local attribution only; CI bound uses its own counts above); S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=5320596423, candidate Ir=15519319294, raw delta=191.6838275294235749254083201%, max 198.6->191.9, recover_round=R5 retained +tests/bench_buf_resample.eigs off 70.5 67.1 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=14114616143, R1.4 Ir=14385424763, measured main delta=+67.2701%, bound=ceil(delta+0.2,0.1); R1.6b measured component: N3 floor exact-number-reader and non-pooled int-result boundary (1200000 calls), plus retained generic index/slot work; banked R1.6a Ir=14397335640, current Ir=14633726307, main Ir=8600110047, main delta=+70.15743086%; bound=ceil(delta+0.2,0.1); R5 must recover this generic int producer/read-boundary work in the interpreter as well as JIT/leaf execution while preserving the semantic checks; main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 8600110047->8579114636 (-0.244%), candidate Ir 14633726307->14620635542 (-0.089%): main sped up more than the merged candidate; main delta=+70.4213%; bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=14621999324, tip Ir=14884514976, net +262515652 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +207811659 Ir (eigs_float_arg +126290530 Ir, eigs_number_arg +216479136 Ir, eigs_num_arg_slow -134958007 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +43200018 Ir (buffer_read_f64 +57600024 Ir, jit_helper_index_get -14400006 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +11503643 Ir (vm_run_ex +11503643 Ir); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +327 Ir (other runtime functions -26 Ir; external string/number-formatting/startup functions +353 Ir; every function delta preserved); main dde78c5c Ir=8579121088, tip Ir=14884505468, local delta=73.49685726%, max 70.7->73.7; bound=ceil(delta+0.2,0.1); sync9-attribution[6512aa34,dffac840]: #1975/6512aa34 main Ir 8579122901->8579141678, candidate Ir 14884532337->14884551117, ratio component -0.000160827 pp; #1976/dffac840 main Ir 8579141678->8997963223, candidate Ir 14884551117->15293770794, ratio component -3.527720861 pp; original gate main Ir=8997962959 candidate Ir=15293769814 delta=69.9692461914701242770474934559018782019860502073000365192990%; residuals (pp, independently bounded by 0.2): precompiler_vs_historical_R17=+0.000001753, private_main_path=+0.000001492, private_candidate_path=+0.000005512, private_path_total=+0.000007005, repeated_supplied_main=-0.000003495, repeated_supplied_candidate=+0.000016404, repeated_supplied_total=+0.000012909; max 73.7->70.2; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-controls.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=8997963724, candidate Ir=15314689410, max 70.2->70.5, local output-equivalent vm.o ablation total +8295323 Ir (vm_run_ex 8295323 Ir), cost-over-controls.json sha256=b16f04b0ab8c0b5845723457c24c3ee2185d26c26ba815c0e84e82c3f83e55dd; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=8999329577, candidate Ir=15367176115, max 70.2->71, local output-equivalent vm.o ablation total +8295323 Ir (vm_run_ex 8295323 Ir), cost-over-controls.json sha256=b16f04b0ab8c0b5845723457c24c3ee2185d26c26ba815c0e84e82c3f83e55dd (local attribution only; CI bound uses its own counts above); S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=9328159530, candidate Ir=15567552924, raw delta=66.88772178406344214827123567%, max 71->67.1, recover_round=R5 retained +tests/bench_dmg_shape.eigs jit 224.6 222.9 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; merge of 94657581 BASE component (#1171): jit_helper_call no longer calls the out-of-line slot_decref (0c16522f), so main's slot_decref drops 14,517,154 -> 28,754 Ir (BASE -14,503,457, -1.12%), while the branch runs this int code interpreted and does not reach it (CAND -445,382, -0.01%); measured +172.92% at R1.3, +175.98% after the merge; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; its float op7 also loses NUM_REUSE; fresh6382b6a5 control Ir=3527659645, R1.4 Ir=3944225152, measured main delta=+208.5522%, bound=ceil(delta+0.2,0.1); R1.5 N8 component on the DMG-shaped benchmark: exact int bitwise results retain int state through generic dictionary/boxed Value paths (dict_set_cached +101124922 Ir and _int_malloc +38500014 Ir); R5 must recover these paths in the interpreter as well as JIT/leaf int execution; fresh2d6587ee control Ir=3943793833, R1.5 Ir=4216636333, measured main delta=+229.8625%, bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=4215433176, tip Ir=4229812330, net +14379154 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +196608 Ir (eigs_float_arg +655360 Ir, eigs_number_arg +786432 Ir, eigs_num_arg_slow -1245184 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +9000000 Ir (buffer_read_f64 +12000000 Ir, jit_helper_index_get -3000000 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +5187517 Ir (vm_run_ex +5187517 Ir); constructor alias retirement -4 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); other-function residual -4967 Ir (other runtime functions -103 Ir; external string/number-formatting/startup functions -4864 Ir; every function delta preserved); main dde78c5c Ir=1278280651, tip Ir=4229768558, local delta=230.89514065%, max 230.1->231.1; bound=ceil(delta+0.2,0.1); sync9-attribution[6512aa34,dffac840]: #1975/6512aa34 main Ir 1278283481->1278295929, candidate Ir 4229780705->4229793374, ratio component -0.002231162 pp; #1976/dffac840 main Ir 1278295929->1344318847, candidate Ir 4229793374->4354298435, ratio component -6.989430910 pp; original gate main Ir=1344310494 candidate Ir=4354294999 delta=223.905453273951754184550760488223935563505316205617598935444%; residuals (pp, independently bounded by 0.2): precompiler_vs_historical_R17=-0.000113835, private_main_path=+0.001192429, private_candidate_path=-0.000013092, private_path_total=+0.001179337, repeated_supplied_main=-0.000820176, repeated_supplied_candidate=+0.000242503, repeated_supplied_total=-0.000577673; max 231.1->224.2; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-controls.json; cost gate rerun required; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 4354294999->4391688939 (+37393940); fixed main Ir 1344310494->1344315502; K1 ratio component +2.781634218 pp, repeated-main residual -0.001206650 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 224.2->226.9; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=1344313981, candidate Ir=4360257835, max 226.9->224.6; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=1346437651, candidate Ir=4381392295, max 226.9->225.7; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=1365479392, candidate Ir=4406262782, raw delta=222.6898046074649217408328342%, max 225.7->222.9, recover_round=R5 retained +tests/bench_dmg_shape.eigs off 73.2 71.3 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; its float op7 also loses NUM_REUSE; fresh6382b6a5 control Ir=3546396130, R1.4 Ir=3963267113, measured main delta=+66.2412%, bound=ceil(delta+0.2,0.1); R1.5 N8 component on the DMG-shaped benchmark: exact int bitwise results retain int state through generic dictionary/boxed Value paths (dict_set_cached +101124922 Ir and _int_malloc +38500146 Ir); R5 must recover these paths in the interpreter as well as JIT/leaf int execution; fresh2d6587ee control Ir=3962833268, R1.5 Ir=4198461839, measured main delta=+76.1062%, bound=ceil(delta+0.2,0.1); sync #6 (main 858bcc8e) component, measured on the exact controls: the main base moved +983 Ir (+0.00004%) and the branch side +1,009,678 Ir (+0.024%), all in vm_run_ex (+1,000,020 Ir: one more instruction per million executions, a codegen shift from main #1918's cold import-source arm in that function); delta 76.40004% re-declared by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=4197227719, tip Ir=4211611742, net +14384023 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +196608 Ir (eigs_float_arg +655360 Ir, eigs_number_arg +786432 Ir, eigs_num_arg_slow -1245184 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +9000000 Ir (buffer_read_f64 +12000000 Ir, jit_helper_index_get -3000000 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +5187517 Ir (vm_run_ex +5187517 Ir); constructor alias retirement -4 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); other-function residual -98 Ir (other runtime functions -59 Ir; external string/number-formatting/startup functions -39 Ir; every function delta preserved); main dde78c5c Ir=2379233275, tip Ir=4211607423, local delta=77.01532116%, max 76.7->77.3; bound=ceil(delta+0.2,0.1); sync9-attribution[6512aa34,dffac840]: #1975/6512aa34 main Ir 2379235340->2379252708, candidate Ir 4211614253->4211637707, ratio component -0.000306401 pp; #1976/dffac840 main Ir 2379252708->2511261893, candidate Ir 4211637707->4336132013, ratio component -4.347692639 pp; original gate main Ir=2511261680 candidate Ir=4336130936 delta=72.6674273148627027988576642478771865781824855464684190139834%; residuals (pp, independently bounded by 0.2): precompiler_vs_historical_R17=+0.000360288, private_main_path=-0.000003919, private_candidate_path=+0.000445871, private_path_total=+0.000441952, repeated_supplied_main=-0.000018564, repeated_supplied_candidate=+0.000488758, repeated_supplied_total=+0.000470194; max 77.3->72.9; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-controls.json; cost gate rerun required; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 4336130936->4373535961 (+37405025); fixed main Ir 2511261680->2511256809; K1 ratio component +1.489494219 pp, repeated-main residual +0.000334917 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 72.9->74.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=2511257539, candidate Ir=4342102828, max 74.4->73.2; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=2513209029, candidate Ir=4363315373, max 74.4->73.9; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=2565051726, candidate Ir=4388795434, raw delta=71.09968541819573427190996148%, max 73.9->71.3, recover_round=R5 retained +tests/bench_idxset.eigs jit 459.7 433.1 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=489963988, R1.4 Ir=531588673, measured main delta=+454.8994%, bound=ceil(delta+0.2,0.1); R1.5 N8 shared VM/helper code-generation component (this workload has no bitwise ops): slot_from_int becomes out of line (+1200000 Ir), eigs_slot_store_owned +1000000 Ir and vm_run_ex +599999 Ir; R5 interpreter int/slot fast paths must recover the off-tier cost as well as JIT/leaf int execution; fresh2d6587ee control Ir=531584777, R1.5 Ir=534388413, measured main delta=+457.8236%, bound=ceil(delta+0.2,0.1); main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 95798818->95787251 (-0.012%), candidate Ir 534388413->533777385 (-0.114%): the candidate got cheaper; main delta=+457.2531%; bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=534010570, tip Ir=535405691, net +1395121 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +300000 Ir (eigs_float_arg +1000000 Ir, eigs_number_arg +1200000 Ir, eigs_num_arg_slow -1900000 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +18 Ir (buffer_read_f64 +24 Ir, jit_helper_index_get -6 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +1100000 Ir (vm_run_ex +1100000 Ir); constructor alias retirement -4 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); other-function residual -4893 Ir (other runtime functions -55 Ir; external string/number-formatting/startup functions -4838 Ir; every function delta preserved); main dde78c5c Ir=95788672, tip Ir=535395772, local delta=458.93432994%, max 457.5->459.2; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 535410098->536010381 (+600283); fixed main Ir 95803283->95798538; K1 ratio component +0.626609771 pp, repeated-main residual +0.027681110 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 459.2->459.8; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=95804115, candidate Ir=536011218, max 459.8->459.7; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=97195219, candidate Ir=536398646, max 459.8->452.1; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=101461399, candidate Ir=540637816, raw delta=432.8507406053015295008892988%, max 452.1->433.1, recover_round=R5 retained +tests/bench_idxset.eigs off 128.8 112.7 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; fresh6382b6a5 control Ir=489962938, R1.4 Ir=531587958, measured main delta=+126.6422%, bound=ceil(delta+0.2,0.1); R1.5 N8 shared VM/helper code-generation component (this workload has no bitwise ops): slot_from_int becomes out of line (+1200000 Ir), eigs_slot_store_owned +1000000 Ir and vm_run_ex +599999 Ir; R5 interpreter int/slot fast paths must recover the off-tier cost as well as JIT/leaf int execution; fresh2d6587ee control Ir=531588681, R1.5 Ir=534387392, measured main delta=+127.8315%, bound=ceil(delta+0.2,0.1); main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 234553779->234547230 (-0.003%), candidate Ir 534387392->533785956 (-0.113%): the candidate got cheaper; main delta=+127.5814%; bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=534004479, tip Ir=535404391, net +1399912 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +300000 Ir (eigs_float_arg +1000000 Ir, eigs_number_arg +1200000 Ir, eigs_num_arg_slow -1900000 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +18 Ir (buffer_read_f64 +24 Ir, jit_helper_index_get -6 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +1100000 Ir (vm_run_ex +1100000 Ir); constructor alias retirement -4 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); other-function residual -102 Ir (other runtime functions -13 Ir; external string/number-formatting/startup functions -89 Ir; every function delta preserved); main dde78c5c Ir=234553336, tip Ir=535399894, local delta=128.26360227%, max 127.8->128.5; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 535408825->536009133 (+600308); fixed main Ir 234558694->234558657; K1 ratio component +0.255930865 pp, repeated-main residual +0.000036007 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 128.5->128.8; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=235942268, candidate Ir=536406225, max 128.8->127.6; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=254742782, candidate Ir=541201092, raw delta=112.4500202718206948057904149%, max 127.6->112.7, recover_round=R5 retained +tests/bench_pcm_codec.eigs jit 384.6 377.7 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; PCM component source-traced; fresh R13 PCM control interrupted, not claimed, R1.4 Ir=39010338030, measured main delta=+376.5029%, bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=39023891158, tip Ir=39585491642, net +561600484 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +468002880 Ir (eigs_float_arg +280809600 Ir, eigs_number_arg +483611520 Ir, eigs_num_arg_slow -296418240 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +86400018 Ir (buffer_read_f64 +115200024 Ir, jit_helper_index_get -28800006 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +7197108 Ir (vm_run_ex +7197108 Ir); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +473 Ir (other runtime functions -39 Ir; external string/number-formatting/startup functions +512 Ir; every function delta preserved); main dde78c5c Ir=8186872469, tip Ir=39585480607, local delta=383.52384573%, max 376.8->383.8; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 39585577735->39640778723 (+55200988); fixed main Ir 8186959128->8186959128; K1 ratio component +0.674255082 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 383.8->384.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: #1939 method derivation, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=8186959964, candidate Ir=39653979543, max 384.4->384.6; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=8188331391, candidate Ir=39760959325, max 384.4->385.8; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=8453087611, candidate Ir=40362501565, raw delta=377.4882672749811630930226260%, max 385.8->377.7, recover_round=R5 retained +tests/bench_pcm_codec.eigs off 101.7 94.1 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; merge of 94657581 component (#1171): env_store_slot +74,400,025 Ir against vm_run_ex -63,600,434 on the branch (CAND +10,800,129, +0.03%), while on main the same move saves (vm_run_ex -92,400,393, env_store_slot +66,000,030: BASE -26,400,939, -0.13%); measured +89.39% at R1.3, +89.70% after the merge; R1.4 int MOD component: exact results use non-pooled make_int instead of the make_num float freelist; retained int state also reaches generic Value API/index paths until R5 int slot/index fast paths; PCM component source-traced; fresh R13 PCM control interrupted, not claimed, R1.4 Ir=39192628174, measured main delta=+97.5413%, bound=ceil(delta+0.2,0.1); main->phase2 sync #5 component (main 0277d7a9->88a1c911): main Ir 19840219829->19771919441 (-0.344%), candidate Ir 39192628174->39195119849 (+0.006%): main sped up on this bench and the merged candidate did not share it; main delta=+98.2363%; bound=ceil(delta+0.2,0.1); R1.6z measured against pre-round2ba66cc8: old Ir=39206179064, tip Ir=39767779547, net +561600483 Ir; final numeric-reader migration at VM buffer stores/mixed arithmetic +468002880 Ir (eigs_float_arg +280809600 Ir, eigs_number_arg +483611520 Ir, eigs_num_arg_slow -296418240 Ir); VM index/iteration/leaf helper migration including R1.6d-D1 finite-data boundary on buffers +86400018 Ir (buffer_read_f64 +115200024 Ir, jit_helper_index_get -28800006 Ir); shared VM dispatch/helper code-generation effect of bridge retirement +7197108 Ir (vm_run_ex +7197108 Ir); IEEE/structural-equality dispatch change +5 Ir (values_equal_impl +5 Ir); other-function residual +472 Ir (other runtime functions -39 Ir; external string/number-formatting/startup functions +511 Ir; every function delta preserved); main dde78c5c Ir=19771925548, tip Ir=39767770128, local delta=101.13250999%, max 98.5->101.4; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 39767867063->39823068224 (+55201161); fixed main Ir 19772012323->19772012323; K1 ratio component +0.279188381 pp, repeated-main residual +0.000000000 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 101.4->101.7; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[ci]: #1939 method derivation, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=19773375931, candidate Ir=39962433104, max 101.7->102.4; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=20923310616, candidate Ir=40563972860, raw delta=93.86976375039253013783198906%, max 102.4->94.1, recover_round=R5 retained +tests/bench_perf.eigs jit 32 30.7 R5 design §3: INT_* literal opcodes stop the JIT scanner and its guards bail on int operands, so int code runs interpreted until the R5 JIT/leaf int fast paths; R1.6 component: its int `range` alone raises this row to +32.4% (range-int control, measured at R1.3, against +15.7%) until R5; R1.6b measured component: N3 range-produced ints activate generic integer arithmetic/Value API paths, as the pre-registered range-int control predicts; banked R1.6a Ir=297793217, current Ir=340827242, main Ir=257477918, main delta=+32.37144554%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; sync9-attribution[S9-K1]: ruling #1758, candidate Ir 340266497->345626689 (+5360192); fixed main Ir 257503282->257503346; K1 ratio component +2.081600912 pp, repeated-main residual -0.000032842 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 32.6->34.5; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=257509128, candidate Ir=339360163, max 34.5->32; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=254144775, candidate Ir=338174717, max 34.5->33.3; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=273355555, candidate Ir=356674407, raw delta=30.48002884009435988963165574%, max 33.3->30.7, recover_round=R5 retained +tests/bench_perf.eigs off 18.3 17.3 R5 design §3: interpreted int arithmetic takes the checked generic arm, and mixed int/float operands, int indexes and int / and % box through slow paths until the R5 int fast paths; R1.6 component: its int `range` alone raises this row to +18.7% (range-int control, measured at R1.3, against +5.4%) until R5; R1.6b measured component: N3 range-produced ints activate generic integer arithmetic/Value API paths, as the pre-registered range-int control predicts; banked R1.6a Ir=309974276, current Ir=349154039, main Ir=294194185, main delta=+18.68148889%; bound=ceil(delta+0.2,0.1); R5 retains recovery of the remaining generic int VM/Value API work in both tiers; R1.6g component +412584 Ir (0.140 pp, measured against pre-R1.6g 59d6bb70): a fixed cost per global-env creation from eagerly registering 77 more builtins (allocation, env_intern_name, env_set_local_hashed, env_hash_rebuild; per-function cg_annotate diff); re-declared 18.9 -> 19.2 by ceil(delta+0.2, 0.1); R1.6z measured against pre-round2ba66cc8: old Ir=349488837, tip Ir=348433201, net -1055636 Ir; shared VM dispatch/helper code-generation effect of bridge retirement -220005 Ir (vm_run_ex -220005 Ir); runtime/container bridge-retirement TU code-generation/inlining consequence in list_append -816088 Ir (source body unchanged; measured old/tip assembly drops three saved registers and stack adjustment, eight executed prologue/epilogue instructions per ordinary append); constructor alias retirement -20 Ir (make_num/make_float combined, avoiding a symbol-rename false cost); other-function residual -19523 Ir (other runtime functions -213 Ir; external string/number-formatting/startup functions -19310 Ir; every function delta preserved); main dde78c5c Ir=293908946, tip Ir=348433252, local delta=18.55142783%, max 19.2->18.8; bound=ceil(delta+0.2,0.1); sync9-attribution[S9-K1]: ruling #1758, candidate Ir 348538179->353212147 (+4673968); fixed main Ir 293916981->293917359; K1 ratio component +1.590232035 pp, repeated-main residual -0.000152508 pp (bounded by 0.2); main #1975/#1976 attribution remains in original transaction; max 18.8->20.4; bound=ceil(current raw delta+0.2,0.1); full function tables: cost-k1-plan.json; cost gate rerun required; S9-K1-C[local]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate cost-measure, base Ir=293937105, candidate Ir=347001891, max 20.4->18.3; S9-K1-C[ci]: S9-K1-C STALE, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/40488660656, base Ir=290567945, candidate Ir=346000572, max 20.4->19.3; S10-STALE[ci]: hosted STALE recovery, ceil(delta+0.2,0.1), own gate https://github.com/InauguralSystems/EigenScript/actions/runs/41176457596/job/124464949394, measured head 7cfe8f6c626e012f2386226e9ac1d9362c2b7fd4, base Ir=311465651, candidate Ir=364660206, raw delta=17.07878696389541843893405761%, max 19.3->17.3, recover_round=R5 retained diff --git a/tests/phase2/embed_buffer_get_raw.c b/tests/phase2/embed_buffer_get_raw.c new file mode 100644 index 000000000..66af0a780 --- /dev/null +++ b/tests/phase2/embed_buffer_get_raw.c @@ -0,0 +1,45 @@ +/* R1.6z-D1: public API raw double contract, including NaN payload bits. */ +#include "eigenscript_all.c" +static int checks, failures; +static void check(int ok, const char *label) { + ++checks; + if (!ok) { ++failures; printf("phase2-embed-buffer: FAIL %s\n", label); } +} +int main(void) { + EigsState *st = eigs_open(); + if (!st) return 1; + const uint64_t bits[] = {UINT64_C(0x7ff8000000000000), UINT64_C(0x7ff8000000000042), + UINT64_C(0x7ff0000000000000), UINT64_C(0xfff0000000000000), UINT64_C(0x8000000000000000)}; + for (int strict = 0; strict <= 1; ++strict) { + g_strict = strict; + EigsValue *b = eigs_value_new_buffer(5); + for (int i = 0; i < 5; ++i) { + double x; memcpy(&x, &bits[i], sizeof x); + eigs_value_buffer_set(b, i, x); + g_math_flags = EIGS_MATH_UNDERFLOW; + double got = eigs_value_buffer_get(b, i); + check(memcmp(&x, &got, sizeof x) == 0, "set/get bit round trip"); + check(!eigs_has_error(), "get never raises"); + check(g_math_flags == EIGS_MATH_UNDERFLOW, "get preserves flags"); + eigs_clear_error(); + } + eigs_value_release(b); + g_math_flags = 0; + EigsValue *r = eigs_eval_string("p is buf_from_list of ([1e308])\n" + "g is buf_from_list of ([1e308])\nsgd_update of [p, g, 1e308]\n" + "sgd_update of [p, p, 1.0]\np\n"); + check(r && !eigs_has_error(), "program stores a NaN through SGD"); + if (r) { + double stored = ((Value *)r)->data.buffer.data[0]; + unsigned flags = g_math_flags; + double got = eigs_value_buffer_get(r, 0); + check(isnan(stored) && memcmp(&stored, &got, sizeof got) == 0, "program NaN bits unchanged"); + check(!eigs_has_error() && flags == g_math_flags, "program getter no side effects"); + eigs_value_release(r); + } + eigs_clear_error(); + } + eigs_close(st); + printf("phase2-embed-buffer: %s checks=%d failures=%d\n", failures ? "FAIL" : "PASS", checks, failures); + return failures ? 1 : 0; +} diff --git a/tests/phase2/fixtures/r16z-buffer-equal.eigs b/tests/phase2/fixtures/r16z-buffer-equal.eigs new file mode 100644 index 000000000..57e2fd2ba --- /dev/null +++ b/tests/phase2/fixtures/r16z-buffer-equal.eigs @@ -0,0 +1,27 @@ +# SGD's raw buffer kernel stores -inf, then -inf - -inf stores NaN. +define payload(kind) as: + local p is buf_from_list of ([1e308]) + local g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + return p +nan_a is payload of "nan" +nan_b is payload of "nan" +inf_a is payload of "inf" +inf_b is payload of "inf" +neg_inf is payload of "-inf" +finite is buf_from_list of ([1e308]) +negzero is buf_from_list of ([-1.0]) +buf_scale_range of [negzero, 0, 1, 0.0] +zero is buffer of 1 +before is math_flags of null +try: + print of [nan_a == nan_b, nan_a == nan_a, inf_a == inf_b, inf_a == finite, negzero == zero] + print of [neg_inf == neg_inf, inf_a == neg_inf, nan_a != nan_a] + print of [list_contains of [[nan_a], nan_a], list_index_of of [[nan_a], nan_a]] + print of ((math_flags of null) == before) +catch e: + print of ["unexpected-equality-raise", e.kind, e.message] diff --git a/tests/phase2/fixtures/r16z-container-self-equal.eigs b/tests/phase2/fixtures/r16z-container-self-equal.eigs new file mode 100644 index 000000000..526643e9a --- /dev/null +++ b/tests/phase2/fixtures/r16z-container-self-equal.eigs @@ -0,0 +1,29 @@ +# R1.6z-D2 cost witness: only 41 distinct lists/buffers, not 2^40 paths. +unobserved: + x is buffer of 1 + for i in range of 40: + x is [x, x] + print of (x == x) + +# SGD's raw buffer kernel stores -inf; self-SGD then stores NaN. +a is buf_from_list of ([1e308]) +g is buf_from_list of ([1e308]) +sgd_update of [a, g, 1e308] +sgd_update of [a, a, 1.0] +l is [a] +d is {"x": a} +nested is [[a]] +finite is [buffer of 1] +cycle is [] +append of [cycle, cycle] +append of [cycle, a] +finite_cycle is [] +append of [finite_cycle, finite_cycle] +before is math_flags of null +try: + print of [l == l, d == d, nested == nested, finite == finite] + print of [cycle == cycle, finite_cycle == finite_cycle, l != l, d != d] + print of [list_contains of [[l], l], list_index_of of [[l], l]] + print of ((math_flags of null) == before) +catch e: + print of ["unexpected-equality-raise", e.kind, e.message] diff --git a/tests/phase2/fixtures/r16z-vm-index.eigs b/tests/phase2/fixtures/r16z-vm-index.eigs new file mode 100644 index 000000000..53c4e43e7 --- /dev/null +++ b/tests/phase2/fixtures/r16z-vm-index.eigs @@ -0,0 +1,23 @@ +define read_cell(b, i) as: + local v is b[i] + return v +finite is buf_from_list of ([2.5]) +for warm in range of 60: + read_cell of [finite, 0.0] +for kind in ["nan", "inf", "-inf"]: + p is buf_from_list of ([1e308]) + g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + try: + v is read_cell of [p, 0.0] + print of [kind, "accepted", v] + catch e: + local strict_nan is kind == "nan" and (env_get of "EIGS_STRICT") == "1" + local message_ok is e.message == "buffer_read_f64: expected a finite number" + if strict_nan: + message_ok is e.message == "arithmetic: result is not a number (NaN has no defined value)" + print of [kind, e.kind, message_ok, e.line, (math_flags of null).invalid] diff --git a/tests/phase2/fixtures/r16z-vm-int-index.eigs b/tests/phase2/fixtures/r16z-vm-int-index.eigs new file mode 100644 index 000000000..fb61c1d97 --- /dev/null +++ b/tests/phase2/fixtures/r16z-vm-int-index.eigs @@ -0,0 +1,23 @@ +define read_cell(b, i) as: + local v is b[i] + return v +finite is buf_from_list of ([2.5]) +for warm in range of 60: + read_cell of [finite, 0.0] +for kind in ["nan", "inf", "-inf"]: + p is buf_from_list of ([1e308]) + g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + try: + v is read_cell of [p, 0] + print of [kind, "accepted", v] + catch e: + local strict_nan is kind == "nan" and (env_get of "EIGS_STRICT") == "1" + local message_ok is e.message == "buffer_read_f64: expected a finite number" + if strict_nan: + message_ok is e.message == "arithmetic: result is not a number (NaN has no defined value)" + print of [kind, e.kind, message_ok, e.line, (math_flags of null).invalid] diff --git a/tests/phase2/fixtures/r16z-vm-iter.eigs b/tests/phase2/fixtures/r16z-vm-iter.eigs new file mode 100644 index 000000000..7e83bf0cb --- /dev/null +++ b/tests/phase2/fixtures/r16z-vm-iter.eigs @@ -0,0 +1,27 @@ +define read_cell(b, i) as: + local result is 0.0 + for cell in b: + result is cell + return result +finite is buffer of 128 +for warm in range of 60: + read_cell of [finite, 0.0] +for kind in ["nan", "inf", "-inf"]: + p is buf_from_list of ([1e308]) + g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + try: + many is buffer of 128 + buf_copy of [p, 0, many, 127, 1] + v is read_cell of [many, 0.0] + print of [kind, "accepted", v] + catch e: + local strict_nan is kind == "nan" and (env_get of "EIGS_STRICT") == "1" + local message_ok is e.message == "buffer_read_f64: expected a finite number" + if strict_nan: + message_ok is e.message == "arithmetic: result is not a number (NaN has no defined value)" + print of [kind, e.kind, message_ok, e.line, (math_flags of null).invalid] diff --git a/tests/phase2/fixtures/r16z-vm-leaf-index.eigs b/tests/phase2/fixtures/r16z-vm-leaf-index.eigs new file mode 100644 index 000000000..7c4c06048 --- /dev/null +++ b/tests/phase2/fixtures/r16z-vm-leaf-index.eigs @@ -0,0 +1,22 @@ +define read_cell(b, i) as: + return b[i] +finite is buf_from_list of ([2.5]) +for warm in range of 60: + read_cell of [finite, 0.0] +for kind in ["nan", "inf", "-inf"]: + p is buf_from_list of ([1e308]) + g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + try: + v is read_cell of [p, 0.0] + print of [kind, "accepted", v] + catch e: + local strict_nan is kind == "nan" and (env_get of "EIGS_STRICT") == "1" + local message_ok is e.message == "buffer_read_f64: expected a finite number" + if strict_nan: + message_ok is e.message == "arithmetic: result is not a number (NaN has no defined value)" + print of [kind, e.kind, message_ok, e.line, (math_flags of null).invalid] diff --git a/tests/phase2/fixtures/r16z-vm-local-index.eigs b/tests/phase2/fixtures/r16z-vm-local-index.eigs new file mode 100644 index 000000000..618de719f --- /dev/null +++ b/tests/phase2/fixtures/r16z-vm-local-index.eigs @@ -0,0 +1,23 @@ +define read_cell(b, i) as: + local v is b[0] + return v +finite is buf_from_list of ([2.5]) +for warm in range of 60: + read_cell of [finite, 0.0] +for kind in ["nan", "inf", "-inf"]: + p is buf_from_list of ([1e308]) + g is buf_from_list of ([1e308]) + if kind == "inf": + g[0] is -1e308 + sgd_update of [p, g, 1e308] + if kind == "nan": + sgd_update of [p, p, 1.0] + try: + v is read_cell of [p, 0.0] + print of [kind, "accepted", v] + catch e: + local strict_nan is kind == "nan" and (env_get of "EIGS_STRICT") == "1" + local message_ok is e.message == "buffer_read_f64: expected a finite number" + if strict_nan: + message_ok is e.message == "arithmetic: result is not a number (NaN has no defined value)" + print of [kind, e.kind, message_ok, e.line, (math_flags of null).invalid] diff --git a/tests/phase2/fmt_parent_ref.txt b/tests/phase2/fmt_parent_ref.txt new file mode 100644 index 000000000..e96379986 --- /dev/null +++ b/tests/phase2/fmt_parent_ref.txt @@ -0,0 +1,3 @@ +# Phase 2 literal syntax (including //) is absent from the main merge-base. +# Compare the complete formatter corpus against the reviewed R1.8 bank. +ed09f908603a414f77ead78e241fa10aff7f2f38 diff --git a/tests/phase2/gfx_audio_boundary.c b/tests/phase2/gfx_audio_boundary.c new file mode 100644 index 000000000..5d685e963 --- /dev/null +++ b/tests/phase2/gfx_audio_boundary.c @@ -0,0 +1,61 @@ +/* #1758 R1.6d-D1: pre-register the stored-data audio boundary before migration. + * Raw C payloads reach the real shared helper; strict script constructors + * would refuse a NaN before any audio builtin could consume it. */ +#define EIGENSCRIPT_EXT_GFX 1 +#include "eigenscript_all.c" +#include "../../src/ext_gfx.c" +static int boundary_checks, boundary_failures; +static void check(int ok,const char *name) { + ++boundary_checks; + if (!ok) { ++boundary_failures; printf("phase2-gfx-audio-test: FAIL %s\n",name); } +} +static void reset_error(void) { + eigs_clear_error_value();g_has_error=g_error_print_pending=0; + g_error_msg[0]=g_error_raw[0]=0;g_math_flags=0; +} +static Value *buffer(int n) { + Value *count=make_int(n);Value *b=builtin_buffer(count);val_decref(count);return b; +} +/* Expected data-boundary raises run inside a real VM try (#1808/#2098). */ +static Value *phase2_reader_probe(Value *arg) { + (void)arg; + for (int strict=0;strict<=1;++strict) { + g_strict=strict; + for (int k=0;k<4;++k) { + Value *b=buffer(2); + double first=k==0 || k==3 ? NAN : k==1 ? INFINITY : -INFINITY; + b->data.buffer.data[0]=first;b->data.buffer.data[1]=k==3 ? INFINITY : 0.0; + reset_error();int n=-77;int16_t *pcm=audio_convert_samples(b,&n); + check(!pcm && g_has_error && g_error_kind==EK_VALUE,"stored nonfinite refuses before conversion"); + check(n==-77,"refusal does not publish a sample count"); + check(g_math_flags==(strict && isnan(first) ? EIGS_MATH_INVALID : 0),"strict first NaN flag only"); + check(strstr(g_error_raw,strict && isnan(first) ? "not a number" : "finite number")!=NULL,"data boundary first diagnostic"); + check((isnan(first) ? isnan(b->data.buffer.data[0]) : b->data.buffer.data[0]==first) && + b->data.buffer.data[1]==(k==3 ? INFINITY : 0.0),"reader leaves stored payload unchanged"); + free(pcm);reset_error();val_decref(b); + } + Value *b=buffer(4);double data[4]={2.5,-3.0,0.5,-0.5}; + memcpy(b->data.buffer.data,data,sizeof data);int n=0; + int16_t *pcm=audio_convert_samples(b,&n); + check(pcm && n==4 && !g_has_error,"finite buffer conversion succeeds"); + check(pcm && pcm[0]==32767 && pcm[1]==-32767 && pcm[2]==16384 && pcm[3]==-16384,"PCM Q7 clamp then Q4 rounding"); + free(pcm);reset_error();val_decref(b); + } + reset_error(); + return make_bool(1); +} +int main(void) { + EigsState *st=eigs_open();if (!st) return 1; + Value *probe=make_builtin(phase2_reader_probe); + eigs_set_global("phase2_reader_probe",probe); + val_decref(probe); + Value *result=eigs_eval_string("try:\n phase2_reader_probe of null\ncatch e:\n false\n"); + if (!result || result->type!=VAL_BOOL || !result->data.boolean || g_has_error) { + puts("phase2-gfx-audio-test: FAIL VM probe execution"); + boundary_failures++; + } + val_decref(result); + eigs_close(st); + printf("phase2-gfx-audio-test: %s checks=%d failures=%d\n",boundary_failures?"FAIL":"PASS",boundary_checks,boundary_failures); + return boundary_failures ? 1 : 0; +} diff --git a/tests/phase2/gfx_byte_boundary.c b/tests/phase2/gfx_byte_boundary.c new file mode 100644 index 000000000..2bdcc21cf --- /dev/null +++ b/tests/phase2/gfx_byte_boundary.c @@ -0,0 +1,219 @@ +/* #1758 R1.6e: pre-registered deferred buffer byte boundary. + * Stock buffers receive raw stored payloads after construction, so this + * reaches the actual gfx_fb/ppu_render_frame consumers before a scalar + * producer can normalize or reject the fixture. SDL's real dummy renderer + * supplies gfx_fb's sink; no SDL function is replaced. + * Intended home: tests/phase2/gfx_byte_boundary.c. Success is 247 checks. */ +#define EIGENSCRIPT_EXT_GFX 1 +#include "eigenscript_all.c" +#include "../../src/ext_gfx.c" + +static int gfx_byte_checks, gfx_byte_failures, gfx_byte_calls; + +static void gfx_byte_check(int ok, const char *client, int strict, + const char *fixture, const char *claim) { + ++gfx_byte_checks; + if (!ok) { + ++gfx_byte_failures; + fprintf(stderr, "phase2-gfx-byte-test: FAIL %s strict=%d %s: %s\n", + client, strict, fixture, claim); + } +} + +static void gfx_byte_clear_error(void) { + eigs_clear_error_value(); + g_has_error = g_error_print_pending = 0; + g_error_msg[0] = g_error_raw[0] = 0; + g_math_flags = 0; +} + +static Value *gfx_byte_stock_buffer(int count, const char *name) { + Value *size = make_int(count); + Value *buffer = builtin_buffer(size); + val_decref(size); + gfx_byte_check(buffer && buffer->type == VAL_BUFFER + && buffer->data.buffer.count == count, + "setup", 1, name, "stock buffer constructor"); + return buffer; +} + +typedef struct { + const char *name; + double first, later; + int reject, strict_nan; + const char *diagnostic; +} GfxByteCase; + +static const GfxByteCase gfx_byte_cases[] = { + {"stored-NaN", NAN, 0.0, 1, 1, "expected a finite number"}, + {"stored-positive-infinity", INFINITY, 0.0, 1, 0, "expected a finite number"}, + {"stored-negative-infinity", -INFINITY, 0.0, 1, 0, "expected a finite number"}, + {"NaN-before-infinity", NAN, INFINITY, 1, 1, "expected a finite number"}, + {"fractional-byte", 1.5, 0.0, 1, 0, "no integer representation"}, + {"above-byte-domain", 256.0, 0.0, 1, 0, "outside [0, 255]"}, + {"integral-float-byte-255", 255.0, 0.0, 0, 0, NULL} +}; + +static uint64_t gfx_byte_raw_bits(const double *cell) { + uint64_t bits; + memcpy(&bits, cell, sizeof bits); + return bits; +} + +static int gfx_byte_frame_is(const Value *frame, double expected) { + for (int i = 0; i < frame->data.buffer.count; ++i) + if (frame->data.buffer.data[i] != expected) return 0; + return 1; +} + +static void gfx_byte_exercise(int ppu, int strict, const GfxByteCase *c, + Value *args, Value *source, Value *frame) { + const char *client = ppu ? "ppu_render_frame" : "gfx_fb"; + int first = ppu ? 0xFF40 : 0; + int later = ppu ? 0xFF42 : 1; + memset(source->data.buffer.data, 0, + (size_t)source->data.buffer.count * sizeof(double)); + /* These writes deliberately bypass scalar constructors and num_guard. */ + source->data.buffer.data[first] = c->first; + source->data.buffer.data[later] = c->later; + uint64_t first_bits = gfx_byte_raw_bits(&source->data.buffer.data[first]); + uint64_t later_bits = gfx_byte_raw_bits(&source->data.buffer.data[later]); + if (ppu) { + for (int i = 0; i < frame->data.buffer.count; ++i) + frame->data.buffer.data[i] = -17.0; + } else { + int rc = p_SDL_SetRenderDrawColor(g_renderer, 255, 255, 255, 255); + int clear_rc = p_SDL_RenderClear(g_renderer); + gfx_byte_check(rc == 0 && clear_rc == 0, client, strict, c->name, + "real renderer starts white"); + } + g_strict = strict; + gfx_byte_clear_error(); + int arg_refs = args->refcount, source_refs = source->refcount; + int frame_refs = frame ? frame->refcount : 0; + ++gfx_byte_calls; + Value *answer = ppu ? builtin_ppu_render_frame(args) : builtin_gfx_fb(args); + + gfx_byte_check(answer && answer->type == VAL_NULL, client, strict, c->name, + "builtin returns its null value"); + gfx_byte_check(!!g_has_error == c->reject, client, strict, c->name, + "stored payload is refused or accepted as declared"); + gfx_byte_check(!c->reject || g_error_kind == EK_VALUE, client, strict, c->name, + "refusal is value"); + unsigned flags = strict && c->strict_nan ? EIGS_MATH_INVALID : 0; + gfx_byte_check(g_math_flags == flags, client, strict, c->name, + "only strict NaN sets INVALID, no overflow flag"); + const char *diagnostic = strict && c->strict_nan ? "not a number" : c->diagnostic; + gfx_byte_check(diagnostic ? strstr(g_error_raw, diagnostic) != NULL + : g_error_raw[0] == 0, + client, strict, c->name, + "first NaN diagnostic wins before any later infinity"); + gfx_byte_check(args->refcount == arg_refs && source->refcount == source_refs + && (!frame || frame->refcount == frame_refs), + client, strict, c->name, "builtin borrows every buffer and argument"); + gfx_byte_check(gfx_byte_raw_bits(&source->data.buffer.data[first]) == first_bits + && gfx_byte_raw_bits(&source->data.buffer.data[later]) == later_bits, + client, strict, c->name, "read leaves stored raw payloads unchanged"); + if (ppu) { + /* LCDC=255 is a valid byte. Zero VRAM/palettes produce shade zero; + * a refused first register must leave the framebuffer untouched. */ + gfx_byte_check(gfx_byte_frame_is(frame, c->reject ? -17.0 : 0.0), + client, strict, c->name, "refusal preserves output; valid LCDC renders"); + } else { + SDL_Rect pixel_rect = {0, 0, 1, 1}; + Uint32 pixel = 0; + int rc = p_SDL_RenderReadPixels(g_renderer, &pixel_rect, + MY_SDL_PIXELFORMAT_ARGB8888, + &pixel, (int)sizeof pixel); + Uint32 expected = c->reject ? UINT32_C(0x00FFFFFF) : 0; + gfx_byte_check(rc == 0 && (pixel & UINT32_C(0x00FFFFFF)) == expected, + client, strict, c->name, + "refusal preserves real pixels; byte 255 selects black"); + } + val_decref(answer); + gfx_byte_clear_error(); +} + +/* Expected data-boundary raises run inside a real VM try (#1808/#2098). + * Cleanup finishes inside the callback; the state outlives VM execution. */ +static Value *phase2_reader_probe(Value *arg) { + (void)arg; + g_strict = 1; + Value *pixels = NULL, *memory = NULL, *frame = NULL; + Value *gfx_args = NULL, *ppu_args = NULL; + gfx_byte_check(setenv("SDL_VIDEODRIVER", "dummy", 1) == 0, + "setup", 1, "SDL", "select real dummy video driver"); + Value *open_args = make_list_heap(3); + list_append_owned(open_args, make_int(16)); + list_append_owned(open_args, make_int(16)); + list_append_owned(open_args, make_str("phase2 gfx byte boundary")); + Value *opened = builtin_gfx_open(open_args); + int ready = opened && opened->type == VAL_BOOL && opened->data.boolean + && g_window && g_renderer && p_SDL_RenderReadPixels; + gfx_byte_check(ready, "setup", 1, "SDL", "gfx_open creates a real 16x16 renderer"); + val_decref(opened); + val_decref(open_args); + if (!ready) goto cleanup; + + pixels = gfx_byte_stock_buffer(2, "gfx pixels"); + memory = gfx_byte_stock_buffer(65536, "GB memory"); + frame = gfx_byte_stock_buffer(23040, "GB framebuffer"); + gfx_byte_check(!g_has_error, "setup", 1, "buffers", "constructors are error-free"); + if (g_has_error || !pixels || pixels->type != VAL_BUFFER + || !memory || memory->type != VAL_BUFFER || !frame || frame->type != VAL_BUFFER) + goto cleanup; + + gfx_args = make_list_heap(6); + list_append(gfx_args, pixels); + list_append_owned(gfx_args, make_int(2)); + list_append_owned(gfx_args, make_int(1)); + list_append_owned(gfx_args, make_int(0)); + list_append_owned(gfx_args, make_int(0)); + list_append_owned(gfx_args, make_int(1)); + ppu_args = make_list_heap(2); + list_append(ppu_args, memory); + list_append(ppu_args, frame); + for (int strict = 0; strict <= 1; ++strict) { + for (unsigned i = 0; i < sizeof gfx_byte_cases / sizeof gfx_byte_cases[0]; ++i) { + gfx_byte_exercise(0, strict, &gfx_byte_cases[i], gfx_args, pixels, NULL); + gfx_byte_exercise(1, strict, &gfx_byte_cases[i], ppu_args, memory, frame); + } + } + gfx_byte_check(gfx_byte_calls == 28, "population", 1, "both clients", + "all seven fixtures run through both clients in both strict modes"); + +cleanup: + gfx_byte_clear_error(); + Value *closed = builtin_gfx_close(make_null()); + gfx_byte_check(closed && closed->type == VAL_NULL && !g_has_error + && !g_window && !g_renderer, + "cleanup", 1, "SDL", "gfx_close releases the real renderer"); + val_decref(closed); + val_decref(gfx_args); + val_decref(ppu_args); + val_decref(pixels); + val_decref(memory); + val_decref(frame); + gfx_byte_clear_error(); + return make_bool(1); +} + +int main(void) { + EigsState *st = eigs_open(); + gfx_byte_check(st != NULL, "setup", 1, "state", "eigs_open"); + if (!st) goto summary; + Value *probe = make_builtin(phase2_reader_probe); + eigs_set_global("phase2_reader_probe", probe); + val_decref(probe); + Value *result = eigs_eval_string("try:\n phase2_reader_probe of null\ncatch e:\n false\n"); + if (!result || result->type != VAL_BOOL || !result->data.boolean || g_has_error) { + fputs("phase2-gfx-byte-test: FAIL VM probe execution\n", stderr); + gfx_byte_failures++; + } + val_decref(result); + eigs_close(st); +summary: + printf("phase2-gfx-byte-test: %s checks=%d failures=%d\n", + gfx_byte_failures ? "FAIL" : "PASS", gfx_byte_checks, gfx_byte_failures); + return gfx_byte_failures ? 1 : 0; +} diff --git a/tests/phase2/lib_floor_mod.txt b/tests/phase2/lib_floor_mod.txt new file mode 100644 index 000000000..d7d4051cc --- /dev/null +++ b/tests/phase2/lib_floor_mod.txt @@ -0,0 +1,14 @@ +# N4 successful stdlib value changes (#1758). TSV: module.function, N4: reason. +# phase2_lib_diff.sh derives modulo users and named callers from source; each +# declaration must be exercised by a changed successful result or is stale. +datetime.days_from_civil N4: _fdiv and month normalization now floor negative years/months in the civil-date formula. +datetime.civil_from_days N4: _fdiv now floors negative era offsets for dates before the civil epoch. +datetime.weekday_from_days N4: days before -4 now map to the non-negative weekday index (days + 4) % 7. +datetime.epoch_from_civil N4: inherits days_from_civil's floor corrections for negative years/months. +datetime.civil_from_epoch N4: _fdiv floors negative epoch seconds to the previous day with a non-negative remainder. +eigen.eigen_eval N4: the meta-interpreter's % binop inherits host floor modulo for opposite-sign operands. +eigen.eigen_run N4: running source through eigen_eval inherits host floor modulo for opposite-sign operands. +checksum.adler32 N4: accepted numeric buffers can contain negative non-byte samples; their modulo residues now floor (documented byte inputs are unchanged). +checksum.sum8 N4: accepted numeric buffers can contain negative non-byte samples; their modulo residues now floor (documented byte inputs are unchanged). +supervise.supervise_step N4: a negative heartbeat's bounded observer signature now uses the non-negative progress % 8 residue. +supervise.supervise_run N4: inherits supervise_step's changed observer feed for negative heartbeat progress. diff --git a/tests/phase2/lib_int_domain.txt b/tests/phase2/lib_int_domain.txt new file mode 100644 index 000000000..adad7850f --- /dev/null +++ b/tests/phase2/lib_int_domain.txt @@ -0,0 +1,6 @@ +# Integer-domain stdlib functions (#1848): tools/phase2_lib_diff.sh lists every +# call that main completes and the branch raises; a listed function must be +# declared here, and a declaration no call needs is stale. Decision (A), #1758: +# int overflow raises, as Julia's factorial(21) does. TSV: module.function, reason. +probability.factorial n! is an integer count: past 20! it leaves int64 and raises, decided (A) in #1758 (comment 6266498388; Julia's factorial(21) raises) +probability.permutations P(n,k) is an integer count: R1.6b N3 range elements keep the multiplicative accumulator in ints; products beyond int64 raise under decision (A), #1758 (for example P(21,21)=21!). diff --git a/tests/phase2/lua_bitwise_diff.py b/tests/phase2/lua_bitwise_diff.py new file mode 100644 index 000000000..9d1889a47 --- /dev/null +++ b/tests/phase2/lua_bitwise_diff.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""N8 differential: Lua 5.4 is the value/kind/error oracle, never Python. + +Usage: python3 tests/phase2/lua_bitwise_diff.py +Python enumerates source literals and compares records; it does no bitwise +arithmetic. Each builtin is compared directly with Lua's matching operator. +""" +import argparse +from collections import Counter +import json +import os +from pathlib import Path +import subprocess +import tempfile + +VALUES = ['0', '1', '-1', '2', '-2', '0x7F', '0xFF', '0xFFFF', + '2147483647', '2147483648', '2147483649', '4611686018427387904', + '9223372036854775807', '(-9223372036854775807 - 1)', '-256', + '9007199254740993', '140737488355327', '140737488355328', + '3.0', '3.5', '-3.0', '-3.5', '9223372036854775808.0'] +COUNTS = [str(x) for x in range(-70, 71)] + ['3.0', '3.5', '-3.0', '-3.5', + '9223372036854775807', '(-9223372036854775807 - 1)', + '9223372036854775808.0'] +OPS = [('&', '&', 'bit_and'), ('|', '|', 'bit_or'), ('^', '~', 'bit_xor'), + ('<<', '<<', 'bit_shl'), ('>>', '>>', 'bit_shr'), ('~', '~', 'bit_not')] + + +def cases(): + for eigs, lua, builtin in OPS: + pairs = [(v, '0') for v in VALUES] if eigs == '~' else [ + (a, b) for a in VALUES for b in (COUNTS if eigs in ('<<', '>>') else VALUES)] + for a, b in pairs: + want = f'{lua}({a})' if eigs == '~' else f'({a}) {lua} ({b})' + for form in ('op', 'builtin'): + got = (f'~({a})' if eigs == '~' else f'({a}) {eigs} ({b})') if form == 'op' else ( + f'{builtin} of ({a})' if eigs == '~' else f'{builtin} of [{a}, {b}]') + yield f'{eigs}/{form}({a}, {b})', want, got + + +def compare(expected, actual): + """Exact records include both kind and integer digits (or value error).""" + return [(i, a, b) for i, (a, b) in enumerate(zip(expected, actual)) if a != b] + + +def run(binary, path, env): + proc = subprocess.run([binary, str(path)], stdin=subprocess.DEVNULL, + capture_output=True, text=True, timeout=120, env=env) + if proc.returncode or proc.stderr: + raise RuntimeError(f'{binary}: rc={proc.returncode}\n{proc.stderr}\n{proc.stdout[-2000:]}') + return proc.stdout.splitlines() + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('lua') + ap.add_argument('eigenscript') + args = ap.parse_args() + lua, eigs = (str(Path(p).resolve()) for p in (args.lua, args.eigenscript)) + version = subprocess.check_output([lua, '-v'], text=True).strip() + if not version.startswith('Lua 5.4.'): + ap.error('the oracle must be Lua 5.4 itself: ' + version) + print(version, flush=True) + env = {k: v for k, v in os.environ.items() if not k.startswith('EIGS_')} + population = list(cases()) + # Independently pin all twelve operator/form populations: a missing + # generator arm must never look like agreement on a smaller grid. + expected_counts = {f'{op}/{form}': n for op, n in + [('&', 529), ('|', 529), ('^', 529), + ('<<', 3404), ('>>', 3404), ('~', 23)] + for form in ('op', 'builtin')} + found = Counter(label.split('(', 1)[0] for label, _, _ in population) + if found != expected_counts: + raise RuntimeError(f'incomplete case populations: expected={expected_counts} actual={dict(found)}') + mismatches = [] + agree = 0 + with tempfile.TemporaryDirectory(prefix='n8-lua-') as d: + for start in range(0, len(population), 120): + batch = population[start:start + 120] + lp, ep = Path(d) / 'oracle.lua', Path(d) / 'subject.eigs' + ls, es = [], [] + for i, (_, want, got) in enumerate(batch): + ls += ['do', f'local ok, r = pcall(function() return {want} end)', + 'if ok then', f'print({i} .. "\\tok\\t" .. math.type(r) .. "\\t" .. tostring(r))', + 'elseif tostring(r):find("no integer representation", 1, true) then', + f'print({i} .. "\\terr\\tvalue")', 'else error(r) end', 'end'] + es += ['try:', f' r is {got}', + f' print of [{i}, "ok", num_type of r, str of r]', + 'catch e:', f' print of [{i}, "err", e.kind]'] + lp.write_text('\n'.join(ls) + '\n') + ep.write_text('\n'.join(es) + '\n') + expected = [line.split('\t') for line in run(lua, lp, env)] + for row in expected: + row[0] = int(row[0]) + if row[1] == 'ok' and row[2] == 'integer': + row[2] = 'int' + actual = [json.loads(line) for line in run(eigs, ep, env)] + if len(expected) != len(batch) or len(actual) != len(batch): + raise RuntimeError(f'incomplete batch at {start}: expected={len(expected)} actual={len(actual)}') + for i, row in enumerate(expected): + if row[0] != i or actual[i][0] != i: + raise RuntimeError('missing, duplicate or reordered case id') + bad = compare(expected, actual) + agree += len(batch) - len(bad) + mismatches += [(population[start + i][0], a, b) for i, a, b in bad] + for label, want, got in mismatches[:24]: + print(f'MISMATCH {label}: Lua={want!r} EigenScript={got!r}') + print(f'cases={len(population)} agree={agree} mismatch={len(mismatches)}', flush=True) + return bool(mismatches) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tests/phase2/net_byte_boundary.c b/tests/phase2/net_byte_boundary.c new file mode 100644 index 000000000..15ee79dfe --- /dev/null +++ b/tests/phase2/net_byte_boundary.c @@ -0,0 +1,83 @@ +/* R1.6d-D1: raw stored buffer bytes reach the actual net_send client. + * A real socketpair verifies that a refusal sends no valid prefix. */ +#define EIGENSCRIPT_EXT_NET 1 +#include "eigenscript_all.c" +#include "../../src/ext_net.c" + +static int checks, failures; +static void check(int ok) { checks++; if (!ok) failures++; } +static void clear_error(void) { + eigs_clear_error_value(); + g_has_error = g_error_print_pending = 0; + g_error_msg[0] = g_error_raw[0] = 0; + g_math_flags = 0; +} +/* Expected data-boundary raises run inside a real VM try (#1808/#2098). */ +static Value *phase2_reader_probe(Value *arg) { + (void)arg; + const double bad[] = { NAN, INFINITY, -INFINITY, NAN, 1.5, 256.0, 255.0 }; + for (int strict = 0; strict <= 1; strict++) { + for (int i = 0; i < 7; i++) { + int fd[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) != 0) return make_bool(0); + Value *handle = make_int((int64_t)net_register_sock(fd[0], NET_SOCK_CONN)); + Value *size = make_int(3); + Value *buffer = builtin_buffer(size); + val_decref(size); + buffer->data.buffer.data[0] = 65.0; + buffer->data.buffer.data[1] = bad[i]; + buffer->data.buffer.data[2] = i == 3 ? INFINITY : 66.0; + Value *args = make_list_heap(2); + list_append(args, handle); + list_append(args, buffer); + g_strict = strict; + clear_error(); + Value *answer = builtin_net_send(args); + int raised = g_has_error; + unsigned char received[8]; + ssize_t n = recv(fd[1], received, sizeof received, MSG_DONTWAIT); + check(!!raised == (i < 6)); + check(i == 6 || g_error_kind == EK_VALUE); + unsigned flags = strict && (i == 0 || i == 3) ? EIGS_MATH_INVALID : 0; + check(g_math_flags == flags); + const char *diagnostic = (i == 0 || i == 3) + ? (strict ? "not a number" : "expected a finite number") + : (i == 1 || i == 2) ? "expected a finite number" + : i == 4 ? "no integer representation" : "outside [0, 255]"; + check(i == 6 || strstr(g_error_raw, diagnostic) != NULL); + if (i < 6) { + check(n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)); + } else { + check(n == 3 && received[0] == 65 && received[1] == 255 && received[2] == 66); + check(answer && answer->type == VAL_INT && VAL_I64_RAW(answer) == 3); + } + val_decref(answer); + clear_error(); + Value *closed = builtin_net_close(handle); + val_decref(closed); + close(fd[1]); + val_decref(args); + val_decref(buffer); + val_decref(handle); + } + } + clear_error(); + return make_bool(1); +} +int main(void) { + EigsState *st = eigs_open(); + if (!st) return 2; + Value *probe = make_builtin(phase2_reader_probe); + eigs_set_global("phase2_reader_probe", probe); + val_decref(probe); + Value *result = eigs_eval_string("try:\n phase2_reader_probe of null\ncatch e:\n false\n"); + int ran = result && result->type == VAL_BOOL && result->data.boolean && !g_has_error; + val_decref(result); + eigs_close(st); + if (!ran) { + fputs("phase2-net-byte: FAIL VM probe execution\n", stderr); + return 2; + } + printf("phase2-net-byte: %s checks=%d failures=%d\n", failures ? "FAIL" : "PASS", checks, failures); + return failures ? 1 : 0; +} diff --git a/tests/phase2/num_text.c b/tests/phase2/num_text.c new file mode 100644 index 000000000..a5198a98d --- /dev/null +++ b/tests/phase2/num_text.c @@ -0,0 +1,120 @@ +/* R1.7 N6: Python repr oracle pins for the shared float text writer. + * --bits reads binary64 words from stdin for an external Python oracle. */ +#include "eigenscript_all.c" + +typedef struct { uint64_t bits; const char *text; } NumTextCase; +static const NumTextCase num_text_cases[] = { + {UINT64_C(0x0000000000000000), "0.0"}, + {UINT64_C(0x4008000000000000), "3.0"}, + {UINT64_C(0x408f400000000000), "1000.0"}, + {UINT64_C(0x3fd3333333333334), "0.30000000000000004"}, + {UINT64_C(0x430c6bf526340000), "1000000000000000.0"}, + {UINT64_C(0x4341c37937e08000), "1e+16"}, + {UINT64_C(0x3ee4f8b588e368f1), "1e-05"}, + {UINT64_C(0x3f1a36e2eb1c432d), "0.0001"}, + {UINT64_C(0x44b52d02c7e14af6), "1e+23"}, + {UINT64_C(0x3f1a36e2eb1c432c), "9.999999999999999e-05"}, + {UINT64_C(0x4341c37937e07fff), "9999999999999998.0"}, + {UINT64_C(0x4341c37937e08001), "1.0000000000000002e+16"}, + /* Exact decimal halfway values choose an even last digit. */ + {UINT64_C(0x430c6bf526340002), "1000000000000000.2"}, + {UINT64_C(0x430c6bf526340006), "1000000000000000.8"}, + {UINT64_C(0x42d6bcc41e900008), "100000000000000.12"}, + {UINT64_C(0x42d6bcc41e900018), "100000000000000.38"}, + /* The nearest rounded decimal misses the shorter round-trip interval. */ + {UINT64_C(0x3e70000000000000), "5.960464477539063e-08"}, + {UINT64_C(0x3d30000000000000), "5.684341886080802e-14"}, + {UINT64_C(0x4580000000000000), "6.189700196426902e+26"}, + {UINT64_C(0x5120000000000000), "6.070840288205404e+82"}, + {UINT64_C(0x7fefffffffffffff), "1.7976931348623157e+308"}, + {UINT64_C(0x0010000000000000), "2.2250738585072014e-308"}, + {UINT64_C(0x000fffffffffffff), "2.225073858507201e-308"}, + {UINT64_C(0x0000000000000001), "5e-324"}, + {UINT64_C(0x0000000000000002), "1e-323"} +}; +static int num_text_checks, num_text_failures; + +static void num_text_check(int ok, const char *label, uint64_t bits, size_t cap) { + num_text_checks++; + if (!ok) { + num_text_failures++; + printf("phase2-num-text-test: FAIL %s bits=%016llx cap=%zu\n", + label, (unsigned long long)bits, cap); + } +} + +static void num_text_test(uint64_t bits, const char *want, int finite) { + double value; + memcpy(&value, &bits, sizeof value); + char got[32]; + eigs_num_text(got, sizeof got, value); + num_text_check(strcmp(got, want) == 0, "Python repr", bits, sizeof got); + if (finite) { + double parsed = strtod(got, NULL); + uint64_t parsed_bits; + memcpy(&parsed_bits, &parsed, sizeof parsed_bits); + num_text_check(parsed_bits == bits, "bit-exact round trip", bits, sizeof got); + } + size_t len = strlen(want); + for (size_t cap = 0; cap <= len + 2; cap++) { + unsigned char bounded[40]; + memset(bounded, 0xa5, sizeof bounded); + eigs_num_text((char *)bounded + 1, cap, value); + size_t copied = cap == 0 ? 0 : len < cap ? len : cap - 1; + int ok = bounded[0] == 0xa5 && + memcmp(bounded + 1, want, copied) == 0; + if (cap > 0) ok = ok && bounded[copied + 1] == 0; + size_t untouched = cap == 0 ? 1 : copied + 2; + for (size_t i = untouched; i < sizeof bounded; i++) + if (bounded[i] != 0xa5) ok = 0; + num_text_check(ok, "bounded output", bits, cap); + } +} + +static int num_text_bits(void) { + char line[32]; + size_t count = 0; + while (fgets(line, sizeof line, stdin)) { + size_t len = strlen(line); + if (len == 17 && line[16] == '\n') len--; + if (len != 16) return 2; + uint64_t bits = 0; + for (size_t i = 0; i < len; i++) { + int digit = line[i] >= '0' && line[i] <= '9' ? line[i] - '0' : + line[i] >= 'a' && line[i] <= 'f' ? line[i] - 'a' + 10 : + line[i] >= 'A' && line[i] <= 'F' ? line[i] - 'A' + 10 : -1; + if (digit < 0) return 2; + bits = (bits << 4) | (unsigned)digit; + } + double value; + char text[32]; + memcpy(&value, &bits, sizeof value); + eigs_num_text(text, sizeof text, value); + puts(text); + count++; + } + return ferror(stdin) || count == 0 ? 2 : 0; +} + +int main(int argc, char **argv) { + if (argc == 2 && strcmp(argv[1], "--bits") == 0) return num_text_bits(); + if (argc != 1) return 2; + for (size_t i = 0; i < sizeof num_text_cases / sizeof num_text_cases[0]; i++) { + const NumTextCase *c = &num_text_cases[i]; + num_text_test(c->bits, c->text, 1); + char negative[32]; + snprintf(negative, sizeof negative, "-%s", c->text); + num_text_test(c->bits | UINT64_C(0x8000000000000000), negative, 1); + } + num_text_test(UINT64_C(0x7ff0000000000000), "inf", 0); + num_text_test(UINT64_C(0xfff0000000000000), "-inf", 0); + num_text_test(UINT64_C(0x7ff8000000000000), "nan", 0); + num_text_test(UINT64_C(0xfff8000000000000), "nan", 0); + /* A zero capacity also permits a null destination. */ + eigs_num_text(NULL, 0, 1.25); + printf("phase2-num-text-test: %s cases=%zu checks=%d failures=%d\n", + num_text_failures ? "FAIL" : "PASS", + 2 * (sizeof num_text_cases / sizeof num_text_cases[0]) + 4, + num_text_checks, num_text_failures); + return num_text_failures ? 1 : 0; +} diff --git a/tests/phase2/observation_hooks.c b/tests/phase2/observation_hooks.c new file mode 100644 index 000000000..eb1aae970 --- /dev/null +++ b/tests/phase2/observation_hooks.c @@ -0,0 +1,159 @@ +/* observation_hooks.c: the test-only LD_PRELOAD shim for the R1.6 per-builtin + * behaviour table (#1758, ruling A4). It starts from the blind bar critic's + * observation_hooks.c (2026-10-20). EigenScript's source and binary are + * unchanged: the runtime dlopen()s SDL and SDL_mixer and dlsym()s each entry + * point, so interposing dlsym() reaches them, and clock_gettime() is + * interposed directly. + * + * One hook per process, chosen by R16_HOOK: + * pcm SDL_QueueAudio: log the int16 samples actually delivered. + * mix SDL_OpenAudioDevice: wrap the playback callback (EigenScript's + * own channel mixer) and log the first non-silent buffer it + * delivers. + * mixvol Mix_VolumeMusic: log the volume passed to SDL_mixer. + * clock clock_gettime(CLOCK_MONOTONIC): answer 9007199 s 254740993 ns, + * that is 2^53 + 1 ns. + * events SDL_PollEvent: deliver mouse motion (17, -9), key scancode 4, + * and button 1 at (23, 42); after that the queue is empty. + * Every hooked process first appends "hook-loaded " to the file named + * by R16_HOOK_LOG. Capture hooks append their payload lines there too. The + * runner refuses a hook row whose log lacks the marker. + * R16_PLANT=silence is a test mutation, used only by the plants: delivered + * PCM becomes zeros, and the log records what was delivered. + * Build: gcc -shared -fPIC -O0 -Wall -Wextra observation_hooks.c -ldl -o observation_hooks.so */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +typedef struct { /* the layout of EigenScript's own SDL_AudioSpec mirror (ext_gfx.c) */ + int freq; + uint16_t format; + uint8_t channels; + uint8_t silence; + uint16_t samples; + uint16_t padding; + uint32_t size; + void (*callback)(void *, uint8_t *, int); + void *userdata; +} HookAudioSpec; + +static int hook_is(const char *name) { + const char *h = getenv("R16_HOOK"); + return h && strcmp(h, name) == 0; +} + +static void hook_log(const char *fmt, const int16_t *s, int n) { + const char *path = getenv("R16_HOOK_LOG"); + if (!path || !path[0]) return; + FILE *f = fopen(path, "a"); + if (!f) return; + fputs(fmt, f); + for (int i = 0; i < n; i++) fprintf(f, " %d", s[i]); + fputc('\n', f); + fclose(f); +} + +__attribute__((constructor)) static void hook_loaded(void) { + const char *h = getenv("R16_HOOK"); + if (!h || !h[0]) return; + char line[64]; + snprintf(line, sizeof line, "hook-loaded %s", h); + hook_log(line, NULL, 0); +} + +static int silence_plant(void) { + const char *p = getenv("R16_PLANT"); + return p && strcmp(p, "silence") == 0; +} + +/* pcm: SDL_QueueAudio(dev, data, len) */ +static int (*real_queue)(uint32_t, const void *, uint32_t); +static int capture_queue(uint32_t dev, const void *data, uint32_t len) { + void *out = (void *)data; + void *zeros = NULL; + if (silence_plant()) { zeros = calloc(len ? len : 1, 1); out = zeros; } + int n = (int)(len / 2); + char head[64]; + snprintf(head, sizeof head, "SDL_QueueAudio %d:", n); + hook_log(head, (const int16_t *)out, n < 8 ? n : 8); + int rc = real_queue(dev, out, len); + free(zeros); + return rc; +} + +/* mix: wrap the playback callback that SDL_OpenAudioDevice registers */ +static uint32_t (*real_open)(const char *, int, const HookAudioSpec *, HookAudioSpec *, int); +static void (*real_cb)(void *, uint8_t *, int); +static int mix_logged; +static void wrapped_cb(void *ud, uint8_t *stream, int len) { + real_cb(ud, stream, len); + if (silence_plant()) memset(stream, 0, (size_t)len); + if (mix_logged) return; + const int16_t *s = (const int16_t *)stream; + int n = len / 2; + for (int i = 0; i < n; i++) { + if (s[i] != 0) { + hook_log("mix:", s, n < 8 ? n : 8); + mix_logged = 1; + return; + } + } +} +static uint32_t wrap_open(const char *device, int iscapture, const HookAudioSpec *desired, + HookAudioSpec *obtained, int allowed) { + if (iscapture || !desired || !desired->callback) return real_open(device, iscapture, desired, obtained, allowed); + HookAudioSpec spec = *desired; + real_cb = spec.callback; + spec.callback = wrapped_cb; + return real_open(device, iscapture, &spec, obtained, allowed); +} + +/* mixvol: Mix_VolumeMusic(volume) */ +static int (*real_volume)(int); +static int capture_volume(int volume) { + char line[64]; + snprintf(line, sizeof line, "Mix_VolumeMusic %d", volume); + hook_log(line, NULL, 0); + return real_volume(volume); +} + +/* events: SDL_PollEvent(SDL_Event *); EigenScript's SDL_Event mirror is 64 bytes */ +static int injected_poll(void *out) { + static int seq = 0; + uint32_t *u = out; + int32_t *s = out; + memset(out, 0, 64); + switch (seq++) { + case 0: u[0] = 0x400; s[5] = 17; s[6] = -9; return 1; /* SDL_MOUSEMOTION */ + case 1: u[0] = 0x300; s[4] = 4; return 1; /* SDL_KEYDOWN, scancode */ + case 2: u[0] = 0x401; ((uint8_t *)out)[16] = 1; s[5] = 23; s[6] = 42; return 1; /* SDL_MOUSEBUTTONDOWN */ + default: return 0; + } +} + +static void *(*real_dlsym)(void *, const char *); +void *dlsym(void *handle, const char *name) { + if (!real_dlsym) real_dlsym = dlvsym(RTLD_NEXT, "dlsym", "GLIBC_2.2.5"); + void *p = real_dlsym(handle, name); + if (!p) return p; + if (hook_is("events") && strcmp(name, "SDL_PollEvent") == 0) return (void *)injected_poll; + if (hook_is("pcm") && strcmp(name, "SDL_QueueAudio") == 0) { real_queue = p; return (void *)capture_queue; } + if (hook_is("mix") && strcmp(name, "SDL_OpenAudioDevice") == 0) { real_open = p; return (void *)wrap_open; } + if (hook_is("mixvol") && strcmp(name, "Mix_VolumeMusic") == 0) { real_volume = p; return (void *)capture_volume; } + return p; +} + +int clock_gettime(clockid_t clockid, struct timespec *ts) { + static int (*real_clock)(clockid_t, struct timespec *); + if (hook_is("clock") && clockid == CLOCK_MONOTONIC) { + ts->tv_sec = 9007199; + ts->tv_nsec = 254740993; + return 0; + } + if (!real_clock) real_clock = dlvsym(RTLD_NEXT, "clock_gettime", "GLIBC_2.17"); + return real_clock(clockid, ts); +} diff --git a/tests/phase2/oracle_expect.txt b/tests/phase2/oracle_expect.txt new file mode 100644 index 000000000..8ddfea54f --- /dev/null +++ b/tests/phase2/oracle_expect.txt @@ -0,0 +1,24 @@ +interp/straight int64_count@10 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/straight int64_count@10 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/straight int64_count@10 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@10 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@10 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@10 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@10 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@10 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@10 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@10 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@10 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@10 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/straight int64_count@60 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/straight int64_count@60 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/straight int64_count@60 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@60 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@60 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +interp/loop int64_count@60 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@60 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@60 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +jit/loop int64_count@60 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@60 converged R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@60 stable R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row +osr/loop int64_count@60 diverging R3 #1830: the predicates read a unit step near 2^53 as settled (relative scale; main reads an exact float counter at 2^50 the same way); first measured at R1.3 because the int64 capability row first runs here; int64_count is R3's acceptance row diff --git a/tests/phase2/q7_readers.c b/tests/phase2/q7_readers.c new file mode 100644 index 000000000..5e544aef1 --- /dev/null +++ b/tests/phase2/q7_readers.c @@ -0,0 +1,102 @@ +/* #1758 R1.6e: Q7 through the real no-SDL amalgamation. */ +#include "eigenscript_all.c" +static int checks, failures; +static void check(int ok, const char *name) { + ++checks; if (!ok) { ++failures; printf("phase2-q7-test: FAIL %s\n", name); } +} +static void clear_error(void) { + eigs_clear_error_value(); g_has_error = g_error_print_pending = 0; + g_error_msg[0] = g_error_raw[0] = 0; +} +static EigsShape shape(EigsShapeKind kind, const double *v, int n) { + Value *args = make_list_heap(n); + /* Raw fixtures intentionally bypass the current scalar constructor clamp. */ + for (int i=0; idata.list.count=n; + EigsShape out; EigsRect area = {0,0,640,480}; + check(eigs_geom_shape(args,0,kind,&area,&out,"q7-test"), "shape read"); + val_decref(args); return out; +} +/* Expected reader raises run inside a real VM try (#1808/#2098). */ +static Value *phase2_reader_probe(Value *arg) { + (void)arg; + EigsShape s = shape(EIGS_RECT, (double[]){-3e9,0,6e9,10},4); + check(!s.empty && s.x==0 && s.y==0 && s.w==640 && s.h==10, "whole rect, not per-vertex saturation"); + s = shape(EIGS_LINE,(double[]){-640,-480,1280,960},4); + check(!s.empty && s.x==0 && s.y==0 && s.x2==640 && s.y2==480, "Liang-Barsky slope"); + s = shape(EIGS_LINE,(double[]){-100,10,100,110},4); + check(!s.empty && s.x==0 && s.y==60 && s.x2==100 && s.y2==110,"line intersection preserves slope"); + s = shape(EIGS_LINE,(double[]){-0x1p100,10,0x1p100,10},4); + check(!s.empty && s.x==0 && s.y==10 && s.x2==640 && s.y2==10,"huge line retains visible interval"); + s = shape(EIGS_LINE,(double[]){639.25,10,639.25,20},4); + check(!s.empty && s.x==639 && s.x2==639,"fractional last-pixel line"); + s = shape(EIGS_LINE,(double[]){-0x1p100,-0x1p100,0x1p100,0x1p100},4); + check(!s.empty && s.x==0 && s.y==0 && s.x2==480 && s.y2==480,"huge diagonal slope"); + s = shape(EIGS_LINE,(double[]){12,13,12,13},4); + check(!s.empty && s.x==12 && s.y==13 && s.x2==12 && s.y2==13,"degenerate visible line"); + s = shape(EIGS_RECT,(double[]){0,0,0,10},4); check(s.empty,"empty rectangle"); + s = shape(EIGS_CIRCLE,(double[]){0,0,-1},3); check(s.empty,"negative radius"); + double hard_line[4]={-0x1p80,0x1.bfffb00000002p7,0x1.0000000000001p80,0x1.700014p9}; + s=shape(EIGS_LINE,hard_line,4); + check(!s.empty && s.x==0 && s.y==480 && s.x2==320 && s.y2==480,"boundary determinant cancellation"); + Value *custom=make_list_heap(4); + for (int k=0;k<4;++k) list_values_storage(custom)[k]=slot_from_f64(hard_line[k]); + custom->data.list.count=4;EigsRect translated={0,480,640,480}; + check(eigs_geom_shape(custom,0,EIGS_LINE,&translated,&s,"q7-test"),"translated drawable line read"); + check(!s.empty && s.x==320 && s.y==480 && s.x2==640 && s.y2==480,"translated drawable determinant cancellation"); + val_decref(custom); + s=shape(EIGS_LINE,(double[]){0,0.5,0x1p100,0x1p99},4); + check(!s.empty && s.x2==640 && s.y2==320,"sub-ulp Q4 boundary tie"); + s=shape(EIGS_LINE,(double[]){-1e308,-1e308,1e308,1e308},4); + check(!s.empty && s.x==0 && s.y==0 && s.x2==480 && s.y2==480,"binary64 product exponent extremes"); + s=shape(EIGS_LINE,(double[]){-1e308,-0x1p-1074,1e308,0x1p-1074},4); + check(!s.empty && s.x==0 && s.y==0 && s.x2==640 && s.y2==0,"binary64 subnormal edge products"); + s = shape(EIGS_CIRCLE,(double[]){320,240,3e9},3); + check(!s.empty && s.x==0 && s.y==0 && s.w==640 && s.h==480,"huge circle visible loops only"); + s = shape(EIGS_RRECT,(double[]){-3e9,-3e9,6e9,6e9,10},5); + check(!s.empty && s.w==640 && s.h==480,"huge rrect"); + s = shape(EIGS_RECT,(double[]){-1e308,0,1.5e308,10},4); + check(!s.empty && s.w==640,"finite intermediate range"); + s = shape(EIGS_POINT,(double[]){3e9,0},2); check(s.empty,"outside point"); + s = shape(EIGS_RECT,(double[]){20.5,20,2,2},4); + check(s.x==21 && s.w==2,"round after intersection"); + int bounded=-1; Value v={.type=VAL_FLOAT}; VAL_F64_RAW(&v)=300; + check(eigs_bounded_arg(&v,0,255,&bounded,"q7-test") && bounded==255,"bounded clamp"); + VAL_F64_RAW(&v)=-1.5; + check(eigs_bounded_arg(&v,-32767,32767,&bounded,"q7-test") && bounded==-2,"Q4 negative tie"); + int64_t mag=0; VAL_F64_RAW(&v)=3e12; + check(!eigs_magnitude_arg(&v,0,INT_MAX,&mag,"q7-test") && g_has_error && g_error_kind==EK_VALUE && strstr(g_error_raw,"q7-test"),"magnitude refusal"); + clear_error(); + Value *args=make_list_heap(2); list_append_owned(args,make_float(0)); list_append_owned(args,make_float(0)); + list_values_storage(args)[0]=slot_from_f64(INFINITY); + EigsRect area={0,0,640,480}; + check(!eigs_geom_shape(args,0,EIGS_POINT,&area,&s,"q7-test") && g_error_kind==EK_VALUE,"nonfinite refusal"); + clear_error(); val_decref(args); + args=make_list_heap(2); + check(!eigs_geom_shape(args,0,EIGS_POINT,&area,&s,"q7-test") && g_error_kind==EK_TYPE,"missing coordinate"); + clear_error(); + list_append_owned(args,make_str("bad")); list_append_owned(args,make_int(0)); + check(!eigs_geom_shape(args,0,EIGS_POINT,&area,&s,"q7-test") && g_error_kind==EK_TYPE,"non-number coordinate"); + clear_error(); val_decref(args); + Value *boxed=make_int(INT64_C(9007199254740993)); args=make_list_heap(2); + list_append(args,boxed);list_append_owned(args,make_int(0)); int refs=boxed->refcount; + check(eigs_geom_shape(args,0,EIGS_POINT,&area,&s,"q7-test"),"boxed integer geometry"); + check(s.empty && boxed->refcount==refs,"boxed coordinate borrowed on clipped success"); + val_decref(args);val_decref(boxed);clear_error(); + return make_bool(1); +} +int main(void) { + EigsState *st = eigs_open(); if (!st) return 1; + Value *probe = make_builtin(phase2_reader_probe); + eigs_set_global("phase2_reader_probe", probe); + val_decref(probe); + Value *result = eigs_eval_string("try:\n phase2_reader_probe of null\ncatch e:\n false\n"); + if (!result || result->type != VAL_BOOL || !result->data.boolean || g_has_error) { + puts("phase2-q7-test: FAIL VM probe execution"); + failures++; + } + val_decref(result); + eigs_close(st); + printf("phase2-q7-test: %s checks=%d failures=%d\n",failures?"FAIL":"PASS",checks,failures); + return failures?1:0; +} diff --git a/tests/phase2/r17_float_text.json b/tests/phase2/r17_float_text.json new file mode 100644 index 000000000..a8f0052d9 --- /dev/null +++ b/tests/phase2/r17_float_text.json @@ -0,0 +1,4257 @@ +{ + "schema": 2, + "ruling": "R1.7-D6", + "base": "b6f9db3879be5578de787ad6cf0d235b809f0e7c", + "profiles": [ + "release", + "server", + "server-db", + "zlib" + ], + "population": { + "tests/phase2/builtins/builtins/acos.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/add.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/asin.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/atan.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/audio_gain.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/audio_mix.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_copy.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_deinterleave.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_dot.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_fill.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_from_list.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_from_pcm16le.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_get.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_mix.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_peak.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_resample_linear.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_scale_range.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_set.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/buf_to_pcm16le.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/copy_into.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/cos.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/divide.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/dot.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/exp.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/f64_from_bytes.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/fround.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/gather.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/get_at.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/join.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/json_build.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/json_decode.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/json_encode.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/leaky_relu.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/log.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/matmul.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/matmul_at.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/matmul_bt.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/max.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/mean.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/min.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/norm.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/num.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/numerical_grad.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/numerical_grad_cols.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/numerical_grad_rows.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/observe.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/pow.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/ppu_render_frame.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/print.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/read_bytes_buf.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/relu.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/scatter_add.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/set_at.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/sgd_update.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/sgd_update_cols.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/sgd_update_rows.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/sin.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/sqrt.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/str.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/stream_write.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/tan.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/tensor_load.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/tensor_save.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/text_builder_append.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/write.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/zeros.branch": [ + "release", + "server", + "server-db", + "zlib" + ], + "tests/phase2/builtins/builtins/zeros_like.branch": [ + "release", + "server", + "server-db", + "zlib" + ] + }, + "population_sha256": "b4228b248a44d778106ee7874651c80a29df743f81cc869dd0839587e645644e", + "kind_domain": "Numeric payload before printing/encoding; source-bound text witnesses preserve numeric-looking input strings byte for byte. rc=0 is a fixed integer trailer. Never infer kind from old spelling.", + "files": { + "tests/phase2/builtins/builtins/acos.branch": { + "old_sha256": "569dbfeb45288a7fdc73dbe3968e33077ead4fb60b286edb0452706e86a46fa7", + "source_sha256": "d50a0a98e2fbbce230f62aac651bbe8384d0d12b50218804c314e0af1c237fbf", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/add.branch": { + "old_sha256": "44cb4a950acc6a46b3995372f1d7cfac1b391606d08bbb23791b59d3c0139441", + "source_sha256": "cc15f8196d96099e8d63563cbddeabf68e22c67cc4f27d4e04299e652a62514f", + "witnesses": [ + { + "line": 8, + "expression": "r", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The exact integer scalar result remains in r." + }, + { + "line": 15, + "expression": "s[0]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Buffer result stores float cells; read the printed s[0]." + }, + { + "line": 17, + "expression": "l[0]", + "kind": "int", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Original list scalar result remains accessible." + }, + { + "line": 17, + "expression": "l[1]", + "kind": "int", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Original list scalar result remains accessible." + } + ] + }, + "tests/phase2/builtins/builtins/asin.branch": { + "old_sha256": "569dbfeb45288a7fdc73dbe3968e33077ead4fb60b286edb0452706e86a46fa7", + "source_sha256": "dd02a681476467ec2fc842b450f4312187de86be8d43b4e905dffe2fac93533e", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/atan.branch": { + "old_sha256": "3bc01a9ceb8c27725c8c651e2da1fa3c8bcc04c2540218f88960a2792deaf053", + "source_sha256": "1036030adfdf440f3f629445c7bc4071f558a53f6abccae57ebe2819c1913335", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/audio_gain.branch": { + "old_sha256": "1f1d88cd0050f69c989b3be281ea0fcf212b6ffa36cb26dde89ddeb6528dd83b", + "source_sha256": "c39f7bd8f30d77d96f9e74b0ba05ace39c5e83ccff8ee09a4c42081d46b51503", + "witnesses": [ + { + "line": 8, + "expression": "g", + "index_path": [ + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained result leaf after the marker; each printed sample gets an independent kind check." + }, + { + "line": 8, + "expression": "g", + "index_path": [ + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained result leaf after the marker; each printed sample gets an independent kind check." + }, + { + "line": 8, + "expression": "g", + "index_path": [ + 2 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained result leaf after the marker; each printed sample gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/audio_mix.branch": { + "old_sha256": "94f7af8ec23efc86083e896fd86961d344d63d8c4c4ee2c8f1a52e6d1d77f883", + "source_sha256": "ac8b550d4d6c26a040575c58929ab88be0f5383993a95c8a3b34a9f5215c54c5", + "witnesses": [ + { + "line": 8, + "expression": "m", + "index_path": [ + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained result leaf after the marker; each printed sample gets an independent kind check." + }, + { + "line": 8, + "expression": "m", + "index_path": [ + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained result leaf after the marker; each printed sample gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/buf_copy.branch": { + "old_sha256": "dac04f02f3803f898bf8d1f64a1e4e489aa151a1a2a1913d397277097f379c71", + "source_sha256": "c5b84980049af61a9aaea744b3b3fd8de21224ef939ec168bb87161ad54bc938", + "witnesses": [ + { + "line": 10, + "expression": "d[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 4, + 1 + ] + ], + "provenance": "d is a buffer throughout; later copies change cell values, never their float numeric kind." + }, + { + "line": 10, + "expression": "d[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "d is a buffer throughout; later copies change cell values, never their float numeric kind." + }, + { + "line": 10, + "expression": "d[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ], + [ + 4, + 2 + ] + ], + "provenance": "d is a buffer throughout; later copies change cell values, never their float numeric kind." + }, + { + "line": 22, + "expression": "d[3]", + "kind": "float", + "positions": [ + [ + 4, + 3 + ] + ], + "provenance": "d is a buffer throughout; later copies change cell values, never their float numeric kind." + } + ] + }, + "tests/phase2/builtins/builtins/buf_deinterleave.branch": { + "old_sha256": "dd951eaad4bbf3c157f5eec675129e0f2d26e33b71b78740c0e8a8e8dcc18f2a", + "source_sha256": "6585d6f7df55b57528dc84d8c0b8b5397228623116774fb7d7d052c30e4da8ce", + "witnesses": [ + { + "line": 9, + "expression": "buf_len of r", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "buf_len returns an integer count." + }, + { + "line": 9, + "expression": "r[0]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 9, + "expression": "r[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 19, + "expression": "f[0]", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 19, + "expression": "f[1]", + "kind": "float", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 19, + "expression": "f[2]", + "kind": "float", + "positions": [ + [ + 4, + 3 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/buf_dot.branch": { + "old_sha256": "c1ecf705f32d6094a9a033c649c8b05c47d09447bc3cd04f5d3f70bdd2181ba0", + "source_sha256": "90e2d0479003cc49b12a3a73be84bb991fa28e063ba9370da496e3f8abe2c56d", + "witnesses": [ + { + "line": 10, + "expression": "d", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 15, + "expression": "buf_dot of [a, b, 1.0, 1.0, 2.0]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Reevaluate the same pure buffer dot call after the marker; a and b were not mutated." + } + ] + }, + "tests/phase2/builtins/builtins/buf_fill.branch": { + "old_sha256": "8c479bf8e435ada89ad51e52521b6e34eb59cda2cf71b4b8188747dc4628428d", + "source_sha256": "4b63a7b983759d7d8b85dedceff46569c04c0088c7453d811455012ad5926b47", + "witnesses": [ + { + "line": 9, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 4, + 1 + ] + ], + "provenance": "b is always a buffer; later fills preserve each cell's float numeric kind." + }, + { + "line": 9, + "expression": "b[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "b is always a buffer; later fills preserve each cell's float numeric kind." + }, + { + "line": 9, + "expression": "b[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "b is always a buffer; later fills preserve each cell's float numeric kind." + }, + { + "line": 9, + "expression": "b[3]", + "kind": "float", + "positions": [ + [ + 1, + 4 + ], + [ + 4, + 2 + ] + ], + "provenance": "b is always a buffer; later fills preserve each cell's float numeric kind." + } + ] + }, + "tests/phase2/builtins/builtins/buf_from_list.branch": { + "old_sha256": "351878b86efd9ca2e8211a62b334ded544593dbf2162176a2f47c9589c255d9f", + "source_sha256": "d7fe7ccfbb42a1a7a563c0cb894d0abce9ebbb64813f2b0f1bec8e331a14b9e9", + "witnesses": [ + { + "line": 8, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "buf_from_list converts every input to float storage; read the original cell." + }, + { + "line": 8, + "expression": "b[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "buf_from_list converts every input to float storage; read the original cell." + }, + { + "line": 8, + "expression": "b[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "buf_from_list converts every input to float storage; read the original cell." + } + ] + }, + "tests/phase2/builtins/builtins/buf_from_pcm16le.branch": { + "old_sha256": "62cc915f80add810815cf56816c78ed4ce2ea6ac7226d78a0dc32c75c7c88d01", + "source_sha256": "7924d636a16bb793bc25382cd7b98967816439e66fae08eb02cb51c6f5aca556", + "witnesses": [ + { + "line": 14, + "expression": "\"byte-300\"", + "kind": "text", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Immutable source string literal names the invalid input. Its numeric-looking token is text, so preserve it byte for byte; type of the source expression must be string.", + "reader": "type" + }, + { + "line": 24, + "expression": "u[0]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 27, + "expression": "s[0]", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 27, + "expression": "s[1]", + "kind": "float", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/buf_get.branch": { + "old_sha256": "1d01ac9d51aceadaf2c618d631ff744e61c90338281246f409610361a1329b70", + "source_sha256": "74894382fd9beeed66da5bc76199242a5b0a0a17f951687d154b7896b737f00e", + "witnesses": [ + { + "line": 9, + "expression": "g", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 15, + "expression": "h", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/buf_mix.branch": { + "old_sha256": "5b90b6a82b8f23b9d4fc822439292a10d8298636f6bf0e7d06cd0ce8b2da8561", + "source_sha256": "3fb8175de9e7d360051a7e3b2762b514303392a15fd01c4972d34843807d953f", + "witnesses": [ + { + "line": 10, + "expression": "d[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "d is always a buffer; subsequent mixing preserves float numeric kinds." + }, + { + "line": 10, + "expression": "d[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "d is always a buffer; subsequent mixing preserves float numeric kinds." + } + ] + }, + "tests/phase2/builtins/builtins/buf_peak.branch": { + "old_sha256": "fb99580cbc37068854d652493bb0543547dd3e4c4db541bbbfc26471ca4a1c14", + "source_sha256": "8b06cd047d040c08c130b32d9b3b02f8ba8dffe262dafda3ecbc4355ddb094c2", + "witnesses": [ + { + "line": 9, + "expression": "p", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 14, + "expression": "buf_peak of [b, 2.0, 1.0]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Reevaluate the identical pure peak call; b is unchanged." + } + ] + }, + "tests/phase2/builtins/builtins/buf_resample_linear.branch": { + "old_sha256": "e4a5237c04513a69773ede55bf480984d7e62e3ee8ad53facc3fcffbed902d14", + "source_sha256": "018060c9ec08097ee3543963cdb88985fe62f52589bb31672786fad2a1ad210f", + "witnesses": [ + { + "line": 9, + "expression": "r[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 9, + "expression": "r[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 9, + "expression": "r[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 14, + "expression": "buf_len of (buf_resample_linear of [s, 3.0])", + "kind": "int", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Reevaluate the identical pure resample/count call; the result is an integer length." + } + ] + }, + "tests/phase2/builtins/builtins/buf_scale_range.branch": { + "old_sha256": "c513bc3bdb60883eba661b853a331456e9518a93ab0759d6f82bdf3235a0db1a", + "source_sha256": "6875495bdd87c5adfe2f703e5cc8db0b6f9c14c25347906e81fd0383e1e98d9f", + "witnesses": [ + { + "line": 9, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "b remains a buffer; later scaling changes values while each cell remains float." + }, + { + "line": 9, + "expression": "b[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "b remains a buffer; later scaling changes values while each cell remains float." + }, + { + "line": 9, + "expression": "b[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "b remains a buffer; later scaling changes values while each cell remains float." + } + ] + }, + "tests/phase2/builtins/builtins/buf_set.branch": { + "old_sha256": "82df09c6853981b39cf330a7dd33f9295d0faa0ad7bf75abe24509c9d051f86a", + "source_sha256": "bde8d975bfd9490a9de7475a2184323406f0557c6bd2dffdc93fbff133996793", + "witnesses": [ + { + "line": 9, + "expression": "b[1]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 16, + "expression": "b[2]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/buf_to_pcm16le.branch": { + "old_sha256": "eecad0ac215863e0085e43178799f26fcb8c0b1780113a3bd7277ce154ad495a", + "source_sha256": "96e73c0eec4fccfa6c228e2ea1cd6d624c07619080c99dc2988d6728f936a5a2", + "witnesses": [ + { + "line": 9, + "expression": "r[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + }, + { + "line": 9, + "expression": "r[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + }, + { + "line": 9, + "expression": "r[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + }, + { + "line": 9, + "expression": "r[3]", + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + }, + { + "line": 9, + "expression": "r[4]", + "kind": "float", + "positions": [ + [ + 1, + 5 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + }, + { + "line": 9, + "expression": "r[5]", + "kind": "float", + "positions": [ + [ + 1, + 6 + ] + ], + "provenance": "The returned PCM byte buffer uses float storage, as the frozen probe explicitly witnesses with nt of r[0]." + } + ] + }, + "tests/phase2/builtins/builtins/copy_into.branch": { + "old_sha256": "83e31940d21f04bb371335e5d1f74382c08e09f70b5a5cdb1295a9e3a669ebd3", + "source_sha256": "8441abf4a97bd4f8a550893d08b305734e0266814863063d9e3b9537f6020344", + "witnesses": [ + { + "line": 7, + "expression": "0", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The initial list zero is an int source literal; it was later overwritten with another int." + }, + { + "line": 8, + "expression": "7", + "kind": "int", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "The source list copies integer literal 7 unchanged." + }, + { + "line": 8, + "expression": "8", + "kind": "int", + "positions": [ + [ + 1, + 3 + ], + [ + 3, + 3 + ] + ], + "provenance": "The source list copies integer literal 8 unchanged." + }, + { + "line": 15, + "expression": "5", + "kind": "int", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "The later list copy writes integer literal 5." + }, + { + "line": 19, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/cos.branch": { + "old_sha256": "dd0c0693f01f952a0f3e40928fd06dfdb51814ae805433c1d0a828ee411fd7e6", + "source_sha256": "371fbcf49fcd0fc61b6f90d670f0aabe7d2c8e1dd1d7d96577211281a24123f3", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/divide.branch": { + "old_sha256": "c86138c24b149017797aea5e0cc1713267adffdb227658ccd12f9d7f14ea27b2", + "source_sha256": "ec0510af18a82cf234626f896d5b944147529a7bdbe17f92e9b1ddb3d23a65ed", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/dot.branch": { + "old_sha256": "2bae8e58272d6a882be4cf8cfbfe6eb01b005f76710ee6b3a7c3787b2f0e5318", + "source_sha256": "1ddf07b64a5e4789b99bab885c6167d50cb570d5fcbd4d743b76421d39dc4498", + "witnesses": [ + { + "line": 8, + "expression": "d", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/exp.branch": { + "old_sha256": "28fa610a97dead760b26a1bec1205054a98da65898ce7131bbc786890d78f924", + "source_sha256": "352095768e8589397a64f0cbf9f14d421f0c8d324288a06528de72d9133a14df", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/f64_from_bytes.branch": { + "old_sha256": "e5600eea405b87eb0819d55f44082cd60a7f8126c15bb1d0253991e7a00681bf", + "source_sha256": "c1418ff7dfa0d48b0bd7f8bbb8ab409c4cd109ebad0b2ac5702a08a45d9174c1", + "witnesses": [ + { + "line": 8, + "expression": "v", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 18, + "expression": "w", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 16, + "expression": "\"byte-319\"", + "kind": "text", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Immutable source string literal names the invalid input. Its numeric-looking token is text, so preserve it byte for byte; type of the source expression must be string.", + "reader": "type" + } + ] + }, + "tests/phase2/builtins/builtins/fround.branch": { + "old_sha256": "12ca1750befb2a38111ec77d9ff236e9dada8e5c7d297aa5cf4f489c2c1f22fa", + "source_sha256": "8195efefa3df62764addec397a097371ca255236efed4149b395116ecdfeeb1f", + "witnesses": [ + { + "line": 3, + "expression": "fround of 0.1", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 8, + "expression": "fround of 1e39", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ] + }, + { + "line": 13, + "expression": "fround of -1e39", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ] + }, + { + "line": 18, + "expression": "fround of 16777217", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ] + }, + { + "line": 23, + "expression": "fround of 9007199254740993", + "kind": "float", + "positions": [ + [ + 5, + 1 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/gather.branch": { + "old_sha256": "2d0ec782b3eaca9a4f0308177593be3c25d34c27ed42e54c8f4878784a503f50", + "source_sha256": "5c4ce04f61e1181c3a2f30f7cab7ff11fe1d0f148cf11d7e43a16c4f7ec9fdb7", + "witnesses": [ + { + "line": 18, + "expression": "h[0]", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 18, + "expression": "h[1]", + "kind": "float", + "positions": [ + [ + 2, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 21, + "expression": "k", + "index_path": [ + 0 + ], + "kind": "int", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Read this exact retained list-gather result leaf; each printed int cell gets an independent kind check." + }, + { + "line": 21, + "expression": "k", + "index_path": [ + 1 + ], + "kind": "int", + "positions": [ + [ + 3, + 2 + ] + ], + "provenance": "Read this exact retained list-gather result leaf; each printed int cell gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/get_at.branch": { + "old_sha256": "0d8f23ecef670cb4b94c02e5131773d4ca520dd9aaa0528969f693bd0cb75e03", + "source_sha256": "cc90608c75801ed3c366eecdf48e5d449485175b6b03116769918cb49a09b869", + "witnesses": [ + { + "line": 8, + "expression": "get_at of [xs, 1]", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Pure read from the unchanged integer list." + }, + { + "line": 13, + "expression": "get_at of [xs, 1.0]", + "kind": "int", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Pure read from the unchanged integer list; integral-float index does not alter value kind." + }, + { + "line": 16, + "expression": "g", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 18, + "expression": "get_at of [m, 1, 0]", + "kind": "int", + "positions": [ + [ + 5, + 1 + ] + ], + "provenance": "Pure read from the unchanged integer matrix." + } + ] + }, + "tests/phase2/builtins/builtins/join.branch": { + "old_sha256": "91e4b9fc32e6f0aef9e817248f48398942b53c92d8a07dec3074740ac757c319", + "source_sha256": "88f2e3278f06a6310c376e78a74cf57b010c08c32673d68778ca4560176d6bfc", + "witnesses": [ + { + "line": 7, + "expression": "1", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The source literal is the numeric payload formatted into join's output string; reevaluating the literal has no effects." + }, + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "The source literal is the numeric payload formatted into join's output string; reevaluating the literal has no effects." + }, + { + "line": 7, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "The source literal is the numeric payload formatted into join's output string; reevaluating the literal has no effects." + }, + { + "line": 7, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "The source literal is the numeric payload formatted into join's output string; reevaluating the literal has no effects." + } + ] + }, + "tests/phase2/builtins/builtins/json_build.branch": { + "old_sha256": "b263c9dc708a82505e5afef5b3dabcb2601699e66deb9d8205e866bb041b4308", + "source_sha256": "079f944da77214d3a2b54e207ecc6f9db03870ea5818adc82d11c40f2e26f5ed", + "witnesses": [ + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 7, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ] + }, + { + "line": 7, + "expression": "-9223372036854775807 - 1", + "kind": "int", + "positions": [ + [ + 1, + 3 + ] + ] + }, + { + "line": 7, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/json_decode.branch": { + "old_sha256": "b0c99b544ec22490674d987f0d5de7cef9872b11a8e39e2be368ef74874d1ea1", + "source_sha256": "4c2d193b22e2440a66f872b375d94a5a12bb67d78c8123c963a2cf1d6534bf79", + "witnesses": [ + { + "line": 8, + "expression": "v[0]", + "kind": "int", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[1]", + "kind": "float", + "positions": [ + [ + 2, + 2 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[2]", + "kind": "int", + "positions": [ + [ + 1, + 1 + ], + [ + 2, + 3 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[3]", + "kind": "float", + "positions": [ + [ + 2, + 4 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[4]", + "kind": "int", + "positions": [ + [ + 1, + 2 + ], + [ + 2, + 5 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[5]", + "kind": "int", + "positions": [ + [ + 2, + 6 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + }, + { + "line": 8, + "expression": "v[6]", + "kind": "float", + "positions": [ + [ + 2, + 7 + ] + ], + "provenance": "Read the original decoded scalar; the decoder retains integer/float kinds determined by JSON lexical form and int64 range." + } + ] + }, + "tests/phase2/builtins/builtins/json_encode.branch": { + "old_sha256": "81bc7821029d6e28236e73ce74331fa3155c7cd5188589e29ecb7c282816c3a9", + "source_sha256": "57ad4bb2bb19eb81781f03e55156d514859df43d4ef15c1f6573b64cc23c90ae", + "witnesses": [ + { + "line": 7, + "expression": "3", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 2 + ], + [ + 1, + 7 + ] + ] + }, + { + "line": 7, + "expression": "9223372036854775807", + "kind": "int", + "positions": [ + [ + 1, + 3 + ] + ] + }, + { + "line": 7, + "expression": "-9223372036854775807 - 1", + "kind": "int", + "positions": [ + [ + 1, + 4 + ] + ] + }, + { + "line": 7, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 5 + ] + ] + }, + { + "line": 7, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 6 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/leaky_relu.branch": { + "old_sha256": "0bc1fd4db8d4a538c959960a5a577e5242a7b0bbb7f9c985d30d3721b19cb281", + "source_sha256": "3919d59d22ef87d757cee26bc7b813650db6be68524223b550caca1e2dfe8a09", + "witnesses": [ + { + "line": 8, + "expression": "r[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 8, + "expression": "r[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/log.branch": { + "old_sha256": "2d22f33b55ea4de06d1a0b8b46253953e26465dd16dcda4e8f1eb5646801f375", + "source_sha256": "7d2cc7a3d6484ab5d99ddb8ee207468db47c2ade5bf166c8a1a5bab7e4428c17", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/matmul.branch": { + "old_sha256": "f1cb59029c7fad2bf37a37f4389a2076ce102a775d4f575a661b595c3345058f", + "source_sha256": "9391c3abf201eb80be85b1796d478e4e924a6fbb02009fb74bb8a437e76502e8", + "witnesses": [ + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/matmul_at.branch": { + "old_sha256": "d6c713a4570fedbd238455087008936022d2c56dc2620685634baa6494fb986a", + "source_sha256": "26b7bf0e171cb8cfa7d8780f715f8e7a3e8161ccff1542fe979b1ede70ce216c", + "witnesses": [ + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/matmul_bt.branch": { + "old_sha256": "3de3670fc9b3b36b00949e2965d35f9e581f4ea7616249aa884dc2ef414318a6", + "source_sha256": "c757d9ca816f613245f83c9c2bc2dcc909aca6cb08a1c2ab8c9e40f07490d717", + "witnesses": [ + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained matrix-product result leaf; each printed cell gets an independent kind check." + } + ] + }, + "tests/phase2/builtins/builtins/max.branch": { + "old_sha256": "540ee7840bf80662918b4d4189add017778d08c503488529c6fec539d0e6bb8a", + "source_sha256": "8c551f0aa27fdf3c9408c88f65fa5c2dde3209f5df1fc9d12d4c09319a119002", + "witnesses": [ + { + "line": 7, + "expression": "max of [9007199254740993, 9007199254740992]", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 9, + "expression": "max of [3, 2.5]", + "kind": "int", + "positions": [ + [ + 2, + 1 + ] + ] + }, + { + "line": 11, + "expression": "max of [2, 2.5]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ] + }, + { + "line": 13, + "expression": "max of 7", + "kind": "int", + "positions": [ + [ + 4, + 1 + ] + ] + }, + { + "line": 15, + "expression": "max of [3, 3.0]", + "kind": "int", + "positions": [ + [ + 5, + 1 + ] + ] + }, + { + "line": 15, + "expression": "max of [3.0, 3]", + "kind": "float", + "positions": [ + [ + 5, + 2 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/mean.branch": { + "old_sha256": "957ab0974154733d37d6fbc3004e82e924761e9c64cae7126984329110a0743b", + "source_sha256": "5cefaf4912e7cbc795a09e20d0edb0576419b6eeea0b89d10435a9cf2711dbd6", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/min.branch": { + "old_sha256": "f45bde7acd9b053b3379e15b35c9e06f2ba11243437f5a5926b62aad588d2118", + "source_sha256": "e4e3051cdc28551753d2a29e4e6667a0e8e63b4cd9b4d072e48226fd8b244b9f", + "witnesses": [ + { + "line": 7, + "expression": "min of [9007199254740993, 9007199254740994]", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 9, + "expression": "min of [3, 3.5]", + "kind": "int", + "positions": [ + [ + 2, + 1 + ] + ] + }, + { + "line": 11, + "expression": "min of [4, 3.5]", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ] + }, + { + "line": 13, + "expression": "min of 7", + "kind": "int", + "positions": [ + [ + 4, + 1 + ] + ] + }, + { + "line": 15, + "expression": "min of [3, 3.0]", + "kind": "int", + "positions": [ + [ + 5, + 1 + ] + ] + }, + { + "line": 15, + "expression": "min of [3.0, 3]", + "kind": "float", + "positions": [ + [ + 5, + 2 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/norm.branch": { + "old_sha256": "5c2f187cf2973b5b1a305d592804123f3574578e251042668adb284090d811a1", + "source_sha256": "2932993a0b0ec38b07f0bf641aca8ec85829e6dcc159b5a3bf9a72262ae1141c", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/num.branch": { + "old_sha256": "5bd7970cd996eb2a437018da3aa033c297f38aa4a877af74f8ad4221ecee0660", + "source_sha256": "52194b92bf0af47121356671a2d608c4adfbf32664d21028641a6fb0e196ed9c", + "witnesses": [ + { + "line": 13, + "expression": "\"3\"", + "kind": "text", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"3\"", + "kind": "int", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"3.0\"", + "kind": "text", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"3.0\"", + "kind": "float", + "positions": [ + [ + 2, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"9007199254740993\"", + "kind": "text", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"9007199254740993\"", + "kind": "int", + "positions": [ + [ + 3, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"9223372036854775808\"", + "kind": "text", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"9223372036854775808\"", + "kind": "float", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\" 12 \"", + "kind": "text", + "positions": [ + [ + 5, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\" 12 \"", + "kind": "int", + "positions": [ + [ + 5, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"+5\"", + "kind": "text", + "positions": [ + [ + 6, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"+5\"", + "kind": "int", + "positions": [ + [ + 6, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"-0x10\"", + "kind": "int", + "positions": [ + [ + 7, + 1 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"0x1F\"", + "kind": "int", + "positions": [ + [ + 8, + 1 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"0X1f\"", + "kind": "int", + "positions": [ + [ + 9, + 1 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"0xFFFFFFFFFFFFFFFF\"", + "kind": "int", + "positions": [ + [ + 10, + 1 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\".5\"", + "kind": "text", + "positions": [ + [ + 11, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\".5\"", + "kind": "float", + "positions": [ + [ + 11, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"1.\"", + "kind": "float", + "positions": [ + [ + 12, + 1 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"1e5\"", + "kind": "text", + "positions": [ + [ + 13, + 1 + ] + ], + "provenance": "Echoed input is the literal string s. type of this same string must be string; its numeric-looking bytes must remain byte-identical.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"1e5\"", + "kind": "float", + "positions": [ + [ + 13, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; this duplicates only the pure conversion, avoiding p's scoped local v.", + "transform": "num" + }, + { + "line": 13, + "expression": "\"-9223372036854775808\"", + "kind": "text", + "positions": [ + [ + 25, + 1 + ] + ], + "provenance": "Echoed numeric-looking string is immutable text.", + "reader": "type" + }, + { + "line": 13, + "expression": "\"-9223372036854775808\"", + "kind": "int", + "positions": [ + [ + 25, + 2 + ] + ], + "provenance": "Apply num to the exact source string input; it returns the exact int64 minimum.", + "transform": "num" + }, + { + "line": 16, + "expression": "n", + "kind": "int", + "positions": [ + [ + 26, + 1 + ] + ], + "provenance": "The original conversion result remains in module binding n." + }, + { + "line": 18, + "expression": "f", + "kind": "float", + "positions": [ + [ + 27, + 1 + ] + ], + "provenance": "The original conversion result remains in module binding f." + } + ] + }, + "tests/phase2/builtins/builtins/numerical_grad.branch": { + "old_sha256": "b6d80c99de9aebf09340531468149f1bba9b5d4283725ff6ab78610fc9e21a53", + "source_sha256": "b0138b8120cd306c55a49e648f1538650f59a42a191edc3f79a7a752aea814c7", + "witnesses": [ + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + } + ] + }, + "tests/phase2/builtins/builtins/numerical_grad_cols.branch": { + "old_sha256": "e1a11fe248d17b6348dcaf4bfc7b4d6cb62d18255ffbadd96d57e6c53491596a", + "source_sha256": "5fd0e6755d72987cff2efbe8c5e5cfae3c418cd41df14c0e2ab6b6ba05b08fdc", + "witnesses": [ + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 16, + "expression": "numerical_grad_cols of [loss, q, [0.0, 1.0], 0.5]", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_cols of [loss, q, [0.0, 1.0], 0.5]", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 2 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_cols of [loss, q, [0.0, 1.0], 0.5]", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 3 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_cols of [loss, q, [0.0, 1.0], 0.5]", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 4 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + } + ] + }, + "tests/phase2/builtins/builtins/numerical_grad_rows.branch": { + "old_sha256": "2b81582aa2f984d195ccdae96c505598820bfd38931ecbd81c9d376a16c74691", + "source_sha256": "aac32d799947b0fcae207b17dec48614b622c71ad7e4b3fd2a55e5b1d161e788", + "witnesses": [ + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 11, + "expression": "g", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained finite-difference gradient leaf after the marker." + }, + { + "line": 16, + "expression": "numerical_grad_rows of [loss, q, [1.0, 0.0], 0.5]", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_rows of [loss, q, [1.0, 0.0], 0.5]", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 2 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_rows of [loss, q, [1.0, 0.0], 0.5]", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 3 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + }, + { + "line": 16, + "expression": "numerical_grad_rows of [loss, q, [1.0, 0.0], 0.5]", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 4 + ] + ], + "provenance": "Reevaluate the exact source finite-difference call and read this output leaf. The kernel restores perturbed q cells, prints nothing, and performs no I/O; no scoped local is required." + } + ] + }, + "tests/phase2/builtins/builtins/observe.branch": { + "old_sha256": "5e443e730314fd12810eab1f7403adb88dc7d14a60b7c7be3b801da6d9555c59", + "source_sha256": "71cc7d272af662bc7694768b0a8ed16f8a30fd06bf0b9fff6ca99b8e9d388e85", + "witnesses": [ + { + "line": 9, + "expression": "r[1]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The original observe result readout remains accessible; no observer read is rerun." + }, + { + "line": 9, + "expression": "r[2]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "The original observe result readout remains accessible; no observer read is rerun." + }, + { + "line": 9, + "expression": "r[3]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "The original observe result readout remains accessible; no observer read is rerun." + } + ] + }, + "tests/phase2/builtins/builtins/pow.branch": { + "old_sha256": "9f38b23f8ce783c1d4a5342c371df39fcbe9c247676b4b395c591138e964dd2c", + "source_sha256": "a8b832fa09a2679eec35184a551036c4627f6236d7fad4b5537d8dc6f5eb954b", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/ppu_render_frame.branch": { + "old_sha256": "4c5783197fa7af691cb64ab41271f11a1fbe9ecda8e6f11ad6868f2e867a5a78", + "source_sha256": "7864916dbbdaee01210c1ebd894a3c33eb93354c823fb820ca3fb3d31280aac6", + "witnesses": [ + { + "line": 11, + "expression": "fb[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 4, + 1 + ] + ], + "provenance": "fb is always a float buffer; later rendering changes values, never the float kind." + }, + { + "line": 27, + "expression": "fb[23039]", + "kind": "float", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 24, + "expression": "\"lcdc-401\"", + "kind": "text", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Immutable source string literal names the invalid input. Its numeric-looking token is text, so preserve it byte for byte; type of the source expression must be string.", + "reader": "type" + } + ] + }, + "tests/phase2/builtins/builtins/print.branch": { + "old_sha256": "c1bb91c32873e25fec016b1c8a33a9dd8626601a39518971a9bcfe71cd71351c", + "source_sha256": "850240e1bcf99689a236882af26025615203f90067b935385a70dea81bf97d8f", + "witnesses": [ + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 1 + ], + [ + 4, + 1 + ] + ], + "provenance": "Both printed occurrences are the same exact integer source literal." + }, + { + "line": 8, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original printed numeric payload is this source float literal." + }, + { + "line": 9, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Original printed numeric payload is this source float literal." + }, + { + "line": 10, + "expression": "1e300", + "kind": "float", + "positions": [ + [ + 4, + 2 + ] + ], + "provenance": "Original printed numeric payload is this source float literal." + } + ] + }, + "tests/phase2/builtins/builtins/read_bytes_buf.branch": { + "old_sha256": "92cd179b023bfae64f3ac77dc15312982db17e17da8415916797aaa0b48d6abf", + "source_sha256": "f315f8491954fadb590b6e8c523e68d9188a95bd2035991ad64af4a95d8cbe6d", + "witnesses": [ + { + "line": 11, + "expression": "buf_len of b", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original byte-buffer length is an integer count; EIGS_PROBE_TMP must name the per-run temporary fixture directory." + }, + { + "line": 11, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 18, + "expression": "buf_len of d", + "kind": "int", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Original byte-buffer length is an integer count." + } + ] + }, + "tests/phase2/builtins/builtins/relu.branch": { + "old_sha256": "2eb77d7f928006355c24388638f58ef6731548e2786028e8addb9bdf79c7e86f", + "source_sha256": "89ebd3e9db35aac2b4226e5d2096bb7e5712efe11d85bd46b8d023c895928f17", + "witnesses": [ + { + "line": 8, + "expression": "r[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 8, + "expression": "r[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 10, + "expression": "s", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/scatter_add.branch": { + "old_sha256": "33667024c1bd1f4c53e0e69beeb861d1216a69d8e15f7baa564e09b02eb8b945", + "source_sha256": "7ac0197139235dd9278418fc3ff0391a58fb3b33d9059f23c89566795c4d4f2c", + "witnesses": [ + { + "line": 9, + "expression": "d[0]", + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "d is always a float buffer; later accumulation preserves numeric kinds." + }, + { + "line": 9, + "expression": "d[1]", + "kind": "float", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "d is always a float buffer; later accumulation preserves numeric kinds." + }, + { + "line": 9, + "expression": "d[2]", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "d is always a float buffer; later accumulation preserves numeric kinds." + } + ] + }, + "tests/phase2/builtins/builtins/set_at.branch": { + "old_sha256": "220750d891ed80228512827027ba0cbc25c44ee8f1faefe1d0f0726b78138842", + "source_sha256": "7e598419b13989f17b453bfaab59db90804f6fe9bf2b92a7a0e3acd49016df5e", + "witnesses": [ + { + "line": 7, + "expression": "10", + "kind": "int", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "Integer source literal copied or retained in the list; later writes preserve the separately witnessed source integer kind." + }, + { + "line": 8, + "expression": "99", + "kind": "int", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "Integer source literal copied or retained in the list; later writes preserve the separately witnessed source integer kind." + }, + { + "line": 7, + "expression": "30", + "kind": "int", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Integer source literal copied or retained in the list; later writes preserve the separately witnessed source integer kind." + }, + { + "line": 15, + "expression": "5", + "kind": "int", + "positions": [ + [ + 3, + 3 + ] + ], + "provenance": "Integer source literal copied or retained in the list; later writes preserve the separately witnessed source integer kind." + }, + { + "line": 19, + "expression": "b[0]", + "kind": "float", + "positions": [ + [ + 4, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/sgd_update.branch": { + "old_sha256": "f02d7a678590875247424cf99d02cf8c15c9402c1be559db90566aec0395c1f5", + "source_sha256": "3036dfed6fdb67f969596d88709c5b9dd93aaf1f7247dfd3f9e6e27b2b0efa4e", + "witnesses": [ + { + "line": 8, + "expression": "r", + "index_path": [ + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained SGD result leaf after the marker." + }, + { + "line": 8, + "expression": "r", + "index_path": [ + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained SGD result leaf after the marker." + } + ] + }, + "tests/phase2/builtins/builtins/sgd_update_cols.branch": { + "old_sha256": "b3db713df38a6acc28f8e8996caf7201528f9ea4d314c28b0849959ac7179e7f", + "source_sha256": "e66955425b033ec9033ad9a6abf0ba2b18a6f26ba83327439dcc5e09d97ba649", + "witnesses": [ + { + "line": 10, + "expression": "m", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ], + [ + 3, + 3 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ], + [ + 3, + 4 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + } + ] + }, + "tests/phase2/builtins/builtins/sgd_update_rows.branch": { + "old_sha256": "823d398fec95c5ac6dcd1c1576ebbcb6420989a6a5b61bc8fedd696cc2e48cb5", + "source_sha256": "e53fc455967a9657fa51f1283b409685ab23b51e5dec0d332c0fad0e209cf295", + "witnesses": [ + { + "line": 10, + "expression": "m", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ], + [ + 3, + 1 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ], + [ + 3, + 2 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ], + [ + 3, + 3 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + }, + { + "line": 10, + "expression": "m", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ], + [ + 3, + 4 + ] + ], + "provenance": "Read this exact retained SGD matrix leaf. Later in-place updates change its value but the scalar kind remains independently witnessed after the marker; original/candidate value bits are checked separately." + } + ] + }, + "tests/phase2/builtins/builtins/sin.branch": { + "old_sha256": "3e39f8107440abe2750bf55634e12e4d1e4f47b971dbf84ad77e03afa0b18ec2", + "source_sha256": "1b7c5e1f0ffcc48aaa6fa0696d16e25ecad4ba4614ff9752a3ead1c8b8dabae1", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/sqrt.branch": { + "old_sha256": "3ccf9c32fa8d3cd6bf5fb1acd09297ad27ff5085362415eec99c863bc551c810", + "source_sha256": "519b5eb0aeb32605b60a169df5114e18d89ebe85e156e916862dbe807a699cb1", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/str.branch": { + "old_sha256": "a546d0eca3bf4900e12fe97f116b9881419c5c99efcda9e22adba6d6816f1b5d", + "source_sha256": "43810df34d1ad8464b7b7e8c9e78412878d94074c6faa2676d226c9f61811178", + "witnesses": [ + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ] + }, + { + "line": 7, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ] + }, + { + "line": 7, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ] + }, + { + "line": 7, + "expression": "-9223372036854775807 - 1", + "kind": "int", + "positions": [ + [ + 1, + 4 + ] + ] + } + ] + }, + "tests/phase2/builtins/builtins/stream_write.branch": { + "old_sha256": "0ba1d1ba909707a169442d449cdd4fc5dd1f1043a38adea49dbee22cc087fc7a", + "source_sha256": "fe3f6d4b99020167af724c5ed8497a08b3e42290152d966dd61f359eaac168bd", + "witnesses": [ + { + "line": 11, + "expression": "t[0]", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Read the original float decoded from the raw-f64 stream; the source int 3 became f64 storage before printing." + }, + { + "line": 11, + "expression": "t[1]", + "kind": "float", + "positions": [ + [ + 2, + 2 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + } + ] + }, + "tests/phase2/builtins/builtins/tan.branch": { + "old_sha256": "7d8dba4273be72ed366e4f8bcdf05715c466ebec943055ea56d723e8271ae95f", + "source_sha256": "3e40600bf9d8c9166350f2bd2fa850186a28108373a6838bdd5ad6dbba89c3fa", + "witnesses": [ + { + "line": 8, + "expression": "a", + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding a." + }, + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Original scalar result remains in module binding b." + } + ] + }, + "tests/phase2/builtins/builtins/tensor_load.branch": { + "old_sha256": "7f45bd23e6a6487be2111a9ab04b407205be16abe6d5654e852c9c3a23f1ba26", + "source_sha256": "1d2d20071e9e6c1b6eb0faf90ef5c6d486ed0f09dcdcb08f7b35bd786157ab71", + "witnesses": [ + { + "line": 11, + "expression": "t", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained loaded tensor leaf after the marker." + }, + { + "line": 11, + "expression": "t", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained loaded tensor leaf after the marker." + }, + { + "line": 11, + "expression": "t", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "Read this exact retained loaded tensor leaf after the marker." + }, + { + "line": 11, + "expression": "t", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 4 + ] + ], + "provenance": "Read this exact retained loaded tensor leaf after the marker." + }, + { + "line": 11, + "expression": "shape of t", + "index_path": [ + 0 + ], + "kind": "int", + "positions": [ + [ + 1, + 5 + ] + ], + "provenance": "Reevaluate the pure shape read and inspect this exact integer dimension count." + }, + { + "line": 11, + "expression": "shape of t", + "index_path": [ + 1 + ], + "kind": "int", + "positions": [ + [ + 1, + 6 + ] + ], + "provenance": "Reevaluate the pure shape read and inspect this exact integer dimension count." + } + ] + }, + "tests/phase2/builtins/builtins/tensor_save.branch": { + "old_sha256": "1b73968293611b206533cb27ee3b3066aa56e222b9a3fe2473880e2ec6cd5057", + "source_sha256": "c57ecd8feca7dc827290c5ed891ae841165a63e5d8446c23a75b6b3af63490ab", + "witnesses": [ + { + "line": 11, + "expression": "t", + "index_path": [ + 0 + ], + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Read this exact retained tensor-load leaf after the marker; no filesystem operation is repeated." + }, + { + "line": 11, + "expression": "t", + "index_path": [ + 1 + ], + "kind": "float", + "positions": [ + [ + 2, + 2 + ] + ], + "provenance": "Read this exact retained tensor-load leaf after the marker; no filesystem operation is repeated." + }, + { + "line": 11, + "expression": "t", + "index_path": [ + 2 + ], + "kind": "float", + "positions": [ + [ + 2, + 3 + ] + ], + "provenance": "Read this exact retained tensor-load leaf after the marker; no filesystem operation is repeated." + } + ] + }, + "tests/phase2/builtins/builtins/text_builder_append.branch": { + "old_sha256": "b312faffe01ba083116c315f35e3bc3fd990944496f95274d38f6ca84af2115d", + "source_sha256": "6aa3d98ab5d07f8ddcd19dc34696289a194977ffde8e9a2c01c00cdcf8e9ae6b", + "witnesses": [ + { + "line": 8, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + }, + { + "line": 10, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + }, + { + "line": 12, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + } + ] + }, + "tests/phase2/builtins/builtins/write.branch": { + "old_sha256": "a69d7121ddc382dd987c69d1cdff1572a48936c3d6c8494f30cd22eee34159c0", + "source_sha256": "a25b9b9d7ab2fa20b4a13b1c0250ca8a7f020fe290b2d94712b84b31ee0cf242", + "witnesses": [ + { + "line": 7, + "expression": "9007199254740993", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + }, + { + "line": 9, + "expression": "2.5", + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + }, + { + "line": 11, + "expression": "3.0", + "kind": "float", + "positions": [ + [ + 1, + 3 + ] + ], + "provenance": "The numeric source literal was formatted into text by this exact source call; evaluating only the literal has no effects." + } + ] + }, + "tests/phase2/builtins/builtins/zeros.branch": { + "old_sha256": "b44706dfefc4bdc12c69f66975b8ff7e59cda39b00b17a252c5b00228996bcd9", + "source_sha256": "cc0718a4bf38f3ae20cb9734919a5d3688d6c68afeabf9341ac78648ab080df2", + "witnesses": [ + { + "line": 8, + "expression": "buf_len of z", + "kind": "int", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Original buffer length is an integer count." + }, + { + "line": 19, + "expression": "buf_len of (zeros of 2.0)", + "kind": "int", + "positions": [ + [ + 5, + 1 + ] + ], + "provenance": "Reevaluate the pure source count expression; integral-float size does not change integer count kind." + }, + { + "line": 14, + "expression": "m", + "index_path": [ + 0, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 1 + ] + ], + "provenance": "Read this exact retained zero-matrix leaf after the marker." + }, + { + "line": 14, + "expression": "m", + "index_path": [ + 0, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 2 + ] + ], + "provenance": "Read this exact retained zero-matrix leaf after the marker." + }, + { + "line": 14, + "expression": "m", + "index_path": [ + 1, + 0 + ], + "kind": "float", + "positions": [ + [ + 3, + 3 + ] + ], + "provenance": "Read this exact retained zero-matrix leaf after the marker." + }, + { + "line": 14, + "expression": "m", + "index_path": [ + 1, + 1 + ], + "kind": "float", + "positions": [ + [ + 3, + 4 + ] + ], + "provenance": "Read this exact retained zero-matrix leaf after the marker." + } + ] + }, + "tests/phase2/builtins/builtins/zeros_like.branch": { + "old_sha256": "b872d9da1b235b3cd0c42c014f5be6666e3d2afea756ed338f72e66c71aa749f", + "source_sha256": "6c97e2bf0c18e9f4efd0eddfb9b3d0fceeb017e0fb5e791c7cd5eb7aad4dda33", + "witnesses": [ + { + "line": 10, + "expression": "b", + "kind": "float", + "positions": [ + [ + 2, + 1 + ] + ], + "provenance": "Stored result is a float; the expression remains accessible after the frozen source completes." + }, + { + "line": 8, + "expression": "a", + "index_path": [ + 0 + ], + "kind": "float", + "positions": [ + [ + 1, + 1 + ] + ], + "provenance": "Read this exact retained zeros_like result leaf after the marker." + }, + { + "line": 8, + "expression": "a", + "index_path": [ + 1 + ], + "kind": "float", + "positions": [ + [ + 1, + 2 + ] + ], + "provenance": "Read this exact retained zeros_like result leaf after the marker." + } + ] + } + }, + "population_sha256_scope": "Original four-profile R1.7-D6 derivation before the explicit S10-D2 retirements; active population excludes only the two retired paths.", + "retirements": { + "tests/phase2/builtins/builtins/proc_read_buf.branch": { + "ruling": "S10-D2", + "anchor": "ed09f908603a414f77ead78e241fa10aff7f2f38", + "branch_sha256": "76b9f9203919cf0f5b50c95f7ba849c0f66dc9f9d46910ecc966a1308ec409a9", + "source_sha256": "e14e9ac3f0a8421380d4f52db2778bd28c7567f8cb17358d386f94f65bd80221" + }, + "tests/phase2/builtins/builtins/proc_read_buf.hi.branch": { + "ruling": "S10-D2", + "anchor": "ed09f908603a414f77ead78e241fa10aff7f2f38", + "branch_sha256": "f786f6b5395f54fa5f9c39c32f18ebc2d0fcda2099395156af684f37783cfe3b", + "source_sha256": "7af30b36bd60d04b0ff0e6bd5d690fa15db493dd25c88cd56813304d0be6cce3" + } + }, + "semantic_amendments": { + "tests/phase2/builtins/builtins/nearest_in_range_all.branch": { + "ruling": "S10-D2", + "anchor": "ed09f908603a414f77ead78e241fa10aff7f2f38", + "decision": "https://github.com/InauguralSystems/EigenScript/issues/1888#issuecomment-6402855103", + "old_sha256": "b10fe4c4d96d69ca28c3216cb3b82b8db1232808558240afcd320eb943bee6d8", + "new_sha256": "fa6f30b2af6cebed9c244889683a356120916ba20cd5b364579b21010f50634e", + "source_sha256": "abf6d29d7dff1a56f1baed7c89dbf6e68025898c760ff598ce6da21626f5afa5" + } + } +} diff --git a/tests/phase2/readers.c b/tests/phase2/readers.c new file mode 100644 index 000000000..b1ccfc214 --- /dev/null +++ b/tests/phase2/readers.c @@ -0,0 +1,868 @@ +/* #1758 R1.6a: final reader contract through the no-SDL amalgamation. + * Raw nonfinite fixtures bypass constructors deliberately: the new readers + * must refuse them before a C conversion. Every call borrows the same heap + * boxed int/string control; reference counts are checked on success/raise. */ +#include "eigenscript_all.c" + +static int reader_checks, reader_failures, reader_calls; +static void check(int ok, const char *name) { + reader_checks++; + if (!ok) { printf("phase2-readers-test: FAIL %s\n", name); reader_failures++; } +} +static void reset_error(void) { + eigs_clear_error_value(); g_has_error = 0; g_error_print_pending = 0; + g_error_msg[0] = 0; g_error_raw[0] = 0; +} +static void checked_error(int want, const char *name) { + check(!!g_has_error == !!want, name); + if (want) check(g_error_kind == EK_VALUE && strstr(g_error_raw, "reader") != NULL, name); +} + +/* Expectations are literals, independent of the readers' conversion code. */ +typedef struct { + const char *name; int is_int; int64_t i; double f; + int finite, exact; int64_t integer; + int magnitude_ok; int64_t magnitude; + int bounded; +} ReaderCase; +static const ReaderCase reader_cases[] = { + {"zero", 1, 0, 0, 1, 1, 0, 1, 0, 0}, + {"minus-one", 1, -1, 0, 1, 1, -1, 1, -1, 0}, + {"byte-max", 1, 255, 0, 1, 1, 255, 1, 255, 255}, + {"above-byte", 1, 256, 0, 1, 1, 256, 1, 256, 255}, + {"bounded-clamp", 1, 300, 0, 1, 1, 300, 1, 300, 255}, + {"two53-plus1-int", 1, INT64_C(9007199254740993), 0, 1, 1, INT64_C(9007199254740993), 1, INT64_C(9007199254740993), 255}, + {"int64-min", 1, INT64_MIN, 0, 1, 1, INT64_MIN, 1, INT64_MIN, 0}, + {"int64-max", 1, INT64_MAX, 0, 1, 1, INT64_MAX, 1, INT64_MAX, 255}, + {"integral-float", 0, 0, 42.0, 1, 1, 42, 1, 42, 42}, + {"fraction", 0, 0, 1.5, 1, 0, 0, 1, 2, 2}, + {"negative-half", 0, 0, -1.5, 1, 0, 0, 1, -2, 0}, + {"two53-plus1-float", 0, 0, 9007199254740993.0, 1, 1, INT64_C(9007199254740992), 1, INT64_C(9007199254740992), 255}, + {"float-int64-min", 0, 0, -9223372036854775808.0, 1, 1, INT64_MIN, 1, INT64_MIN, 0}, + {"float-int64-upper-inside", 0, 0, 9223372036854774784.0, 1, 1, INT64_C(9223372036854774784), 1, INT64_C(9223372036854774784), 255}, + {"float-int64-upper", 0, 0, 9223372036854775808.0, 1, 0, 0, 0, 0, 255}, + {"below-int64-min", 0, 0, -9223372036854777856.0, 1, 0, 0, 0, 0, 0}, + {"nan", 0, 0, NAN, 0, 0, 0, 0, 0, 0}, + {"inf", 0, 0, INFINITY, 0, 0, 0, 0, 0, 0}, + {"minus-inf", 0, 0, -INFINITY, 0, 0, 0, 0, 0, 0}, +}; + +static void exercise_case(const ReaderCase *c) { + Value fv = { .type = VAL_FLOAT }; + VAL_F64_RAW(&fv) = c->f; + Value *v = c->is_int ? make_int(c->i) : &fv; + EigsSlot slot = c->is_int ? (c->i >= SLOT_SMI_MIN && c->i <= SLOT_SMI_MAX ? slot_from_int(c->i) : slot_from_heap(v)) : slot_from_f64(c->f); + Value *list = make_list_heap(1); + /* A borrowed raw fixture must retain a boxed int for the list. This also + * lets hostile buffer/scalar doubles reach each reader without num_guard. */ + list_values_storage(list)[0] = slot_read_owned(slot); + list->data.list.count = 1; + int refs = v->refcount; + const char *who = "builtin_reader"; + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = eigs_number_arg(v, who); + checked_error(want_error, "eigs_number_arg"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_number_arg result"); + check(v->refcount == refs, "eigs_number_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = eigs_list_number(list, 0, who); + checked_error(want_error, "eigs_list_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_list_number result"); + check(v->refcount == refs, "eigs_list_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = eigs_elem_number(v, who); + checked_error(want_error, "eigs_elem_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_elem_number result"); + check(v->refcount == refs, "eigs_elem_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = (EigsNum){ .is_int = 1, .value = { .i64 = -73 } }; + int rc = eigs_opt_number(v, &out, who); + check(rc == !want_error, "eigs_opt_number status"); + check(!want_error || (out.is_int && out.value.i64 == -73), "eigs_opt_number refusal preserves output"); + checked_error(want_error, "eigs_opt_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_opt_number result"); + check(v->refcount == refs, "eigs_opt_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = eigs_slot_number(slot, who); + checked_error(want_error, "eigs_slot_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_slot_number result"); + check(v->refcount == refs, "eigs_slot_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = eigs_slot_elem_number(slot, who); + checked_error(want_error, "eigs_slot_elem_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_slot_elem_number result"); + check(v->refcount == refs, "eigs_slot_elem_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + EigsNum out = (EigsNum){ .is_int = 1, .value = { .i64 = -73 } }; + int rc = eigs_slot_opt_number(slot, &out, who); + check(rc == !want_error, "eigs_slot_opt_number status"); + check(!want_error || (out.is_int && out.value.i64 == -73), "eigs_slot_opt_number refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_number"); + check(want_error || (out.is_int == c->is_int && (c->is_int ? out.value.i64 == c->i : out.value.f64 == c->f)), "eigs_slot_opt_number result"); + check(v->refcount == refs, "eigs_slot_opt_number borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = eigs_float_arg(v, who); + checked_error(want_error, "eigs_float_arg"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_float_arg result"); + check(v->refcount == refs, "eigs_float_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = eigs_list_float(list, 0, who); + checked_error(want_error, "eigs_list_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_list_float result"); + check(v->refcount == refs, "eigs_list_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = eigs_elem_float(v, who); + checked_error(want_error, "eigs_elem_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_elem_float result"); + check(v->refcount == refs, "eigs_elem_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = -73; + int rc = eigs_opt_float(v, &out, who); + check(rc == !want_error, "eigs_opt_float status"); + check(!want_error || (out == -73), "eigs_opt_float refusal preserves output"); + checked_error(want_error, "eigs_opt_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_opt_float result"); + check(v->refcount == refs, "eigs_opt_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = eigs_slot_float(slot, who); + checked_error(want_error, "eigs_slot_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_slot_float result"); + check(v->refcount == refs, "eigs_slot_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = eigs_slot_elem_float(slot, who); + checked_error(want_error, "eigs_slot_elem_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_slot_elem_float result"); + check(v->refcount == refs, "eigs_slot_elem_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + double out = -73; + int rc = eigs_slot_opt_float(slot, &out, who); + check(rc == !want_error, "eigs_slot_opt_float status"); + check(!want_error || (out == -73), "eigs_slot_opt_float refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_float"); + check(want_error || (out == (c->is_int ? (double)c->i : c->f)), "eigs_slot_opt_float result"); + check(v->refcount == refs, "eigs_slot_opt_float borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = eigs_int_arg(v, who); + checked_error(want_error, "eigs_int_arg"); + check(want_error || (out == c->integer), "eigs_int_arg result"); + check(v->refcount == refs, "eigs_int_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = eigs_list_int(list, 0, who); + checked_error(want_error, "eigs_list_int"); + check(want_error || (out == c->integer), "eigs_list_int result"); + check(v->refcount == refs, "eigs_list_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = eigs_elem_int(v, who); + checked_error(want_error, "eigs_elem_int"); + check(want_error || (out == c->integer), "eigs_elem_int result"); + check(v->refcount == refs, "eigs_elem_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_opt_int(v, &out, who); + check(rc == !want_error, "eigs_opt_int status"); + check(!want_error || (out == -73), "eigs_opt_int refusal preserves output"); + checked_error(want_error, "eigs_opt_int"); + check(want_error || (out == c->integer), "eigs_opt_int result"); + check(v->refcount == refs, "eigs_opt_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = eigs_slot_int(slot, who); + checked_error(want_error, "eigs_slot_int"); + check(want_error || (out == c->integer), "eigs_slot_int result"); + check(v->refcount == refs, "eigs_slot_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = eigs_slot_elem_int(slot, who); + checked_error(want_error, "eigs_slot_elem_int"); + check(want_error || (out == c->integer), "eigs_slot_elem_int result"); + check(v->refcount == refs, "eigs_slot_elem_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_slot_opt_int(slot, &out, who); + check(rc == !want_error, "eigs_slot_opt_int status"); + check(!want_error || (out == -73), "eigs_slot_opt_int refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_int"); + check(want_error || (out == c->integer), "eigs_slot_opt_int result"); + check(v->refcount == refs, "eigs_slot_opt_int borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = eigs_byte_arg(v, who); + checked_error(want_error, "eigs_byte_arg"); + check(want_error || (out == c->integer), "eigs_byte_arg result"); + check(v->refcount == refs, "eigs_byte_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = eigs_list_byte(list, 0, who); + checked_error(want_error, "eigs_list_byte"); + check(want_error || (out == c->integer), "eigs_list_byte result"); + check(v->refcount == refs, "eigs_list_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = eigs_elem_byte(v, who); + checked_error(want_error, "eigs_elem_byte"); + check(want_error || (out == c->integer), "eigs_elem_byte result"); + check(v->refcount == refs, "eigs_elem_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = 73; + int rc = eigs_opt_byte(v, &out, who); + check(rc == !want_error, "eigs_opt_byte status"); + check(!want_error || (out == 73), "eigs_opt_byte refusal preserves output"); + checked_error(want_error, "eigs_opt_byte"); + check(want_error || (out == c->integer), "eigs_opt_byte result"); + check(v->refcount == refs, "eigs_opt_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = eigs_slot_byte(slot, who); + checked_error(want_error, "eigs_slot_byte"); + check(want_error || (out == c->integer), "eigs_slot_byte result"); + check(v->refcount == refs, "eigs_slot_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = eigs_slot_elem_byte(slot, who); + checked_error(want_error, "eigs_slot_elem_byte"); + check(want_error || (out == c->integer), "eigs_slot_elem_byte result"); + check(v->refcount == refs, "eigs_slot_elem_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact || c->integer < 0 || c->integer > 255; + uint8_t out = 73; + int rc = eigs_slot_opt_byte(slot, &out, who); + check(rc == !want_error, "eigs_slot_opt_byte status"); + check(!want_error || (out == 73), "eigs_slot_opt_byte refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_byte"); + check(want_error || (out == c->integer), "eigs_slot_opt_byte result"); + check(v->refcount == refs, "eigs_slot_opt_byte borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_int_arg_range(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_int_arg_range status"); + check(!want_error || (out == -73), "eigs_int_arg_range refusal preserves output"); + checked_error(want_error, "eigs_int_arg_range"); + check(want_error || (out == c->integer), "eigs_int_arg_range result"); + check(v->refcount == refs, "eigs_int_arg_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_list_int_range(list, 0, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_list_int_range status"); + check(!want_error || (out == -73), "eigs_list_int_range refusal preserves output"); + checked_error(want_error, "eigs_list_int_range"); + check(want_error || (out == c->integer), "eigs_list_int_range result"); + check(v->refcount == refs, "eigs_list_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_elem_int_range(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_elem_int_range status"); + check(!want_error || (out == -73), "eigs_elem_int_range refusal preserves output"); + checked_error(want_error, "eigs_elem_int_range"); + check(want_error || (out == c->integer), "eigs_elem_int_range result"); + check(v->refcount == refs, "eigs_elem_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_opt_int_range(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_opt_int_range status"); + check(!want_error || (out == -73), "eigs_opt_int_range refusal preserves output"); + checked_error(want_error, "eigs_opt_int_range"); + check(want_error || (out == c->integer), "eigs_opt_int_range result"); + check(v->refcount == refs, "eigs_opt_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_slot_int_range(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_int_range status"); + check(!want_error || (out == -73), "eigs_slot_int_range refusal preserves output"); + checked_error(want_error, "eigs_slot_int_range"); + check(want_error || (out == c->integer), "eigs_slot_int_range result"); + check(v->refcount == refs, "eigs_slot_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_slot_elem_int_range(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_elem_int_range status"); + check(!want_error || (out == -73), "eigs_slot_elem_int_range refusal preserves output"); + checked_error(want_error, "eigs_slot_elem_int_range"); + check(want_error || (out == c->integer), "eigs_slot_elem_int_range result"); + check(v->refcount == refs, "eigs_slot_elem_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->exact; + int64_t out = -73; + int rc = eigs_slot_opt_int_range(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_opt_int_range status"); + check(!want_error || (out == -73), "eigs_slot_opt_int_range refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_int_range"); + check(want_error || (out == c->integer), "eigs_slot_opt_int_range result"); + check(v->refcount == refs, "eigs_slot_opt_int_range borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_bounded_arg(v, 0, 255, &out, who); + check(rc == !want_error, "eigs_bounded_arg status"); + check(!want_error || (out == -73), "eigs_bounded_arg refusal preserves output"); + checked_error(want_error, "eigs_bounded_arg"); + check(want_error || (out == c->bounded), "eigs_bounded_arg result"); + check(v->refcount == refs, "eigs_bounded_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_list_bounded(list, 0, 0, 255, &out, who); + check(rc == !want_error, "eigs_list_bounded status"); + check(!want_error || (out == -73), "eigs_list_bounded refusal preserves output"); + checked_error(want_error, "eigs_list_bounded"); + check(want_error || (out == c->bounded), "eigs_list_bounded result"); + check(v->refcount == refs, "eigs_list_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_elem_bounded(v, 0, 255, &out, who); + check(rc == !want_error, "eigs_elem_bounded status"); + check(!want_error || (out == -73), "eigs_elem_bounded refusal preserves output"); + checked_error(want_error, "eigs_elem_bounded"); + check(want_error || (out == c->bounded), "eigs_elem_bounded result"); + check(v->refcount == refs, "eigs_elem_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_opt_bounded(v, 0, 255, &out, who); + check(rc == !want_error, "eigs_opt_bounded status"); + check(!want_error || (out == -73), "eigs_opt_bounded refusal preserves output"); + checked_error(want_error, "eigs_opt_bounded"); + check(want_error || (out == c->bounded), "eigs_opt_bounded result"); + check(v->refcount == refs, "eigs_opt_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_slot_bounded(slot, 0, 255, &out, who); + check(rc == !want_error, "eigs_slot_bounded status"); + check(!want_error || (out == -73), "eigs_slot_bounded refusal preserves output"); + checked_error(want_error, "eigs_slot_bounded"); + check(want_error || (out == c->bounded), "eigs_slot_bounded result"); + check(v->refcount == refs, "eigs_slot_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_slot_elem_bounded(slot, 0, 255, &out, who); + check(rc == !want_error, "eigs_slot_elem_bounded status"); + check(!want_error || (out == -73), "eigs_slot_elem_bounded refusal preserves output"); + checked_error(want_error, "eigs_slot_elem_bounded"); + check(want_error || (out == c->bounded), "eigs_slot_elem_bounded result"); + check(v->refcount == refs, "eigs_slot_elem_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->finite; + int out = -73; + int rc = eigs_slot_opt_bounded(slot, 0, 255, &out, who); + check(rc == !want_error, "eigs_slot_opt_bounded status"); + check(!want_error || (out == -73), "eigs_slot_opt_bounded refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_bounded"); + check(want_error || (out == c->bounded), "eigs_slot_opt_bounded result"); + check(v->refcount == refs, "eigs_slot_opt_bounded borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_magnitude_arg(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_magnitude_arg status"); + check(!want_error || (out == -73), "eigs_magnitude_arg refusal preserves output"); + checked_error(want_error, "eigs_magnitude_arg"); + check(want_error || (out == c->magnitude), "eigs_magnitude_arg result"); + check(v->refcount == refs, "eigs_magnitude_arg borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_list_magnitude(list, 0, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_list_magnitude status"); + check(!want_error || (out == -73), "eigs_list_magnitude refusal preserves output"); + checked_error(want_error, "eigs_list_magnitude"); + check(want_error || (out == c->magnitude), "eigs_list_magnitude result"); + check(v->refcount == refs, "eigs_list_magnitude borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_elem_magnitude(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_elem_magnitude status"); + check(!want_error || (out == -73), "eigs_elem_magnitude refusal preserves output"); + checked_error(want_error, "eigs_elem_magnitude"); + check(want_error || (out == c->magnitude), "eigs_elem_magnitude result"); + check(v->refcount == refs, "eigs_elem_magnitude borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_opt_magnitude(v, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_opt_magnitude status"); + check(!want_error || (out == -73), "eigs_opt_magnitude refusal preserves output"); + checked_error(want_error, "eigs_opt_magnitude"); + check(want_error || (out == c->magnitude), "eigs_opt_magnitude result"); + check(v->refcount == refs, "eigs_opt_magnitude borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_slot_magnitude(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_magnitude status"); + check(!want_error || (out == -73), "eigs_slot_magnitude refusal preserves output"); + checked_error(want_error, "eigs_slot_magnitude"); + check(want_error || (out == c->magnitude), "eigs_slot_magnitude result"); + check(v->refcount == refs, "eigs_slot_magnitude borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_slot_elem_magnitude(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_elem_magnitude status"); + check(!want_error || (out == -73), "eigs_slot_elem_magnitude refusal preserves output"); + checked_error(want_error, "eigs_slot_elem_magnitude"); + check(want_error || (out == c->magnitude), "eigs_slot_elem_magnitude result"); + check(v->refcount == refs, "eigs_slot_elem_magnitude borrowed ownership"); + } + { + reset_error(); reader_calls++; + int want_error = !c->magnitude_ok; + int64_t out = -73; + int rc = eigs_slot_opt_magnitude(slot, INT64_MIN, INT64_MAX, &out, who); + check(rc == !want_error, "eigs_slot_opt_magnitude status"); + check(!want_error || (out == -73), "eigs_slot_opt_magnitude refusal preserves output"); + checked_error(want_error, "eigs_slot_opt_magnitude"); + check(want_error || (out == c->magnitude), "eigs_slot_opt_magnitude result"); + check(v->refcount == refs, "eigs_slot_opt_magnitude borrowed ownership"); + } + reset_error(); + val_decref(list); + if (c->is_int) val_decref(v); +} + +static void boundaries(void) { + int64_t out = -73; + int bounded = -73; + Value v = { .type = VAL_FLOAT }; + const int64_t xs[] = { -11, -10, 10, 11 }; + for (unsigned k = 0; k < sizeof xs / sizeof xs[0]; k++) { + Value *i = make_int(xs[k]); + reset_error(); int rc = eigs_int_arg_range(i, -10, 10, &out, "reader"); + check(rc == (k == 1 || k == 2), "int range inclusive boundaries"); + check(!rc || out == xs[k], "int range boundary result"); + reset_error(); rc = eigs_magnitude_arg(i, -10, 10, &out, "reader"); + check(rc == (k == 1 || k == 2), "magnitude inclusive boundaries"); + val_decref(i); + } + /* The C int edge is a separate range from int64: both edges succeed, + * their adjacent integers refuse exact/magnitude conversion, and bounded + * conversion clamps them before rounding. Expected values are literals. */ + static const struct { + double input; int in_range; int expected; + } c_int_edges[] = { + { -2147483649.0, 0, INT_MIN }, + { -2147483648.0, 1, INT_MIN }, + { 2147483647.0, 1, INT_MAX }, + { 2147483648.0, 0, INT_MAX }, + }; + for (unsigned k = 0; k < sizeof c_int_edges / sizeof c_int_edges[0]; k++) { + VAL_F64_RAW(&v) = c_int_edges[k].input; + reset_error(); out = -73; + int rc = eigs_int_arg_range(&v, INT_MIN, INT_MAX, &out, "reader"); + check(rc == c_int_edges[k].in_range, "C int exact inclusive boundary"); + check(rc ? out == c_int_edges[k].expected : out == -73, + "C int exact boundary output"); + checked_error(!c_int_edges[k].in_range, "C int exact boundary error"); + reset_error(); out = -73; + rc = eigs_magnitude_arg(&v, INT_MIN, INT_MAX, &out, "reader"); + check(rc == c_int_edges[k].in_range, "C int magnitude inclusive boundary"); + check(rc ? out == c_int_edges[k].expected : out == -73, + "C int magnitude boundary output"); + checked_error(!c_int_edges[k].in_range, "C int magnitude boundary error"); + reset_error(); bounded = -73; + check(eigs_bounded_arg(&v, INT_MIN, INT_MAX, &bounded, "reader") && + bounded == c_int_edges[k].expected && !g_has_error, + "C int bounded edges clamp"); + } + VAL_F64_RAW(&v) = 1.5; + reset_error(); (void)eigs_int_arg(&v, "builtin_reader"); + check(g_has_error && strcmp(g_error_raw, "'reader': number 1.5 has no integer representation") == 0, + "N5 reuses EIGS_NO_INT_REP_FMT"); + VAL_F64_RAW(&v) = 3e12; + reset_error(); + check(!eigs_magnitude_arg(&v, INT_MIN, INT_MAX, &out, "reader") && g_error_kind == EK_VALUE, + "magnitude 3e12 refuses C int range"); + VAL_F64_RAW(&v) = 10.49; + reset_error(); check(eigs_magnitude_arg(&v, -10, 10, &out, "reader") && out == 10, + "magnitude rounds before range"); + VAL_F64_RAW(&v) = 10.5; + reset_error(); check(!eigs_magnitude_arg(&v, -10, 10, &out, "reader"), "magnitude rounded above range"); + VAL_F64_RAW(&v) = -0.5; + reset_error(); check(eigs_bounded_arg(&v, -255, 255, &bounded, "reader") && bounded == -1, + "bounded negative half away"); + reset_error(); check(!eigs_bounded_arg(&v, NAN, 255, &bounded, "reader"), "bounded invalid domain"); + reset_error(); check(!eigs_int_arg_range(&v, 10, -10, &out, "reader"), "inverted int range"); + reset_error(); check(!eigs_magnitude_arg(&v, 10, -10, &out, "reader"), "inverted magnitude range"); + reset_error(); +} + +static void optional_and_types(void) { + Value *bad = make_str("heap-type-control"); + const char *who = "reader"; + int refs = bad->refcount; + { + EigsNum out = (EigsNum){ .is_int = 1, .value = { .i64 = 73 } }; + reset_error(); + check(!eigs_opt_number(NULL, &out, who) && !g_has_error && (out.is_int && out.value.i64 == 73), "eigs_opt_number absent"); + check(!eigs_opt_number(make_null(), &out, who) && !g_has_error && (out.is_int && out.value.i64 == 73), "eigs_opt_number null"); + check(!eigs_slot_opt_number(slot_null(), &out, who) && !g_has_error && (out.is_int && out.value.i64 == 73), "eigs_slot_opt_number null"); + reset_error(); + check(!eigs_opt_number(bad, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out.is_int && out.value.i64 == 73), "eigs_opt_number wrong type"); + g_strict = 0; reset_error(); + out = eigs_elem_number(bad, who); check((out.is_int == 0 && out.value.f64 == 0) && !g_has_error, "eigs_elem_number non-strict zero"); + reset_error(); (void)eigs_elem_number(make_bool(1), who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_number bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_number(bad, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_number strict wrong type"); + reset_error(); (void)eigs_list_number(NULL, 0, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_number missing argument"); + check(bad->refcount == refs, "number heap refusal ownership"); + } + { + double out = 73; + reset_error(); + check(!eigs_opt_float(NULL, &out, who) && !g_has_error && (out == 73), "eigs_opt_float absent"); + check(!eigs_opt_float(make_null(), &out, who) && !g_has_error && (out == 73), "eigs_opt_float null"); + check(!eigs_slot_opt_float(slot_null(), &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_float null"); + reset_error(); + check(!eigs_opt_float(bad, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_float wrong type"); + g_strict = 0; reset_error(); + out = eigs_elem_float(bad, who); check((out == 0) && !g_has_error, "eigs_elem_float non-strict zero"); + reset_error(); (void)eigs_elem_float(make_bool(1), who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_float bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_float(bad, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_float strict wrong type"); + reset_error(); (void)eigs_list_float(NULL, 0, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_float missing argument"); + check(bad->refcount == refs, "float heap refusal ownership"); + } + { + int64_t out = 73; + reset_error(); + check(!eigs_opt_int(NULL, &out, who) && !g_has_error && (out == 73), "eigs_opt_int absent"); + check(!eigs_opt_int(make_null(), &out, who) && !g_has_error && (out == 73), "eigs_opt_int null"); + check(!eigs_slot_opt_int(slot_null(), &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_int null"); + reset_error(); + check(!eigs_opt_int(bad, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_int wrong type"); + g_strict = 0; reset_error(); + out = eigs_elem_int(bad, who); check((out == 0) && !g_has_error, "eigs_elem_int non-strict zero"); + reset_error(); (void)eigs_elem_int(make_bool(1), who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_int bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_int(bad, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_int strict wrong type"); + reset_error(); (void)eigs_list_int(NULL, 0, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_int missing argument"); + check(bad->refcount == refs, "int heap refusal ownership"); + } + { + uint8_t out = 73; + reset_error(); + check(!eigs_opt_byte(NULL, &out, who) && !g_has_error && (out == 73), "eigs_opt_byte absent"); + check(!eigs_opt_byte(make_null(), &out, who) && !g_has_error && (out == 73), "eigs_opt_byte null"); + check(!eigs_slot_opt_byte(slot_null(), &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_byte null"); + reset_error(); + check(!eigs_opt_byte(bad, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_byte wrong type"); + g_strict = 0; reset_error(); + out = eigs_elem_byte(bad, who); check((out == 0) && !g_has_error, "eigs_elem_byte non-strict zero"); + reset_error(); (void)eigs_elem_byte(make_bool(1), who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_byte bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_byte(bad, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_byte strict wrong type"); + reset_error(); (void)eigs_list_byte(NULL, 0, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_byte missing argument"); + check(bad->refcount == refs, "byte heap refusal ownership"); + } + { + int64_t out = 73; + reset_error(); + check(!eigs_opt_int_range(NULL, -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_opt_int_range absent"); + check(!eigs_opt_int_range(make_null(), -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_opt_int_range null"); + check(!eigs_slot_opt_int_range(slot_null(), -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_int_range null"); + reset_error(); + check(!eigs_opt_int_range(bad, -10, 10, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_int_range wrong type"); + g_strict = 0; reset_error(); + check(eigs_elem_int_range(bad, -10, 10, &out, who) && (out == 0) && !g_has_error, "eigs_elem_int_range non-strict zero"); + reset_error(); (void)eigs_elem_int_range(make_bool(1), -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_int_range bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_int_range(bad, -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_int_range strict wrong type"); + reset_error(); (void)eigs_list_int_range(NULL, 0, -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_int_range missing argument"); + check(bad->refcount == refs, "int_range heap refusal ownership"); + } + { + int out = 73; + reset_error(); + check(!eigs_opt_bounded(NULL, 0, 255, &out, who) && !g_has_error && (out == 73), "eigs_opt_bounded absent"); + check(!eigs_opt_bounded(make_null(), 0, 255, &out, who) && !g_has_error && (out == 73), "eigs_opt_bounded null"); + check(!eigs_slot_opt_bounded(slot_null(), 0, 255, &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_bounded null"); + reset_error(); + check(!eigs_opt_bounded(bad, 0, 255, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_bounded wrong type"); + g_strict = 0; reset_error(); + check(eigs_elem_bounded(bad, 0, 255, &out, who) && (out == 0) && !g_has_error, "eigs_elem_bounded non-strict zero"); + reset_error(); (void)eigs_elem_bounded(make_bool(1), 0, 255, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_bounded bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_bounded(bad, 0, 255, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_bounded strict wrong type"); + reset_error(); (void)eigs_list_bounded(NULL, 0, 0, 255, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_bounded missing argument"); + check(bad->refcount == refs, "bounded heap refusal ownership"); + } + { + int64_t out = 73; + reset_error(); + check(!eigs_opt_magnitude(NULL, -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_opt_magnitude absent"); + check(!eigs_opt_magnitude(make_null(), -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_opt_magnitude null"); + check(!eigs_slot_opt_magnitude(slot_null(), -10, 10, &out, who) && !g_has_error && (out == 73), "eigs_slot_opt_magnitude null"); + reset_error(); + check(!eigs_opt_magnitude(bad, -10, 10, &out, who) && g_has_error && g_error_kind == EK_TYPE && (out == 73), "eigs_opt_magnitude wrong type"); + g_strict = 0; reset_error(); + check(eigs_elem_magnitude(bad, -10, 10, &out, who) && (out == 0) && !g_has_error, "eigs_elem_magnitude non-strict zero"); + reset_error(); (void)eigs_elem_magnitude(make_bool(1), -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_magnitude bool always refuses"); + g_strict = 1; reset_error(); (void)eigs_elem_magnitude(bad, -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_elem_magnitude strict wrong type"); + reset_error(); (void)eigs_list_magnitude(NULL, 0, -10, 10, &out, who); check(g_has_error && g_error_kind == EK_TYPE, "eigs_list_magnitude missing argument"); + check(bad->refcount == refs, "magnitude heap refusal ownership"); + } + reset_error(); val_decref(bad); +} + +static void container_and_buffer(void) { + Value *list = make_list_heap(1), *dict = make_dict(1); + Value *str = make_str("owned-container-control"); + list_append_owned(list, str); + dict_set_owned(dict, "x", make_str("owned-dict-control")); + double f = -73; int64_t i = -73; + list_set_int(list, 0, INT64_C(9007199254740993)); + check(list_get_int(list, 0, &i) && i == INT64_C(9007199254740993), "list_set/get_int boxed exact"); + check(!list_get_f64(list, 0, &f) && f == -73, "list_get_f64 refuses int"); + list_rewrite_int(list, 0, INT64_MAX); + check(list_get_int(list, 0, &i) && i == INT64_MAX, "list_rewrite_int boxed ownership"); + list_set_f64(list, 0, 1.5); + check(list_get_f64(list, 0, &f) && f == 1.5, "list_set/get_f64"); + check(!list_get_int(list, 0, &i), "list_get_int refuses float"); + list_rewrite_f64(list, 0, 42.0); + check(list_get_f64(list, 0, &f) && f == 42.0, "list_rewrite_f64"); + list_append_int(list, INT64_MIN); + check(list_get_int(list, 1, &i) && i == INT64_MIN, "list_append_int boxed exact"); + list_append_f64(list, 2.5); + check(list_get_f64(list, 2, &f) && f == 2.5, "list_append_f64"); + dict_value_set_int(dict, 0, INT64_C(9007199254740993)); + check(dict_value_get_int(dict, 0, &i) && i == INT64_C(9007199254740993), "dict_value_set/get_int boxed exact"); + check(!dict_value_get_f64(dict, 0, &f), "dict_value_get_f64 refuses int"); + dict_value_rewrite_int(dict, 0, INT64_MIN); + check(dict_value_get_int(dict, 0, &i) && i == INT64_MIN, "dict_value_rewrite_int"); + dict_value_set_f64(dict, 0, 2.5); + check(dict_value_get_f64(dict, 0, &f) && f == 2.5, "dict_value_set/get_f64"); + check(!dict_value_get_int(dict, 0, &i), "dict_value_get_int refuses float"); + dict_value_rewrite_f64(dict, 0, 42.0); + check(dict_value_get_f64(dict, 0, &f) && f == 42, "dict_value_rewrite_f64"); + /* Typed container getters report storage kind, including IEEE payloads; + * validation belongs to the checked _arg readers, not the storage lens. */ + list_values_storage(list)[0] = slot_from_f64(NAN); + dict_values_storage(dict)[0] = slot_from_f64(INFINITY); + check(list_get_f64(list, 0, &f) && isnan(f), "list_get_f64 IEEE storage"); + check(!list_get_int(list, 0, &i), "list_get_int refuses IEEE float"); + check(dict_value_get_f64(dict, 0, &f) && isinf(f), "dict_value_get_f64 IEEE storage"); + check(!dict_value_get_int(dict, 0, &i), "dict_value_get_int refuses IEEE float"); + val_decref(list); val_decref(dict); + double cells[] = { 0, 255, -1, 256, 1.5, 9007199254740993.0, + -9223372036854775808.0, 9223372036854775808.0, NAN, INFINITY }; + Value buf = { .type = VAL_BUFFER }; + buf.data.buffer.data = cells; + buf.data.buffer.count = (int)(sizeof cells / sizeof cells[0]); + for (int k = 0; k < buf.data.buffer.count; k++) { + reset_error(); uint8_t byte = 73; + int rc = buffer_byte_at(&buf, k, &byte, "reader"); + check(rc == (k < 2), "buffer_byte_at exact boundary"); + check(rc ? byte == (uint8_t)cells[k] : byte == 73, "buffer_byte_at output"); + reset_error(); i = -73; + rc = buffer_index_at(&buf, k, &i, "reader"); + check(rc == (k < 7 && k != 4), "buffer_index_at exact conversion"); + if (k == 5) check(rc && i == INT64_C(9007199254740992), "buffer index is stored rounded float"); + if (k == 6) check(rc && i == INT64_MIN, "buffer index int64 lower edge"); + reset_error(); f = buffer_read_f64(&buf, k); + check(k < 8 ? !g_has_error && f == cells[k] : g_has_error && g_error_kind == EK_VALUE, + "buffer_read_f64 finite data and nonfinite refusal"); + } + int saved_strict = g_strict; + unsigned saved_flags = g_math_flags; + for (int strict = 0; strict <= 1; strict++) { + g_strict = strict; + g_math_flags = 0; + reset_error(); + f = buffer_read_f64(&buf, 8); + check(g_has_error && g_error_kind == EK_VALUE && f == 0.0, + "buffer_read_f64 NaN refusal in both modes"); + check(g_math_flags == (strict ? EIGS_MATH_INVALID : 0), + "buffer_read_f64 strict NaN retains INVALID only"); + check(strstr(g_error_raw, strict ? "not a number" : "expected a finite number") != NULL, + "buffer_read_f64 strict NaN retains its diagnostic"); + } + g_strict = saved_strict; + g_math_flags = saved_flags; + reset_error(); check(!buffer_index_at(&buf, -1, &i, "reader") && g_error_kind == EK_INDEX, "buffer negative index"); + reset_error(); check(!buffer_byte_at(&buf, buf.data.buffer.count, (uint8_t *)&i, "reader") && g_error_kind == EK_INDEX, "buffer upper index"); + reset_error(); + Value *normal = make_float(1.5), *permanent = make_float_permanent(2.5); + check(normal->type == VAL_FLOAT && eigs_float_arg(normal, "reader") == 1.5, "make_float"); + check(permanent->type == VAL_FLOAT && eigs_float_arg(permanent, "reader") == 2.5, "make_float_permanent"); + val_decref(normal); val_decref(permanent); +} + +/* Sync #10: #1697's tombstones must not hide a non-reflexive descendant + * from Phase2's R1.6z-D2 structural self-equality traversal. */ +static void tombstoned_dict_equality(void) { + int saved_strict = g_strict; + unsigned saved_flags = g_math_flags; + Value *nanbuf = eigs_value_new_buffer(1); + eigs_value_buffer_set(nanbuf, 0, NAN); /* public raw setter preserves bits */ + Value *dict = make_dict(3); + dict_set_owned(dict, "removed", make_int(1)); + dict_set_owned(dict, "finite", make_int(2)); + dict_set(dict, "nan-buffer", nanbuf); + dict_remove(dict, "removed"); + check(dict->data.dict.count == 2 && dict->data.dict.used == 3, + "tombstone leaves NaN buffer beyond live count"); + for (int strict = 0; strict <= 1; strict++) { + g_strict = strict; + g_math_flags = EIGS_MATH_UNDERFLOW; + reset_error(); + check(!values_equal(dict, dict), + "tombstoned dict self-equality reaches NaN buffer"); + check(!g_has_error && g_math_flags == EIGS_MATH_UNDERFLOW, + "tombstoned dict equality never raises or writes math flags"); + } + val_decref(dict); + val_decref(nanbuf); + g_strict = saved_strict; + g_math_flags = saved_flags; + reset_error(); +} + +/* #1808/#2098: expected raises need a handler on the active VM frame. + * A suspended task's aggregate try depth is not a catch context. */ +static Value *phase2_reader_probe(Value *arg) { + (void)arg; + for (unsigned k = 0; k < sizeof reader_cases / sizeof reader_cases[0]; k++) + exercise_case(&reader_cases[k]); + check(reader_calls == 49 * (int)(sizeof reader_cases / sizeof reader_cases[0]), + "all 49 declared reader forms executed for every fixture"); + boundaries(); optional_and_types(); container_and_buffer(); + tombstoned_dict_equality(); + reset_error(); + return make_bool(1); +} + +int main(void) { + EigsState *st = eigs_open(); + if (!st) { puts("phase2-readers-test: FAIL eigs_open"); return 1; } + Value *probe = make_builtin(phase2_reader_probe); + eigs_set_global("phase2_reader_probe", probe); + val_decref(probe); + Value *result = eigs_eval_string("try:\n phase2_reader_probe of null\ncatch e:\n false\n"); + if (!result || result->type != VAL_BOOL || !result->data.boolean || g_has_error) { + puts("phase2-readers-test: FAIL VM probe execution"); + reader_failures++; + } + val_decref(result); + eigs_close(st); + printf("phase2-readers-test: %s cases=%zu forms=%d checks=%d failures=%d\n", + reader_failures ? "FAIL" : "PASS", sizeof reader_cases / sizeof reader_cases[0], + reader_calls / (int)(sizeof reader_cases / sizeof reader_cases[0]), reader_checks, reader_failures); + return reader_failures ? 1 : 0; +} diff --git a/tests/phase2/rows/A.add-overflow-raises.eigs b/tests/phase2/rows/A.add-overflow-raises.eigs new file mode 100644 index 000000000..c4d7e56c5 --- /dev/null +++ b/tests/phase2/rows/A.add-overflow-raises.eigs @@ -0,0 +1,10 @@ +# ROW: A.add-overflow-raises +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (9223372036854775807 + 1) +catch e: + print of [e.kind, (index_of of [e.message, "+"]) >= 0] diff --git a/tests/phase2/rows/A.add-overflow-raises.out b/tests/phase2/rows/A.add-overflow-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/A.add-overflow-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/A.fn-add-overflow.eigs b/tests/phase2/rows/A.fn-add-overflow.eigs new file mode 100644 index 000000000..ee028484c --- /dev/null +++ b/tests/phase2/rows/A.fn-add-overflow.eigs @@ -0,0 +1,19 @@ +# ROW: A.fn-add-overflow +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop inc INT_ONE +# OPS: INT_ONE +define inc(a) as: + warm is 0.5 + t is a + 1 + return t +r is null +i is 0.0 +loop while i < 60.0: + r is inc of 2 + i is i + 1.0 +try: + print of (inc of 9223372036854775807) +catch e: + print of e.kind diff --git a/tests/phase2/rows/A.fn-add-overflow.out b/tests/phase2/rows/A.fn-add-overflow.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/A.fn-add-overflow.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/A.leaf-mul-overflow.eigs b/tests/phase2/rows/A.leaf-mul-overflow.eigs new file mode 100644 index 000000000..fded1f911 --- /dev/null +++ b/tests/phase2/rows/A.leaf-mul-overflow.eigs @@ -0,0 +1,12 @@ +# ROW: A.leaf-mul-overflow +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen sq +# OPS: MUL +define sq(x) as: + return x * x +try: + print of (sq of 3037000500) +catch e: + print of e.kind diff --git a/tests/phase2/rows/A.leaf-mul-overflow.out b/tests/phase2/rows/A.leaf-mul-overflow.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/A.leaf-mul-overflow.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/A.mul-overflow-raises.eigs b/tests/phase2/rows/A.mul-overflow-raises.eigs new file mode 100644 index 000000000..318ac2f3d --- /dev/null +++ b/tests/phase2/rows/A.mul-overflow-raises.eigs @@ -0,0 +1,10 @@ +# ROW: A.mul-overflow-raises +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (4611686018427387904 * 2) +catch e: + print of [e.kind, (index_of of [e.message, "*"]) >= 0] diff --git a/tests/phase2/rows/A.mul-overflow-raises.out b/tests/phase2/rows/A.mul-overflow-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/A.mul-overflow-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/A.osr-double-overflow.eigs b/tests/phase2/rows/A.osr-double-overflow.eigs new file mode 100644 index 000000000..f527f752f --- /dev/null +++ b/tests/phase2/rows/A.osr-double-overflow.eigs @@ -0,0 +1,18 @@ +# ROW: A.osr-double-overflow +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop dbl MUL +# OPS: MUL +define dbl(n, two) as: + x is two + i is 1.0 + loop while true: + x is x * two + i is i + 1.0 + if i >= n: + return x +try: + print of (dbl of [63.0, 2]) +catch e: + print of e.kind diff --git a/tests/phase2/rows/A.osr-double-overflow.out b/tests/phase2/rows/A.osr-double-overflow.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/A.osr-double-overflow.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/A.sub-overflow-raises.eigs b/tests/phase2/rows/A.sub-overflow-raises.eigs new file mode 100644 index 000000000..fba24880f --- /dev/null +++ b/tests/phase2/rows/A.sub-overflow-raises.eigs @@ -0,0 +1,10 @@ +# ROW: A.sub-overflow-raises +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (-9223372036854775807 - 2) +catch e: + print of [e.kind, (index_of of [e.message, "-"]) >= 0] diff --git a/tests/phase2/rows/A.sub-overflow-raises.out b/tests/phase2/rows/A.sub-overflow-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/A.sub-overflow-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/A.sum-overflow-raises.eigs b/tests/phase2/rows/A.sum-overflow-raises.eigs new file mode 100644 index 000000000..791968d65 --- /dev/null +++ b/tests/phase2/rows/A.sum-overflow-raises.eigs @@ -0,0 +1,10 @@ +# ROW: A.sum-overflow-raises +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (sum of [9223372036854775807, 1]) +catch e: + print of [e.kind, (index_of of [e.message, "sum"]) >= 0] diff --git a/tests/phase2/rows/A.sum-overflow-raises.out b/tests/phase2/rows/A.sum-overflow-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/A.sum-overflow-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/A.tensor-scalar-overflow-raises.eigs b/tests/phase2/rows/A.tensor-scalar-overflow-raises.eigs new file mode 100644 index 000000000..7f691c18a --- /dev/null +++ b/tests/phase2/rows/A.tensor-scalar-overflow-raises.eigs @@ -0,0 +1,10 @@ +# ROW: A.tensor-scalar-overflow-raises +# ITEMS: A +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (multiply of [4611686018427387904, 2]) +catch e: + print of [e.kind, (index_of of [e.message, "multiply"]) >= 0] diff --git a/tests/phase2/rows/A.tensor-scalar-overflow-raises.out b/tests/phase2/rows/A.tensor-scalar-overflow-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/A.tensor-scalar-overflow-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/B.type-of-both-num.eigs b/tests/phase2/rows/B.type-of-both-num.eigs new file mode 100644 index 000000000..1b8a03a1e --- /dev/null +++ b/tests/phase2/rows/B.type-of-both-num.eigs @@ -0,0 +1,7 @@ +# ROW: B.type-of-both-num +# ITEMS: B +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [type of 3, type of 3.0, (type of 3) == (type of 3.0), num_type of 3] diff --git a/tests/phase2/rows/B.type-of-both-num.out b/tests/phase2/rows/B.type-of-both-num.out new file mode 100644 index 000000000..d87d0837b --- /dev/null +++ b/tests/phase2/rows/B.type-of-both-num.out @@ -0,0 +1 @@ +["num", "num", true, "int"] diff --git a/tests/phase2/rows/N1.canon-eq.eigs b/tests/phase2/rows/N1.canon-eq.eigs new file mode 100644 index 000000000..94e61d179 --- /dev/null +++ b/tests/phase2/rows/N1.canon-eq.eigs @@ -0,0 +1,9 @@ +# ROW: N1.canon-eq +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +a is abs of -5 +b is 0 - 5 +print of [a == 5, -b == 5, num_type of a] diff --git a/tests/phase2/rows/N1.canon-eq.out b/tests/phase2/rows/N1.canon-eq.out new file mode 100644 index 000000000..16c0e6f1b --- /dev/null +++ b/tests/phase2/rows/N1.canon-eq.out @@ -0,0 +1 @@ +[true, true, "int"] diff --git a/tests/phase2/rows/N1.decimal-past-int64-is-float.eigs b/tests/phase2/rows/N1.decimal-past-int64-is-float.eigs new file mode 100644 index 000000000..c5db139cc --- /dev/null +++ b/tests/phase2/rows/N1.decimal-past-int64-is-float.eigs @@ -0,0 +1,8 @@ +# ROW: N1.decimal-past-int64-is-float +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +x is 9223372036854775808 +print of [num_type of x, x] diff --git a/tests/phase2/rows/N1.decimal-past-int64-is-float.out b/tests/phase2/rows/N1.decimal-past-int64-is-float.out new file mode 100644 index 000000000..958576330 --- /dev/null +++ b/tests/phase2/rows/N1.decimal-past-int64-is-float.out @@ -0,0 +1 @@ +["float", 9.223372036854776e+18] diff --git a/tests/phase2/rows/N1.exact-past-2p53.eigs b/tests/phase2/rows/N1.exact-past-2p53.eigs new file mode 100644 index 000000000..9ffc01acf --- /dev/null +++ b/tests/phase2/rows/N1.exact-past-2p53.eigs @@ -0,0 +1,7 @@ +# ROW: N1.exact-past-2p53 +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [9007199254740993, 9223372036854775807] diff --git a/tests/phase2/rows/N1.exact-past-2p53.out b/tests/phase2/rows/N1.exact-past-2p53.out new file mode 100644 index 000000000..8e07d68ac --- /dev/null +++ b/tests/phase2/rows/N1.exact-past-2p53.out @@ -0,0 +1 @@ +[9007199254740993, 9223372036854775807] diff --git a/tests/phase2/rows/N1.fn-dot-fields.eigs b/tests/phase2/rows/N1.fn-dot-fields.eigs new file mode 100644 index 000000000..43ab9882e --- /dev/null +++ b/tests/phase2/rows/N1.fn-dot-fields.eigs @@ -0,0 +1,15 @@ +# ROW: N1.fn-dot-fields +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn setx +# OPS: LOCAL_DOT_SET LOCAL_DOT_GET +define setx(p, q) as: + p.x is q + return p.x +r is null +i is 0.0 +loop while i < 60.0: + r is setx of [{"x": 1}, 9007199254740993] + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N1.fn-dot-fields.out b/tests/phase2/rows/N1.fn-dot-fields.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N1.fn-dot-fields.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N1.fn-float-kind.eigs b/tests/phase2/rows/N1.fn-float-kind.eigs new file mode 100644 index 000000000..848374fb3 --- /dev/null +++ b/tests/phase2/rows/N1.fn-float-kind.eigs @@ -0,0 +1,16 @@ +# ROW: N1.fn-float-kind +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn f +# OPS: NUM_ONE NUM_ZERO MUL ADD +define f(a) as: + t is a * 1.0 + u is t + 0.0 + return u +r is null +i is 0.0 +loop while i < 60.0: + r is f of 2.0 + i is i + 1.0 +print of [num_type of r, r == 2.0] diff --git a/tests/phase2/rows/N1.fn-float-kind.out b/tests/phase2/rows/N1.fn-float-kind.out new file mode 100644 index 000000000..62e3a0482 --- /dev/null +++ b/tests/phase2/rows/N1.fn-float-kind.out @@ -0,0 +1 @@ +["float", true] diff --git a/tests/phase2/rows/N1.fn-float-literals.eigs b/tests/phase2/rows/N1.fn-float-literals.eigs new file mode 100644 index 000000000..fea663f6e --- /dev/null +++ b/tests/phase2/rows/N1.fn-float-literals.eigs @@ -0,0 +1,16 @@ +# ROW: N1.fn-float-literals +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn f +# OPS: NUM_ONE NUM_ZERO MUL ADD +define f(a) as: + t is a * 1.0 + u is t + 0.0 + return u +r is null +i is 0.0 +loop while i < 60.0: + r is f of 2.0 + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N1.fn-float-literals.out b/tests/phase2/rows/N1.fn-float-literals.out new file mode 100644 index 000000000..cd5ac039d --- /dev/null +++ b/tests/phase2/rows/N1.fn-float-literals.out @@ -0,0 +1 @@ +2.0 diff --git a/tests/phase2/rows/N1.fn-int-literal.eigs b/tests/phase2/rows/N1.fn-int-literal.eigs new file mode 100644 index 000000000..0649ac1de --- /dev/null +++ b/tests/phase2/rows/N1.fn-int-literal.eigs @@ -0,0 +1,16 @@ +# ROW: N1.fn-int-literal +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop g INT_ONE +# OPS: INT_ONE +define g(a) as: + warm is 0.5 + t is a + 1 + return t * 2 +r is null +i is 0.0 +loop while i < 60.0: + r is g of 9007199254740992 + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N1.fn-int-literal.out b/tests/phase2/rows/N1.fn-int-literal.out new file mode 100644 index 000000000..07fc2a0e2 --- /dev/null +++ b/tests/phase2/rows/N1.fn-int-literal.out @@ -0,0 +1 @@ +18014398509481986 diff --git a/tests/phase2/rows/N1.fn-int-local.eigs b/tests/phase2/rows/N1.fn-int-local.eigs new file mode 100644 index 000000000..64ed02d91 --- /dev/null +++ b/tests/phase2/rows/N1.fn-int-local.eigs @@ -0,0 +1,15 @@ +# ROW: N1.fn-int-local +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn keep +# OPS: SET_LOCAL POP +define keep(q) as: + t is q + return t +r is null +i is 0.0 +loop while i < 60.0: + r is keep of 9007199254740993 + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N1.fn-int-local.out b/tests/phase2/rows/N1.fn-int-local.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N1.fn-int-local.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N1.hex-wraps.eigs b/tests/phase2/rows/N1.hex-wraps.eigs new file mode 100644 index 000000000..a6734c0fa --- /dev/null +++ b/tests/phase2/rows/N1.hex-wraps.eigs @@ -0,0 +1,7 @@ +# ROW: N1.hex-wraps +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [0x1FFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF] diff --git a/tests/phase2/rows/N1.hex-wraps.out b/tests/phase2/rows/N1.hex-wraps.out new file mode 100644 index 000000000..d8d8f6a9d --- /dev/null +++ b/tests/phase2/rows/N1.hex-wraps.out @@ -0,0 +1 @@ +[-1, -1, 9223372036854775807] diff --git a/tests/phase2/rows/N1.leaf-dot-get.eigs b/tests/phase2/rows/N1.leaf-dot-get.eigs new file mode 100644 index 000000000..b78e45a9c --- /dev/null +++ b/tests/phase2/rows/N1.leaf-dot-get.eigs @@ -0,0 +1,10 @@ +# ROW: N1.leaf-dot-get +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf px +# OPS: LOCAL_DOT_GET +define px(p) as: + return p.x +d is {"x": 9007199254740993} +print of (px of d) diff --git a/tests/phase2/rows/N1.leaf-dot-get.out b/tests/phase2/rows/N1.leaf-dot-get.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-dot-get.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N1.leaf-float-kind.eigs b/tests/phase2/rows/N1.leaf-float-kind.eigs new file mode 100644 index 000000000..483389f54 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-float-kind.eigs @@ -0,0 +1,13 @@ +# ROW: N1.leaf-float-kind +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf onef zerof halff +# OPS: NUM_ONE NUM_ZERO CONST +define onef(x) as: + return 1.0 +define zerof(x) as: + return 0.0 +define halff(x) as: + return 0.5 +print of [num_type of (onef of 7), num_type of (zerof of 7), num_type of (halff of 7)] diff --git a/tests/phase2/rows/N1.leaf-float-kind.out b/tests/phase2/rows/N1.leaf-float-kind.out new file mode 100644 index 000000000..bbf995e58 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-float-kind.out @@ -0,0 +1 @@ +["float", "float", "float"] diff --git a/tests/phase2/rows/N1.leaf-float-literal.eigs b/tests/phase2/rows/N1.leaf-float-literal.eigs new file mode 100644 index 000000000..3aabf5249 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-float-literal.eigs @@ -0,0 +1,13 @@ +# ROW: N1.leaf-float-literal +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf onef zerof halff +# OPS: NUM_ONE NUM_ZERO CONST +define onef(x) as: + return 1.0 +define zerof(x) as: + return 0.0 +define halff(x) as: + return 0.5 +print of [onef of 7, zerof of 7, halff of 7] diff --git a/tests/phase2/rows/N1.leaf-float-literal.out b/tests/phase2/rows/N1.leaf-float-literal.out new file mode 100644 index 000000000..96cc29938 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-float-literal.out @@ -0,0 +1 @@ +[1.0, 0.0, 0.5] diff --git a/tests/phase2/rows/N1.leaf-int-literal.eigs b/tests/phase2/rows/N1.leaf-int-literal.eigs new file mode 100644 index 000000000..c200ef9c8 --- /dev/null +++ b/tests/phase2/rows/N1.leaf-int-literal.eigs @@ -0,0 +1,11 @@ +# ROW: N1.leaf-int-literal +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen big one +# OPS: INT_CONST INT_ONE +define big(x) as: + return 9007199254740993 +define one(x) as: + return 1 +print of [big of 0, one of 0] diff --git a/tests/phase2/rows/N1.leaf-int-literal.out b/tests/phase2/rows/N1.leaf-int-literal.out new file mode 100644 index 000000000..70aa4847c --- /dev/null +++ b/tests/phase2/rows/N1.leaf-int-literal.out @@ -0,0 +1 @@ +[9007199254740993, 1] diff --git a/tests/phase2/rows/N1.literal-kind.eigs b/tests/phase2/rows/N1.literal-kind.eigs new file mode 100644 index 000000000..7f709a76d --- /dev/null +++ b/tests/phase2/rows/N1.literal-kind.eigs @@ -0,0 +1,7 @@ +# ROW: N1.literal-kind +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of 3, num_type of 3.0, num_type of 1e3, num_type of 0x10] diff --git a/tests/phase2/rows/N1.literal-kind.out b/tests/phase2/rows/N1.literal-kind.out new file mode 100644 index 000000000..8b2dde2b3 --- /dev/null +++ b/tests/phase2/rows/N1.literal-kind.out @@ -0,0 +1 @@ +["int", "float", "float", "int"] diff --git a/tests/phase2/rows/N1.negative-past-int64-is-float.eigs b/tests/phase2/rows/N1.negative-past-int64-is-float.eigs new file mode 100644 index 000000000..7a6ddc2e7 --- /dev/null +++ b/tests/phase2/rows/N1.negative-past-int64-is-float.eigs @@ -0,0 +1,9 @@ +# ROW: N1.negative-past-int64-is-float +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +x is -9223372036854775809 +print of [x == -9223372036854775807 - 1, num_type of x] +print of -9223372036854775809 diff --git a/tests/phase2/rows/N1.negative-past-int64-is-float.out b/tests/phase2/rows/N1.negative-past-int64-is-float.out new file mode 100644 index 000000000..376e1c582 --- /dev/null +++ b/tests/phase2/rows/N1.negative-past-int64-is-float.out @@ -0,0 +1,2 @@ +[true, "float"] +-9.223372036854776e+18 diff --git a/tests/phase2/rows/N1.osr-global-int-store.eigs b/tests/phase2/rows/N1.osr-global-int-store.eigs new file mode 100644 index 000000000..de81e9986 --- /dev/null +++ b/tests/phase2/rows/N1.osr-global-int-store.eigs @@ -0,0 +1,15 @@ +# ROW: N1.osr-global-int-store +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr put +# OPS: SET_NAME NUM_ONE ADD GE JUMP_IF_FALSE +x is 0 +define put(big, n) as: + i is 0.0 + loop while true: + x is big + i is i + 1.0 + if i >= n: + return x +print of (put of [9007199254740993, 4.0]) diff --git a/tests/phase2/rows/N1.osr-global-int-store.out b/tests/phase2/rows/N1.osr-global-int-store.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N1.osr-global-int-store.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N1.osr-int-counter.eigs b/tests/phase2/rows/N1.osr-int-counter.eigs new file mode 100644 index 000000000..50969541b --- /dev/null +++ b/tests/phase2/rows/N1.osr-int-counter.eigs @@ -0,0 +1,15 @@ +# ROW: N1.osr-int-counter +# ITEMS: N1 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop count INT_ONE +# OPS: INT_ONE +define count(n) as: + i is 9007199254740990 + k is 0.0 + loop while true: + i is i + 1 + k is k + 1.0 + if k >= n: + return i +print of (count of 10.0) diff --git a/tests/phase2/rows/N1.osr-int-counter.out b/tests/phase2/rows/N1.osr-int-counter.out new file mode 100644 index 000000000..eebb72239 --- /dev/null +++ b/tests/phase2/rows/N1.osr-int-counter.out @@ -0,0 +1 @@ +9007199254741000 diff --git a/tests/phase2/rows/N10.int-or-float.eigs b/tests/phase2/rows/N10.int-or-float.eigs new file mode 100644 index 000000000..6bbd9ab10 --- /dev/null +++ b/tests/phase2/rows/N10.int-or-float.eigs @@ -0,0 +1,7 @@ +# ROW: N10.int-or-float +# ITEMS: N10 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of (num of "3"), num_type of (num of "3.0"), num_type of (num of "9223372036854775808"), num of "9007199254740993"] diff --git a/tests/phase2/rows/N10.int-or-float.out b/tests/phase2/rows/N10.int-or-float.out new file mode 100644 index 000000000..dc375a607 --- /dev/null +++ b/tests/phase2/rows/N10.int-or-float.out @@ -0,0 +1 @@ +["int", "float", "float", 9007199254740993] diff --git a/tests/phase2/rows/N10.partial-refused.eigs b/tests/phase2/rows/N10.partial-refused.eigs new file mode 100644 index 000000000..dfd999ee7 --- /dev/null +++ b/tests/phase2/rows/N10.partial-refused.eigs @@ -0,0 +1,10 @@ +# ROW: N10.partial-refused +# ITEMS: N10 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (num of "12abc") +catch e: + print of e.kind diff --git a/tests/phase2/rows/N10.partial-refused.out b/tests/phase2/rows/N10.partial-refused.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N10.partial-refused.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N10.radix.eigs b/tests/phase2/rows/N10.radix.eigs new file mode 100644 index 000000000..ca309ed9d --- /dev/null +++ b/tests/phase2/rows/N10.radix.eigs @@ -0,0 +1,12 @@ +# ROW: N10.radix +# ITEMS: N10 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num of "0x1F", num of "0X1f"] +for text in ["0b101", "0o17"]: + try: + print of (num of text) + catch e: + print of e.kind diff --git a/tests/phase2/rows/N10.radix.out b/tests/phase2/rows/N10.radix.out new file mode 100644 index 000000000..46c435156 --- /dev/null +++ b/tests/phase2/rows/N10.radix.out @@ -0,0 +1,3 @@ +[31, 31] +value +value diff --git a/tests/phase2/rows/N11.buffer-equal-ieee.out b/tests/phase2/rows/N11.buffer-equal-ieee.out new file mode 100644 index 000000000..9147c0f8c --- /dev/null +++ b/tests/phase2/rows/N11.buffer-equal-ieee.out @@ -0,0 +1,8 @@ +[false, false, true, false, true] +[true, false, true] +[false, -1] +true +[false, false, true, false, true] +[true, false, true] +[false, -1] +true diff --git a/tests/phase2/rows/N11.buffer-equal-ieee.sh b/tests/phase2/rows/N11.buffer-equal-ieee.sh new file mode 100644 index 000000000..2bb23408b --- /dev/null +++ b/tests/phase2/rows/N11.buffer-equal-ieee.sh @@ -0,0 +1,11 @@ +# ROW: N11.buffer-equal-ieee +# ITEMS: N11 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6z-D1: sgd_update stores infinities and NaN in real program buffers. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-buffer-equal.eigs" +done diff --git a/tests/phase2/rows/N11.container-self-equal-ieee.out b/tests/phase2/rows/N11.container-self-equal-ieee.out new file mode 100644 index 000000000..2a8511cf8 --- /dev/null +++ b/tests/phase2/rows/N11.container-self-equal-ieee.out @@ -0,0 +1,10 @@ +true +[false, false, false, true] +[false, true, true, true] +[false, -1] +true +true +[false, false, false, true] +[false, true, true, true] +[false, -1] +true diff --git a/tests/phase2/rows/N11.container-self-equal-ieee.sh b/tests/phase2/rows/N11.container-self-equal-ieee.sh new file mode 100644 index 000000000..0fe51c001 --- /dev/null +++ b/tests/phase2/rows/N11.container-self-equal-ieee.sh @@ -0,0 +1,12 @@ +# ROW: N11.container-self-equal-ieee +# ITEMS: N11 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6z-D2: raw SGD stores NaN; self-comparison follows it through containers. +# The 40-level shared DAG is the cost witness; the row timeout is 30 seconds. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-container-self-equal.eigs" +done diff --git a/tests/phase2/rows/N13.binary-line-int-errors.eigs b/tests/phase2/rows/N13.binary-line-int-errors.eigs new file mode 100644 index 000000000..5cf469dbd --- /dev/null +++ b/tests/phase2/rows/N13.binary-line-int-errors.eigs @@ -0,0 +1,36 @@ +# ROW: N13.binary-line-int-errors +# ITEMS: N13 N3 N4 A +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop binary_add ADD +# OPS: BINARY_LINE ADD +# Sync10 #1425/#2035 + S9-K1: line scopes do not convert exact int operands. +# LINE-NUMBER SENSITIVE: error lines are literal physical operator lines. +define binary_add(a, b) as: + local answer is a + ( + b) + return answer + +define binary_idiv(a, b) as: + local answer is a // ( + b) + return answer + +local i is 0.0 +loop while i < 60.0: + local answer is binary_add of [2, 3] + i is i + 1.0 +try: + local answer is binary_add of [9223372036854775807, 1] +catch e: + print of [e.kind, e.line == 10, num_type of e.line] +try: + local answer is binary_idiv of [-9223372036854775807 - 1, -1] +catch e: + print of [e.kind, e.line == 15, num_type of e.line] +try: + local answer is binary_idiv of [7, 0] +catch e: + print of [e.kind, e.line == 15, num_type of e.line] +local quotient is binary_idiv of [-7, 2] +print of [quotient, num_type of quotient] diff --git a/tests/phase2/rows/N13.binary-line-int-errors.out b/tests/phase2/rows/N13.binary-line-int-errors.out new file mode 100644 index 000000000..7fcc19e37 --- /dev/null +++ b/tests/phase2/rows/N13.binary-line-int-errors.out @@ -0,0 +1,4 @@ +["value", true, "int"] +["value", true, "int"] +["value", true, "int"] +[-4, "int"] diff --git a/tests/phase2/rows/N13.binary-line-parent-restore.eigs b/tests/phase2/rows/N13.binary-line-parent-restore.eigs new file mode 100644 index 000000000..b5edde33e --- /dev/null +++ b/tests/phase2/rows/N13.binary-line-parent-restore.eigs @@ -0,0 +1,27 @@ +# ROW: N13.binary-line-parent-restore +# ITEMS: N13 N5 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn restored_index +# OPS: BINARY_LINE ADD BINARY_LINE_END INDEX_GET +# Sync10 #1425/#2035 + S9-K1: successful native float arithmetic restores +# the operand line before a failing parent index; e.line remains an int. +# LINE-NUMBER SENSITIVE: the parent keeps the right operand's physical line. +define restored_index(items, a, b) as: + local answer is items[a + ( + b)] + return answer + +local items is [0.5, 1.5] +local i is 0.0 +loop while i < 60.0: + local answer is restored_index of [items, 0.0, 1.0] + i is i + 1.0 +local diagnostic is ["missing", false, "missing"] +try: + local answer is restored_index of [null, 0.0, 1.0] +catch e: + diagnostic is [e.kind, e.line == 12, num_type of e.line] +print of diagnostic +# Keep the witnessed call last: its native full return crosses both markers. +print of (restored_index of [items, 0.0, 1.0]) diff --git a/tests/phase2/rows/N13.binary-line-parent-restore.out b/tests/phase2/rows/N13.binary-line-parent-restore.out new file mode 100644 index 000000000..16e8db281 --- /dev/null +++ b/tests/phase2/rows/N13.binary-line-parent-restore.out @@ -0,0 +1,2 @@ +["type_mismatch", true, "int"] +1.5 diff --git a/tests/phase2/rows/N13.vm-buffer-index-data.out b/tests/phase2/rows/N13.vm-buffer-index-data.out new file mode 100644 index 000000000..0b86f3498 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-index-data.out @@ -0,0 +1,6 @@ +["nan", "value", true, 2, false] +["inf", "value", true, 2, false] +["-inf", "value", true, 2, false] +["nan", "value", true, 2, true] +["inf", "value", true, 2, true] +["-inf", "value", true, 2, true] diff --git a/tests/phase2/rows/N13.vm-buffer-index-data.sh b/tests/phase2/rows/N13.vm-buffer-index-data.sh new file mode 100644 index 000000000..e5b82a929 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-index-data.sh @@ -0,0 +1,13 @@ +# S9-K1 (#1758): e.line is an exact int; k1-output-ablation/proof.json. +# ROW: N13.vm-buffer-index-data +# ITEMS: N13 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6d-D1: get_at oracle; 60 finite calls warm the same function first. +# Semantic tier row; native path is separately killed by the JIT-helper plant. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-vm-index.eigs" +done diff --git a/tests/phase2/rows/N13.vm-buffer-int-index-data.out b/tests/phase2/rows/N13.vm-buffer-int-index-data.out new file mode 100644 index 000000000..0b86f3498 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-int-index-data.out @@ -0,0 +1,6 @@ +["nan", "value", true, 2, false] +["inf", "value", true, 2, false] +["-inf", "value", true, 2, false] +["nan", "value", true, 2, true] +["inf", "value", true, 2, true] +["-inf", "value", true, 2, true] diff --git a/tests/phase2/rows/N13.vm-buffer-int-index-data.sh b/tests/phase2/rows/N13.vm-buffer-int-index-data.sh new file mode 100644 index 000000000..bdb01c8c1 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-int-index-data.sh @@ -0,0 +1,13 @@ +# S9-K1 (#1758): e.line is an exact int; k1-output-ablation/proof.json. +# ROW: N13.vm-buffer-int-index-data +# ITEMS: N13 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6d-D1: get_at oracle; 60 finite calls warm the same function first. +# Semantic tier row; native path is separately killed by the JIT-helper plant. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-vm-int-index.eigs" +done diff --git a/tests/phase2/rows/N13.vm-buffer-iter-data.out b/tests/phase2/rows/N13.vm-buffer-iter-data.out new file mode 100644 index 000000000..21155cc28 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-iter-data.out @@ -0,0 +1,6 @@ +["nan", "value", true, 3, false] +["inf", "value", true, 3, false] +["-inf", "value", true, 3, false] +["nan", "value", true, 3, true] +["inf", "value", true, 3, true] +["-inf", "value", true, 3, true] diff --git a/tests/phase2/rows/N13.vm-buffer-iter-data.sh b/tests/phase2/rows/N13.vm-buffer-iter-data.sh new file mode 100644 index 000000000..488d2f0de --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-iter-data.sh @@ -0,0 +1,13 @@ +# S9-K1 (#1758): e.line is an exact int; k1-output-ablation/proof.json. +# ROW: N13.vm-buffer-iter-data +# ITEMS: N13 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6d-D1: get_at oracle; 60 finite calls warm the same function first. +# Semantic tier row; native path is separately killed by the JIT-helper plant. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-vm-iter.eigs" +done diff --git a/tests/phase2/rows/N13.vm-buffer-leaf-index-data.out b/tests/phase2/rows/N13.vm-buffer-leaf-index-data.out new file mode 100644 index 000000000..0b86f3498 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-leaf-index-data.out @@ -0,0 +1,6 @@ +["nan", "value", true, 2, false] +["inf", "value", true, 2, false] +["-inf", "value", true, 2, false] +["nan", "value", true, 2, true] +["inf", "value", true, 2, true] +["-inf", "value", true, 2, true] diff --git a/tests/phase2/rows/N13.vm-buffer-leaf-index-data.sh b/tests/phase2/rows/N13.vm-buffer-leaf-index-data.sh new file mode 100644 index 000000000..208adba7f --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-leaf-index-data.sh @@ -0,0 +1,13 @@ +# S9-K1 (#1758): e.line is an exact int; k1-output-ablation/proof.json. +# ROW: N13.vm-buffer-leaf-index-data +# ITEMS: N13 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6d-D1: get_at oracle; 60 finite calls warm the same function first. +# Semantic tier row; native path is separately killed by the JIT-helper plant. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-vm-leaf-index.eigs" +done diff --git a/tests/phase2/rows/N13.vm-buffer-local-index-data.out b/tests/phase2/rows/N13.vm-buffer-local-index-data.out new file mode 100644 index 000000000..0b86f3498 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-local-index-data.out @@ -0,0 +1,6 @@ +["nan", "value", true, 2, false] +["inf", "value", true, 2, false] +["-inf", "value", true, 2, false] +["nan", "value", true, 2, true] +["inf", "value", true, 2, true] +["-inf", "value", true, 2, true] diff --git a/tests/phase2/rows/N13.vm-buffer-local-index-data.sh b/tests/phase2/rows/N13.vm-buffer-local-index-data.sh new file mode 100644 index 000000000..f48b38bf4 --- /dev/null +++ b/tests/phase2/rows/N13.vm-buffer-local-index-data.sh @@ -0,0 +1,13 @@ +# S9-K1 (#1758): e.line is an exact int; k1-output-ablation/proof.json. +# ROW: N13.vm-buffer-local-index-data +# ITEMS: N13 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6d-D1: get_at oracle; 60 finite calls warm the same function first. +# Semantic tier row; native path is separately killed by the JIT-helper plant. +set -eu +for strict in 0 1; do + EIGS_STRICT=$strict "$1" "$(dirname "$0")/../fixtures/r16z-vm-local-index.eigs" +done diff --git a/tests/phase2/rows/N14.decode-kinds.eigs b/tests/phase2/rows/N14.decode-kinds.eigs new file mode 100644 index 000000000..83d648561 --- /dev/null +++ b/tests/phase2/rows/N14.decode-kinds.eigs @@ -0,0 +1,8 @@ +# ROW: N14.decode-kinds +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +v is json_decode of "[3, 3.0, 9223372036854775807, 18446744073709551615]" +print of [num_type of v[0], num_type of v[1], v[2], num_type of v[3]] diff --git a/tests/phase2/rows/N14.decode-kinds.out b/tests/phase2/rows/N14.decode-kinds.out new file mode 100644 index 000000000..c93856f1d --- /dev/null +++ b/tests/phase2/rows/N14.decode-kinds.out @@ -0,0 +1 @@ +["int", "float", 9223372036854775807, "float"] diff --git a/tests/phase2/rows/N14.encode-kinds.eigs b/tests/phase2/rows/N14.encode-kinds.eigs new file mode 100644 index 000000000..c3c94282f --- /dev/null +++ b/tests/phase2/rows/N14.encode-kinds.eigs @@ -0,0 +1,7 @@ +# ROW: N14.encode-kinds +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (json_encode of [3, 3.0, 0.1, -0.0]) diff --git a/tests/phase2/rows/N14.encode-kinds.out b/tests/phase2/rows/N14.encode-kinds.out new file mode 100644 index 000000000..17fc2a71f --- /dev/null +++ b/tests/phase2/rows/N14.encode-kinds.out @@ -0,0 +1 @@ +[3,3.0,0.1,-0.0] diff --git a/tests/phase2/rows/N14.json-build-int-exact.eigs b/tests/phase2/rows/N14.json-build-int-exact.eigs new file mode 100644 index 000000000..b274e4c85 --- /dev/null +++ b/tests/phase2/rows/N14.json-build-int-exact.eigs @@ -0,0 +1,7 @@ +# ROW: N14.json-build-int-exact +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (json_build of ["small", 3, "big", 9223372036854775807, "min", -9223372036854775807 - 1]) diff --git a/tests/phase2/rows/N14.json-build-int-exact.out b/tests/phase2/rows/N14.json-build-int-exact.out new file mode 100644 index 000000000..3b62ebf15 --- /dev/null +++ b/tests/phase2/rows/N14.json-build-int-exact.out @@ -0,0 +1 @@ +{"small": 3, "big": 9223372036854775807, "min": -9223372036854775808} diff --git a/tests/phase2/rows/N14.json-encode-int-exact.eigs b/tests/phase2/rows/N14.json-encode-int-exact.eigs new file mode 100644 index 000000000..f8f913ff4 --- /dev/null +++ b/tests/phase2/rows/N14.json-encode-int-exact.eigs @@ -0,0 +1,7 @@ +# ROW: N14.json-encode-int-exact +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (json_encode of [3, 9223372036854775807, {"min": -9223372036854775807 - 1}]) diff --git a/tests/phase2/rows/N14.json-encode-int-exact.out b/tests/phase2/rows/N14.json-encode-int-exact.out new file mode 100644 index 000000000..ca429d73b --- /dev/null +++ b/tests/phase2/rows/N14.json-encode-int-exact.out @@ -0,0 +1 @@ +[3,9223372036854775807,{"min":-9223372036854775808}] diff --git a/tests/phase2/rows/N14.json-path-int-exact.eigs b/tests/phase2/rows/N14.json-path-int-exact.eigs new file mode 100644 index 000000000..867a0d3d8 --- /dev/null +++ b/tests/phase2/rows/N14.json-path-int-exact.eigs @@ -0,0 +1,7 @@ +# ROW: N14.json-path-int-exact +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (json_path of ["{\"n\":9223372036854775807}", "n"]) diff --git a/tests/phase2/rows/N14.json-path-int-exact.out b/tests/phase2/rows/N14.json-path-int-exact.out new file mode 100644 index 000000000..2045006ed --- /dev/null +++ b/tests/phase2/rows/N14.json-path-int-exact.out @@ -0,0 +1 @@ +9223372036854775807 diff --git a/tests/phase2/rows/N14.store-exact-digits.out b/tests/phase2/rows/N14.store-exact-digits.out new file mode 100644 index 000000000..601ef4cf6 --- /dev/null +++ b/tests/phase2/rows/N14.store-exact-digits.out @@ -0,0 +1 @@ +[true, true, true] diff --git a/tests/phase2/rows/N14.store-exact-digits.sh b/tests/phase2/rows/N14.store-exact-digits.sh new file mode 100644 index 000000000..7e66acf94 --- /dev/null +++ b/tests/phase2/rows/N14.store-exact-digits.sh @@ -0,0 +1,19 @@ +# ROW: N14.store-exact-digits +# ITEMS: N14 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +set -eu +bin=$1 +scratch=$(mktemp -d /tmp/eigs-r17-store.XXXXXX) +trap 'rm -rf "$scratch"' EXIT +rm -rf "$scratch/db" +cat > "$scratch/p.eigs" <<'EOF' +s is store_open of (env_get of "R17_STORE_PATH") +k is store_put of [s, "c", {"v": 1234567890123456, "f": 0.1 + 0.2, "t": 9007199254740991}] +r is store_get of [s, "c", 1] +print of [r["v"] == 1234567890123456, r["f"] == 0.1 + 0.2, r["t"] == 9007199254740991] +store_close of s +EOF +R17_STORE_PATH="$scratch/db" "$bin" "$scratch/p.eigs" diff --git a/tests/phase2/rows/N2.exact-eq-past-2p53.eigs b/tests/phase2/rows/N2.exact-eq-past-2p53.eigs new file mode 100644 index 000000000..19644de62 --- /dev/null +++ b/tests/phase2/rows/N2.exact-eq-past-2p53.eigs @@ -0,0 +1,7 @@ +# ROW: N2.exact-eq-past-2p53 +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [9007199254740993 == 9007199254740992.0, 9007199254740993 != 9007199254740992.0] diff --git a/tests/phase2/rows/N2.exact-eq-past-2p53.out b/tests/phase2/rows/N2.exact-eq-past-2p53.out new file mode 100644 index 000000000..cb579368c --- /dev/null +++ b/tests/phase2/rows/N2.exact-eq-past-2p53.out @@ -0,0 +1 @@ +[false, true] diff --git a/tests/phase2/rows/N2.exact-order.eigs b/tests/phase2/rows/N2.exact-order.eigs new file mode 100644 index 000000000..f646471fc --- /dev/null +++ b/tests/phase2/rows/N2.exact-order.eigs @@ -0,0 +1,7 @@ +# ROW: N2.exact-order +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [9007199254740993 > 9007199254740992.0, 9223372036854775807 < 9223372036854775808.0] diff --git a/tests/phase2/rows/N2.exact-order.out b/tests/phase2/rows/N2.exact-order.out new file mode 100644 index 000000000..ae6199aee --- /dev/null +++ b/tests/phase2/rows/N2.exact-order.out @@ -0,0 +1 @@ +[true, true] diff --git a/tests/phase2/rows/N2.fn-compare-all.eigs b/tests/phase2/rows/N2.fn-compare-all.eigs new file mode 100644 index 000000000..d7c5257fa --- /dev/null +++ b/tests/phase2/rows/N2.fn-compare-all.eigs @@ -0,0 +1,19 @@ +# ROW: N2.fn-compare-all +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop cmp EQ +# OPS: EQ +define cmp(a, b) as: + e is a == b + n is a != b + g is a > b + l is a <= b + q is a >= b + return [e, n, g, l, q] +r is null +i is 0.0 +loop while i < 60.0: + r is cmp of [9007199254740993, 9007199254740992.0] + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N2.fn-compare-all.out b/tests/phase2/rows/N2.fn-compare-all.out new file mode 100644 index 000000000..059f24083 --- /dev/null +++ b/tests/phase2/rows/N2.fn-compare-all.out @@ -0,0 +1 @@ +[false, true, true, false, true] diff --git a/tests/phase2/rows/N2.fn-exact-compare.eigs b/tests/phase2/rows/N2.fn-exact-compare.eigs new file mode 100644 index 000000000..0198b49c3 --- /dev/null +++ b/tests/phase2/rows/N2.fn-exact-compare.eigs @@ -0,0 +1,15 @@ +# ROW: N2.fn-exact-compare +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop lt LT +# OPS: LT +define lt(a, b) as: + t is a < b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is lt of [9007199254740992, 9007199254740993] + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N2.fn-exact-compare.out b/tests/phase2/rows/N2.fn-exact-compare.out new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/phase2/rows/N2.fn-exact-compare.out @@ -0,0 +1 @@ +true diff --git a/tests/phase2/rows/N2.int-eq-float-kinds.eigs b/tests/phase2/rows/N2.int-eq-float-kinds.eigs new file mode 100644 index 000000000..080fae5ee --- /dev/null +++ b/tests/phase2/rows/N2.int-eq-float-kinds.eigs @@ -0,0 +1,7 @@ +# ROW: N2.int-eq-float-kinds +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1 == 1.0, 1 != 1.0, num_type of 1, num_type of 1.0] diff --git a/tests/phase2/rows/N2.int-eq-float-kinds.out b/tests/phase2/rows/N2.int-eq-float-kinds.out new file mode 100644 index 000000000..99105c483 --- /dev/null +++ b/tests/phase2/rows/N2.int-eq-float-kinds.out @@ -0,0 +1 @@ +[true, false, "int", "float"] diff --git a/tests/phase2/rows/N2.membership-exact.eigs b/tests/phase2/rows/N2.membership-exact.eigs new file mode 100644 index 000000000..1351c22b3 --- /dev/null +++ b/tests/phase2/rows/N2.membership-exact.eigs @@ -0,0 +1,7 @@ +# ROW: N2.membership-exact +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [list_contains of [[9007199254740993], 9007199254740992.0], list_index_of of [[1, 9007199254740993], 9007199254740992.0]] diff --git a/tests/phase2/rows/N2.membership-exact.out b/tests/phase2/rows/N2.membership-exact.out new file mode 100644 index 000000000..b24713b9a --- /dev/null +++ b/tests/phase2/rows/N2.membership-exact.out @@ -0,0 +1 @@ +[false, -1] diff --git a/tests/phase2/rows/N2.sort-by-exact.eigs b/tests/phase2/rows/N2.sort-by-exact.eigs new file mode 100644 index 000000000..a2c184d5f --- /dev/null +++ b/tests/phase2/rows/N2.sort-by-exact.eigs @@ -0,0 +1,11 @@ +# ROW: N2.sort-by-exact +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +define key(r) as: + return r.k +xs is [{"id": "high", "k": 9007199254740993}, {"id": "low", "k": 9007199254740991}, {"id": "tie-a", "k": 9007199254740992.0}, {"id": "tie-b", "k": 9007199254740992}] +ys is sort_by of [xs, key] +print of [ys[0].id, ys[1].id, ys[2].id, ys[3].id] diff --git a/tests/phase2/rows/N2.sort-by-exact.out b/tests/phase2/rows/N2.sort-by-exact.out new file mode 100644 index 000000000..02425217e --- /dev/null +++ b/tests/phase2/rows/N2.sort-by-exact.out @@ -0,0 +1 @@ +["low", "tie-a", "tie-b", "high"] diff --git a/tests/phase2/rows/N2.sort-exact.eigs b/tests/phase2/rows/N2.sort-exact.eigs new file mode 100644 index 000000000..b9ed3c83f --- /dev/null +++ b/tests/phase2/rows/N2.sort-exact.eigs @@ -0,0 +1,7 @@ +# ROW: N2.sort-exact +# ITEMS: N2 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (sort of [9007199254740993, 9007199254740992, -1]) diff --git a/tests/phase2/rows/N2.sort-exact.out b/tests/phase2/rows/N2.sort-exact.out new file mode 100644 index 000000000..07981deda --- /dev/null +++ b/tests/phase2/rows/N2.sort-exact.out @@ -0,0 +1 @@ +[-1, 9007199254740992, 9007199254740993] diff --git a/tests/phase2/rows/N3.exact-product.eigs b/tests/phase2/rows/N3.exact-product.eigs new file mode 100644 index 000000000..da046afc7 --- /dev/null +++ b/tests/phase2/rows/N3.exact-product.eigs @@ -0,0 +1,7 @@ +# ROW: N3.exact-product +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [3037000499 * 3037000499, 4503599627370496 * 2 + 1] diff --git a/tests/phase2/rows/N3.exact-product.out b/tests/phase2/rows/N3.exact-product.out new file mode 100644 index 000000000..01df45d41 --- /dev/null +++ b/tests/phase2/rows/N3.exact-product.out @@ -0,0 +1 @@ +[9223372030926249001, 9007199254740993] diff --git a/tests/phase2/rows/N3.fn-arith-exact.eigs b/tests/phase2/rows/N3.fn-arith-exact.eigs new file mode 100644 index 000000000..495b8e55a --- /dev/null +++ b/tests/phase2/rows/N3.fn-arith-exact.eigs @@ -0,0 +1,17 @@ +# ROW: N3.fn-arith-exact +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop f MUL +# OPS: BINARY_LINE MUL +define f(a, b) as: + t is a * ( + b) + u is t + a + return u - b +r is null +i is 0.0 +loop while i < 60.0: + r is f of [94906267, 94906267] + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N3.fn-arith-exact.out b/tests/phase2/rows/N3.fn-arith-exact.out new file mode 100644 index 000000000..0be214cb1 --- /dev/null +++ b/tests/phase2/rows/N3.fn-arith-exact.out @@ -0,0 +1 @@ +9007199515875289 diff --git a/tests/phase2/rows/N3.int-ops-kind.eigs b/tests/phase2/rows/N3.int-ops-kind.eigs new file mode 100644 index 000000000..581c1aa3b --- /dev/null +++ b/tests/phase2/rows/N3.int-ops-kind.eigs @@ -0,0 +1,7 @@ +# ROW: N3.int-ops-kind +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of (2 + 3), num_type of (7 - 9), num_type of (6 * 7), num_type of (2 + 0.5)] diff --git a/tests/phase2/rows/N3.int-ops-kind.out b/tests/phase2/rows/N3.int-ops-kind.out new file mode 100644 index 000000000..156bf0616 --- /dev/null +++ b/tests/phase2/rows/N3.int-ops-kind.out @@ -0,0 +1 @@ +["int", "int", "int", "float"] diff --git a/tests/phase2/rows/N3.leaf-add-exact.eigs b/tests/phase2/rows/N3.leaf-add-exact.eigs new file mode 100644 index 000000000..7e89ac2f8 --- /dev/null +++ b/tests/phase2/rows/N3.leaf-add-exact.eigs @@ -0,0 +1,9 @@ +# ROW: N3.leaf-add-exact +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen add2 +# OPS: ADD +define add2(a, b) as: + return a + b +print of (add2 of [9007199254740992, 1]) diff --git a/tests/phase2/rows/N3.leaf-add-exact.out b/tests/phase2/rows/N3.leaf-add-exact.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N3.leaf-add-exact.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N3.leaf-mul-exact.eigs b/tests/phase2/rows/N3.leaf-mul-exact.eigs new file mode 100644 index 000000000..c712ccc00 --- /dev/null +++ b/tests/phase2/rows/N3.leaf-mul-exact.eigs @@ -0,0 +1,9 @@ +# ROW: N3.leaf-mul-exact +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen sq +# OPS: MUL +define sq(x) as: + return x * x +print of (sq of 94906267) diff --git a/tests/phase2/rows/N3.leaf-mul-exact.out b/tests/phase2/rows/N3.leaf-mul-exact.out new file mode 100644 index 000000000..0be214cb1 --- /dev/null +++ b/tests/phase2/rows/N3.leaf-mul-exact.out @@ -0,0 +1 @@ +9007199515875289 diff --git a/tests/phase2/rows/N3.osr-module-sum.eigs b/tests/phase2/rows/N3.osr-module-sum.eigs new file mode 100644 index 000000000..21f98eb40 --- /dev/null +++ b/tests/phase2/rows/N3.osr-module-sum.eigs @@ -0,0 +1,15 @@ +# ROW: N3.osr-module-sum +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop ADD +# OPS: ADD +acc is 9007199254740000 +step is 1 +i is 0.0 +loop while true: + acc is acc + step + i is i + 1.0 + if i >= 1000.0: + print of acc + return acc diff --git a/tests/phase2/rows/N3.osr-module-sum.out b/tests/phase2/rows/N3.osr-module-sum.out new file mode 100644 index 000000000..eebb72239 --- /dev/null +++ b/tests/phase2/rows/N3.osr-module-sum.out @@ -0,0 +1 @@ +9007199254741000 diff --git a/tests/phase2/rows/N3.osr-sum-exact.eigs b/tests/phase2/rows/N3.osr-sum-exact.eigs new file mode 100644 index 000000000..11ad8facc --- /dev/null +++ b/tests/phase2/rows/N3.osr-sum-exact.eigs @@ -0,0 +1,15 @@ +# ROW: N3.osr-sum-exact +# ITEMS: N3 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr-stop total ADD +# OPS: ADD +define total(n, step) as: + acc is 9007199254740000 + i is 0.0 + loop while true: + acc is acc + step + i is i + 1.0 + if i >= n: + return acc +print of (total of [1000.0, 1]) diff --git a/tests/phase2/rows/N3.osr-sum-exact.out b/tests/phase2/rows/N3.osr-sum-exact.out new file mode 100644 index 000000000..eebb72239 --- /dev/null +++ b/tests/phase2/rows/N3.osr-sum-exact.out @@ -0,0 +1 @@ +9007199254741000 diff --git a/tests/phase2/rows/N4.compound-float-floordiv-boundaries.eigs b/tests/phase2/rows/N4.compound-float-floordiv-boundaries.eigs new file mode 100644 index 000000000..4785442aa --- /dev/null +++ b/tests/phase2/rows/N4.compound-float-floordiv-boundaries.eigs @@ -0,0 +1,14 @@ +# ROW: N4.compound-float-floordiv-boundaries +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +x is 1.0 +x //= 0.1 +d is {"x": 1} +d.x //= 0.1 +xs is [1e-300, -1e-300] +xs[0] //= -1e300 +xs[1] //= 1e300 +print of [x, d.x, xs, num_type of x, num_type of d.x] diff --git a/tests/phase2/rows/N4.compound-float-floordiv-boundaries.out b/tests/phase2/rows/N4.compound-float-floordiv-boundaries.out new file mode 100644 index 000000000..b5da3a842 --- /dev/null +++ b/tests/phase2/rows/N4.compound-float-floordiv-boundaries.out @@ -0,0 +1 @@ +[9.0, 9.0, [-1.0, -1.0], "float", "float"] diff --git a/tests/phase2/rows/N4.compound-floordiv.eigs b/tests/phase2/rows/N4.compound-floordiv.eigs new file mode 100644 index 000000000..d3bbda151 --- /dev/null +++ b/tests/phase2/rows/N4.compound-floordiv.eigs @@ -0,0 +1,14 @@ +# ROW: N4.compound-floordiv +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +x is -7 +x //= 2 +d is {"x": 7.5} +d.x //= -2 +xs is [7, -7.5] +xs[0] //= -2 +xs[1] //= 2 +print of [x, d.x, xs, num_type of x, num_type of d.x] diff --git a/tests/phase2/rows/N4.compound-floordiv.out b/tests/phase2/rows/N4.compound-floordiv.out new file mode 100644 index 000000000..a88b09bc0 --- /dev/null +++ b/tests/phase2/rows/N4.compound-floordiv.out @@ -0,0 +1 @@ +[-4, -4.0, [-4, -4.0], "int", "float"] diff --git a/tests/phase2/rows/N4.div-always-float.eigs b/tests/phase2/rows/N4.div-always-float.eigs new file mode 100644 index 000000000..84c3f3629 --- /dev/null +++ b/tests/phase2/rows/N4.div-always-float.eigs @@ -0,0 +1,7 @@ +# ROW: N4.div-always-float +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [7 / 2, 6 / 2, num_type of (6 / 2)] diff --git a/tests/phase2/rows/N4.div-always-float.out b/tests/phase2/rows/N4.div-always-float.out new file mode 100644 index 000000000..e4dcdb274 --- /dev/null +++ b/tests/phase2/rows/N4.div-always-float.out @@ -0,0 +1 @@ +[3.5, 3.0, "float"] diff --git a/tests/phase2/rows/N4.float-floordiv-rounding.eigs b/tests/phase2/rows/N4.float-floordiv-rounding.eigs new file mode 100644 index 000000000..85f442101 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-rounding.eigs @@ -0,0 +1,7 @@ +# ROW: N4.float-floordiv-rounding +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1.0 // 0.1, 1 // 0.1, -1.0 // -0.1, 7.5 // 0.1, 1.0 // -0.1, 3.0 // -0.1, num_type of (1 // 0.1)] diff --git a/tests/phase2/rows/N4.float-floordiv-rounding.out b/tests/phase2/rows/N4.float-floordiv-rounding.out new file mode 100644 index 000000000..be0c5f218 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-rounding.out @@ -0,0 +1 @@ +[9.0, 9.0, 9.0, 74.0, -10.0, -30.0, "float"] diff --git a/tests/phase2/rows/N4.float-floordiv-underflow.eigs b/tests/phase2/rows/N4.float-floordiv-underflow.eigs new file mode 100644 index 000000000..1edbed832 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-underflow.eigs @@ -0,0 +1,7 @@ +# ROW: N4.float-floordiv-underflow +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1e-300 // -1e300, -1e-300 // 1e300, 1e-300 // 1e300, num_type of (1e-300 // -1e300)] diff --git a/tests/phase2/rows/N4.float-floordiv-underflow.out b/tests/phase2/rows/N4.float-floordiv-underflow.out new file mode 100644 index 000000000..080cfd107 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-underflow.out @@ -0,0 +1 @@ +[-1.0, -1.0, 0.0, "float"] diff --git a/tests/phase2/rows/N4.float-floordiv-zero-raises.eigs b/tests/phase2/rows/N4.float-floordiv-zero-raises.eigs new file mode 100644 index 000000000..21371e513 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-zero-raises.eigs @@ -0,0 +1,11 @@ +# ROW: N4.float-floordiv-zero-raises +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +for pair in [[1.0, 0], [0.0, 0.0], [7, 0.0], [1.0, -0.0]]: + try: + print of (pair[0] // pair[1]) + catch e: + print of [e.kind, e.message] diff --git a/tests/phase2/rows/N4.float-floordiv-zero-raises.out b/tests/phase2/rows/N4.float-floordiv-zero-raises.out new file mode 100644 index 000000000..73f961180 --- /dev/null +++ b/tests/phase2/rows/N4.float-floordiv-zero-raises.out @@ -0,0 +1,4 @@ +["value", "division by zero"] +["value", "division by zero"] +["value", "division by zero"] +["value", "division by zero"] diff --git a/tests/phase2/rows/N4.floor-mod.eigs b/tests/phase2/rows/N4.floor-mod.eigs new file mode 100644 index 000000000..c03b313fa --- /dev/null +++ b/tests/phase2/rows/N4.floor-mod.eigs @@ -0,0 +1,7 @@ +# ROW: N4.floor-mod +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [-7 % 3, 7 % -3, -7.5 % 2, 7 % 3] diff --git a/tests/phase2/rows/N4.floor-mod.out b/tests/phase2/rows/N4.floor-mod.out new file mode 100644 index 000000000..c67209e7a --- /dev/null +++ b/tests/phase2/rows/N4.floor-mod.out @@ -0,0 +1 @@ +[2, -2, 0.5, 1] diff --git a/tests/phase2/rows/N4.floordiv.eigs b/tests/phase2/rows/N4.floordiv.eigs new file mode 100644 index 000000000..35dcf9fb9 --- /dev/null +++ b/tests/phase2/rows/N4.floordiv.eigs @@ -0,0 +1,7 @@ +# ROW: N4.floordiv +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [7 // 2, -7 // 2, 7.5 // 2, num_type of (7 // 2), num_type of (7.5 // 2)] diff --git a/tests/phase2/rows/N4.floordiv.out b/tests/phase2/rows/N4.floordiv.out new file mode 100644 index 000000000..43a8bf5f2 --- /dev/null +++ b/tests/phase2/rows/N4.floordiv.out @@ -0,0 +1 @@ +[3, -4, 3.0, "int", "float"] diff --git a/tests/phase2/rows/N4.fn-div-float.eigs b/tests/phase2/rows/N4.fn-div-float.eigs new file mode 100644 index 000000000..f26e241c1 --- /dev/null +++ b/tests/phase2/rows/N4.fn-div-float.eigs @@ -0,0 +1,15 @@ +# ROW: N4.fn-div-float +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop d DIV +# OPS: DIV +define d(a, b) as: + t is a / b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is d of [6, 2] + i is i + 1.0 +print of r diff --git a/tests/phase2/rows/N4.fn-div-float.out b/tests/phase2/rows/N4.fn-div-float.out new file mode 100644 index 000000000..9f55b2ccb --- /dev/null +++ b/tests/phase2/rows/N4.fn-div-float.out @@ -0,0 +1 @@ +3.0 diff --git a/tests/phase2/rows/N4.fn-float-floordiv-rounding.eigs b/tests/phase2/rows/N4.fn-float-floordiv-rounding.eigs new file mode 100644 index 000000000..7bd9e3a9c --- /dev/null +++ b/tests/phase2/rows/N4.fn-float-floordiv-rounding.eigs @@ -0,0 +1,16 @@ +# ROW: N4.fn-float-floordiv-rounding +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop rd IDIV +# OPS: IDIV +define rd(a, b) as: + warm is 0.5 + t is a // b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is rd of [1.0, 0.1] + i is i + 1.0 +print of [r, rd of [1, 0.1], rd of [-1.0, -0.1], rd of [7.5, 0.1], rd of [3.0, -0.1], num_type of r] diff --git a/tests/phase2/rows/N4.fn-float-floordiv-rounding.out b/tests/phase2/rows/N4.fn-float-floordiv-rounding.out new file mode 100644 index 000000000..f0d17590a --- /dev/null +++ b/tests/phase2/rows/N4.fn-float-floordiv-rounding.out @@ -0,0 +1 @@ +[9.0, 9.0, 9.0, 74.0, -30.0, "float"] diff --git a/tests/phase2/rows/N4.fn-float-floordiv-underflow.eigs b/tests/phase2/rows/N4.fn-float-floordiv-underflow.eigs new file mode 100644 index 000000000..4cb505b4f --- /dev/null +++ b/tests/phase2/rows/N4.fn-float-floordiv-underflow.eigs @@ -0,0 +1,16 @@ +# ROW: N4.fn-float-floordiv-underflow +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop ud IDIV +# OPS: IDIV +define ud(a, b) as: + warm is 0.5 + t is a // b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is ud of [1e-300, -1e300] + i is i + 1.0 +print of [r, ud of [-1e-300, 1e300], num_type of r] diff --git a/tests/phase2/rows/N4.fn-float-floordiv-underflow.out b/tests/phase2/rows/N4.fn-float-floordiv-underflow.out new file mode 100644 index 000000000..5d21c00a3 --- /dev/null +++ b/tests/phase2/rows/N4.fn-float-floordiv-underflow.out @@ -0,0 +1 @@ +[-1.0, -1.0, "float"] diff --git a/tests/phase2/rows/N4.fn-floor-mod.eigs b/tests/phase2/rows/N4.fn-floor-mod.eigs new file mode 100644 index 000000000..af80c94ff --- /dev/null +++ b/tests/phase2/rows/N4.fn-floor-mod.eigs @@ -0,0 +1,16 @@ +# ROW: N4.fn-floor-mod +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop m MOD +# OPS: MOD +define m(a, b) as: + warm is 0.5 + t is a % b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is m of [-7, 3] + i is i + 1.0 +print of [r, m of [-7.5, 2]] diff --git a/tests/phase2/rows/N4.fn-floor-mod.out b/tests/phase2/rows/N4.fn-floor-mod.out new file mode 100644 index 000000000..ede3ad8ce --- /dev/null +++ b/tests/phase2/rows/N4.fn-floor-mod.out @@ -0,0 +1 @@ +[2, 0.5] diff --git a/tests/phase2/rows/N4.fn-floordiv.eigs b/tests/phase2/rows/N4.fn-floordiv.eigs new file mode 100644 index 000000000..f516e14d7 --- /dev/null +++ b/tests/phase2/rows/N4.fn-floordiv.eigs @@ -0,0 +1,16 @@ +# ROW: N4.fn-floordiv +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop d IDIV +# OPS: IDIV +define d(a, b) as: + warm is 0.5 + t is a // b + return t +r is null +i is 0.0 +loop while i < 60.0: + r is d of [-7.5, 2] + i is i + 1.0 +print of [r, num_type of r] diff --git a/tests/phase2/rows/N4.fn-floordiv.out b/tests/phase2/rows/N4.fn-floordiv.out new file mode 100644 index 000000000..7bef99a48 --- /dev/null +++ b/tests/phase2/rows/N4.fn-floordiv.out @@ -0,0 +1 @@ +[-4.0, "float"] diff --git a/tests/phase2/rows/N4.int-floordiv-zero-raises.eigs b/tests/phase2/rows/N4.int-floordiv-zero-raises.eigs new file mode 100644 index 000000000..128a6923b --- /dev/null +++ b/tests/phase2/rows/N4.int-floordiv-zero-raises.eigs @@ -0,0 +1,10 @@ +# ROW: N4.int-floordiv-zero-raises +# ITEMS: N4 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (7 // 0) +catch e: + print of e.kind diff --git a/tests/phase2/rows/N4.int-floordiv-zero-raises.out b/tests/phase2/rows/N4.int-floordiv-zero-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N4.int-floordiv-zero-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N4b.abs-min-raises.eigs b/tests/phase2/rows/N4b.abs-min-raises.eigs new file mode 100644 index 000000000..87ee5b5ec --- /dev/null +++ b/tests/phase2/rows/N4b.abs-min-raises.eigs @@ -0,0 +1,11 @@ +# ROW: N4b.abs-min-raises +# ITEMS: N4b +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +m is -9223372036854775807 - 1 +try: + print of (abs of m) +catch e: + print of [e.kind, (index_of of [e.message, "abs"]) >= 0] diff --git a/tests/phase2/rows/N4b.abs-min-raises.out b/tests/phase2/rows/N4b.abs-min-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/N4b.abs-min-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.eigs b/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.eigs new file mode 100644 index 000000000..5d5c402b1 --- /dev/null +++ b/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.eigs @@ -0,0 +1,11 @@ +# ROW: N4b.floordiv-min-by-neg1-raises +# ITEMS: N4b +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +m is -9223372036854775807 - 1 +try: + print of (m // -1) +catch e: + print of [e.kind, (index_of of [e.message, "//"]) >= 0] diff --git a/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.out b/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/N4b.floordiv-min-by-neg1-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/N4b.fn-neg-min.eigs b/tests/phase2/rows/N4b.fn-neg-min.eigs new file mode 100644 index 000000000..78417f59e --- /dev/null +++ b/tests/phase2/rows/N4b.fn-neg-min.eigs @@ -0,0 +1,19 @@ +# ROW: N4b.fn-neg-min +# ITEMS: N4b +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop n NEG +# OPS: NEG +define n(a) as: + t is -a + return t +r is null +i is 0.0 +loop while i < 60.0: + r is n of 2 + i is i + 1.0 +m is -9223372036854775807 - 1 +try: + print of (n of m) +catch e: + print of e.kind diff --git a/tests/phase2/rows/N4b.fn-neg-min.out b/tests/phase2/rows/N4b.fn-neg-min.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N4b.fn-neg-min.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N4b.min-mod-neg1-zero.eigs b/tests/phase2/rows/N4b.min-mod-neg1-zero.eigs new file mode 100644 index 000000000..3c9ea8013 --- /dev/null +++ b/tests/phase2/rows/N4b.min-mod-neg1-zero.eigs @@ -0,0 +1,8 @@ +# ROW: N4b.min-mod-neg1-zero +# ITEMS: N4b +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +m is -9223372036854775807 - 1 +print of [m % -1, num_type of (m % -1)] diff --git a/tests/phase2/rows/N4b.min-mod-neg1-zero.out b/tests/phase2/rows/N4b.min-mod-neg1-zero.out new file mode 100644 index 000000000..d44312f4b --- /dev/null +++ b/tests/phase2/rows/N4b.min-mod-neg1-zero.out @@ -0,0 +1 @@ +[0, "int"] diff --git a/tests/phase2/rows/N4b.neg-min-raises.eigs b/tests/phase2/rows/N4b.neg-min-raises.eigs new file mode 100644 index 000000000..dab9a0c1b --- /dev/null +++ b/tests/phase2/rows/N4b.neg-min-raises.eigs @@ -0,0 +1,11 @@ +# ROW: N4b.neg-min-raises +# ITEMS: N4b +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +m is -9223372036854775807 - 1 +try: + print of (-m) +catch e: + print of [e.kind, (index_of of [e.message, "-"]) >= 0] diff --git a/tests/phase2/rows/N4b.neg-min-raises.out b/tests/phase2/rows/N4b.neg-min-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/N4b.neg-min-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/N5.audio-channel-domain.eigs b/tests/phase2/rows/N5.audio-channel-domain.eigs new file mode 100644 index 000000000..7da3dfb83 --- /dev/null +++ b/tests/phase2/rows/N5.audio-channel-domain.eigs @@ -0,0 +1,29 @@ +# ROW: N5.audio-channel-domain +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6e-D1, #1922 comment 6355101247: pre-registered before migration. +define probe_channel(fn, close_fn, label) as: + for count in [0, 256, 257, -1, 1.5]: + try: + local result is fn of [44100, count] + print of [label, count, "accepted"] + catch e: + local domain is contains of [e.message, "outside [1, 255]"] + local fractional is contains of [e.message, "no integer representation"] + print of [label, count, e.kind, domain or fractional] + close_fn of null + for count in [255, 1]: + try: + local result is fn of [44100, count] + print of [label, count, "reader-pass", true] + catch e: + local domain is contains of [e.message, "outside [1, 255]"] + local fractional is contains of [e.message, "no integer representation"] + print of [label, count, "reader-pass", not (domain or fractional)] + close_fn of null +probe_channel of [audio_open, audio_close, "audio_open"] +probe_channel of [audio_capture_open, audio_capture_close, "audio_capture_open"] +probe_channel of [audio_stream_open, audio_stream_close, "audio_stream_open"] diff --git a/tests/phase2/rows/N5.audio-channel-domain.out b/tests/phase2/rows/N5.audio-channel-domain.out new file mode 100644 index 000000000..01fd2b53b --- /dev/null +++ b/tests/phase2/rows/N5.audio-channel-domain.out @@ -0,0 +1,21 @@ +["audio_open", 0, "value", true] +["audio_open", 256, "value", true] +["audio_open", 257, "value", true] +["audio_open", -1, "value", true] +["audio_open", 1.5, "value", true] +["audio_open", 255, "reader-pass", true] +["audio_open", 1, "reader-pass", true] +["audio_capture_open", 0, "value", true] +["audio_capture_open", 256, "value", true] +["audio_capture_open", 257, "value", true] +["audio_capture_open", -1, "value", true] +["audio_capture_open", 1.5, "value", true] +["audio_capture_open", 255, "reader-pass", true] +["audio_capture_open", 1, "reader-pass", true] +["audio_stream_open", 0, "value", true] +["audio_stream_open", 256, "value", true] +["audio_stream_open", 257, "value", true] +["audio_stream_open", -1, "value", true] +["audio_stream_open", 1.5, "value", true] +["audio_stream_open", 255, "reader-pass", true] +["audio_stream_open", 1, "reader-pass", true] diff --git a/tests/phase2/rows/N5.bitwise-fraction-raises.eigs b/tests/phase2/rows/N5.bitwise-fraction-raises.eigs new file mode 100644 index 000000000..996f9d0f3 --- /dev/null +++ b/tests/phase2/rows/N5.bitwise-fraction-raises.eigs @@ -0,0 +1,10 @@ +# ROW: N5.bitwise-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (1.5 | 0) +catch e: + print of e.kind diff --git a/tests/phase2/rows/N5.bitwise-fraction-raises.out b/tests/phase2/rows/N5.bitwise-fraction-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N5.bitwise-fraction-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N5.gather-index-fraction-raises.eigs b/tests/phase2/rows/N5.gather-index-fraction-raises.eigs new file mode 100644 index 000000000..7ba7caa23 --- /dev/null +++ b/tests/phase2/rows/N5.gather-index-fraction-raises.eigs @@ -0,0 +1,21 @@ +# ROW: N5.gather-index-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +t is buffer of [2, 3] +t[0] is 10 +t[1] is 11 +t[2] is 12 +t[3] is 20 +t[4] is 21 +t[5] is 22 +ib is buffer of 2 +ib[0] is 1.5 +ib[1] is 2.0 +try: + g is gather of [t, ib] + print of [g[0], g[1]] +catch e: + print of e.kind diff --git a/tests/phase2/rows/N5.gather-index-fraction-raises.out b/tests/phase2/rows/N5.gather-index-fraction-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N5.gather-index-fraction-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N5.http-port-before-config.out b/tests/phase2/rows/N5.http-port-before-config.out new file mode 100644 index 000000000..44eefec3d --- /dev/null +++ b/tests/phase2/rows/N5.http-port-before-config.out @@ -0,0 +1,4 @@ +["http_early_bind", "value"] +route registered +["http_serve", "value"] +route registered diff --git a/tests/phase2/rows/N5.http-port-before-config.sh b/tests/phase2/rows/N5.http-port-before-config.sh new file mode 100644 index 000000000..de20c9cc8 --- /dev/null +++ b/tests/phase2/rows/N5.http-port-before-config.sh @@ -0,0 +1,26 @@ +# ROW: N5.http-port-before-config +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# PROFILE: server +# C6: invalid effective port refuses before changing configuration state. +set -eu +binary=$1 +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +cat > "$tmp/port.eigs" <<'EIGS' +for name in ["http_early_bind", "http_serve"]: + try: + if name == "http_serve": + http_serve of 0 + else: + http_early_bind of 0 + print of "accepted-invalid-port" + exit of 0 + catch e: + print of [name, e.kind] + print of (http_route of ["GET", "/" + name, "ok"]) +EIGS +PORT=65536 "$binary" "$tmp/port.eigs" diff --git a/tests/phase2/rows/N5.integral-float-converts.eigs b/tests/phase2/rows/N5.integral-float-converts.eigs new file mode 100644 index 000000000..548e13bcb --- /dev/null +++ b/tests/phase2/rows/N5.integral-float-converts.eigs @@ -0,0 +1,7 @@ +# ROW: N5.integral-float-converts +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [6.0 | 1, num_type of (6.0 | 1), (list_slice of [[1, 2, 3], 1.0, 2.0])] diff --git a/tests/phase2/rows/N5.integral-float-converts.out b/tests/phase2/rows/N5.integral-float-converts.out new file mode 100644 index 000000000..abe7f6482 --- /dev/null +++ b/tests/phase2/rows/N5.integral-float-converts.out @@ -0,0 +1 @@ +[7, "int", [2]] diff --git a/tests/phase2/rows/N5.leaf-buffer-index.eigs b/tests/phase2/rows/N5.leaf-buffer-index.eigs new file mode 100644 index 000000000..db9e76b58 --- /dev/null +++ b/tests/phase2/rows/N5.leaf-buffer-index.eigs @@ -0,0 +1,10 @@ +# ROW: N5.leaf-buffer-index +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: L +# WITNESS: leaf-gen bat +# OPS: INDEX_GET +define bat(b, i) as: + return b[i] +b is buffer of 3 +print of (bat of [b, 1]) diff --git a/tests/phase2/rows/N5.leaf-buffer-index.out b/tests/phase2/rows/N5.leaf-buffer-index.out new file mode 100644 index 000000000..ba66466c2 --- /dev/null +++ b/tests/phase2/rows/N5.leaf-buffer-index.out @@ -0,0 +1 @@ +0.0 diff --git a/tests/phase2/rows/N5.net-send-byte-raises.out b/tests/phase2/rows/N5.net-send-byte-raises.out new file mode 100644 index 000000000..3bca63229 --- /dev/null +++ b/tests/phase2/rows/N5.net-send-byte-raises.out @@ -0,0 +1,6 @@ +["buffer", 256, "value"] +["list", 256, "value"] +["buffer", 1.5, "value"] +["list", 1.5, "value"] +[2, "int"] +ok diff --git a/tests/phase2/rows/N5.net-send-byte-raises.sh b/tests/phase2/rows/N5.net-send-byte-raises.sh new file mode 100644 index 000000000..f7ffd3a91 --- /dev/null +++ b/tests/phase2/rows/N5.net-send-byte-raises.sh @@ -0,0 +1,41 @@ +# ROW: N5.net-send-byte-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# PROFILE: server +# R1.6d-D1 / RULINGS C7: invalid byte payloads send no prefix. +# R1.6f-D2: the raw stored-byte socketpair fixture is the mechanical gate +# gate.N5.net-byte-boundary (tools/phase2_readers.sh), built once outside +# this row's per-tier 30 s timeout. +set -eu +binary=$1 +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +cat > "$tmp/bytes.eigs" <<'EIGS' +listener is net_listen of 0 +client is net_dial of ["127.0.0.1", net_port of listener] +peer is net_accept of [listener, 2000] +for bad in [256, 1.5]: + b is buffer of 2 + b[0] is 65 + b[1] is bad + try: + answer is net_send of [client, b] + print of ["buffer", bad, "accepted"] + catch e: + print of ["buffer", bad, e.kind] + try: + answer is net_send of [client, [65, bad]] + print of ["list", bad, "accepted"] + catch e: + print of ["list", bad, e.kind] +n is net_send of [client, [111.0, 107.0]] +print of [n, num_type of n] +print of (str_from_bytes of (net_recv of [peer, 64, 2000])) +net_close of client +net_close of peer +net_close of listener +EIGS +"$binary" "$tmp/bytes.eigs" diff --git a/tests/phase2/rows/N5.osr-index-set.eigs b/tests/phase2/rows/N5.osr-index-set.eigs new file mode 100644 index 000000000..110b969c0 --- /dev/null +++ b/tests/phase2/rows/N5.osr-index-set.eigs @@ -0,0 +1,15 @@ +# ROW: N5.osr-index-set +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: O +# WITNESS: osr fill +# OPS: INDEX_SET NUM_ONE ADD GE JUMP_IF_FALSE SET_LOCAL POP +define fill(big, n) as: + xs is [0, 0, 0, 0] + i is 0.0 + loop while true: + xs[i] is big + i is i + 1.0 + if i >= n: + return xs +print of (fill of [9007199254740993, 4.0]) diff --git a/tests/phase2/rows/N5.osr-index-set.out b/tests/phase2/rows/N5.osr-index-set.out new file mode 100644 index 000000000..317c794e7 --- /dev/null +++ b/tests/phase2/rows/N5.osr-index-set.out @@ -0,0 +1 @@ +[9007199254740993, 9007199254740993, 9007199254740993, 9007199254740993] diff --git a/tests/phase2/rows/N5.pcm16-byte-raises.eigs b/tests/phase2/rows/N5.pcm16-byte-raises.eigs new file mode 100644 index 000000000..310bd8445 --- /dev/null +++ b/tests/phase2/rows/N5.pcm16-byte-raises.eigs @@ -0,0 +1,18 @@ +# ROW: N5.pcm16-byte-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +b is buffer of 2 +b[0] is 300 +b[1] is 0 +try: + buf_from_pcm16le of [b, 0, 1] +catch e: + print of e.kind +b[0] is 1.5 +try: + buf_from_pcm16le of [b, 0, 1] +catch e: + print of e.kind diff --git a/tests/phase2/rows/N5.pcm16-byte-raises.out b/tests/phase2/rows/N5.pcm16-byte-raises.out new file mode 100644 index 000000000..50faf622e --- /dev/null +++ b/tests/phase2/rows/N5.pcm16-byte-raises.out @@ -0,0 +1,2 @@ +value +value diff --git a/tests/phase2/rows/N5.range-fraction-raises.eigs b/tests/phase2/rows/N5.range-fraction-raises.eigs new file mode 100644 index 000000000..9fe94cfbe --- /dev/null +++ b/tests/phase2/rows/N5.range-fraction-raises.eigs @@ -0,0 +1,10 @@ +# ROW: N5.range-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (range of 2.5) +catch e: + print of e.kind diff --git a/tests/phase2/rows/N5.range-fraction-raises.out b/tests/phase2/rows/N5.range-fraction-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N5.range-fraction-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N5.store-handle-fraction-raises.eigs b/tests/phase2/rows/N5.store-handle-fraction-raises.eigs new file mode 100644 index 000000000..1f346a6e8 --- /dev/null +++ b/tests/phase2/rows/N5.store-handle-fraction-raises.eigs @@ -0,0 +1,51 @@ +# ROW: N5.store-handle-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# R1.6f-D1, #1924 comment 6367017285: all shared-reader callers. +# R1.6f-D3: stores live under /tmp and start clean, as tests/test_store.eigs does. +# The corpus differential runs this file from src/, where a relative store left by +# an earlier failed run poisoned the next one. +define call_store(name, h) as: + if name == "store_close": + return store_close of h + if name == "store_collections": + return store_collections of h + if name == "store_count": + return store_count of [h, "c"] + if name == "store_drop": + return store_drop of [h, "c"] + if name == "store_get": + return store_get of [h, "c", "k"] + if name == "store_put": + return store_put of [h, "c", {"_id": "new", "v": 2}] + if name == "store_delete": + return store_delete of [h, "c", "k"] + if name == "store_query": + return store_query of [h, "c"] + if name == "store_update": + return store_update of [h, "c", "k", {"v": 2}] +for name in ["store_close", "store_collections", "store_count", "store_drop", "store_get", "store_put", "store_delete", "store_query", "store_update"]: + for field in ["_store_id", "_store_gen"]: + path is "/tmp/eigs_r16f_store_" + name + field + ".db" + if file_exists of path: + remove_file of path + s is store_open of path + key is store_put of [s, "c", {"_id": "k", "v": 1}] + h is {"_store_id": s["_store_id"], "_store_gen": s["_store_gen"], "_type": "eigenstore"} + h[field] is h[field] + 0.5 + try: + result is call_store of [name, h] + print of [name, field, "accepted"] + catch e: + print of [name, field, e.kind] + assert of [(store_count of [s, "c"]) == 1, "refusal keeps the store open and its record count unchanged"] + record is store_get of [s, "c", "k"] + assert of [record.v == 1, "refusal leaves the original record unchanged"] + try: + store_close of s + catch e: + unused is null + remove_file of path diff --git a/tests/phase2/rows/N5.store-handle-fraction-raises.out b/tests/phase2/rows/N5.store-handle-fraction-raises.out new file mode 100644 index 000000000..210a37edb --- /dev/null +++ b/tests/phase2/rows/N5.store-handle-fraction-raises.out @@ -0,0 +1,18 @@ +["store_close", "_store_id", "value"] +["store_close", "_store_gen", "value"] +["store_collections", "_store_id", "value"] +["store_collections", "_store_gen", "value"] +["store_count", "_store_id", "value"] +["store_count", "_store_gen", "value"] +["store_drop", "_store_id", "value"] +["store_drop", "_store_gen", "value"] +["store_get", "_store_id", "value"] +["store_get", "_store_gen", "value"] +["store_put", "_store_id", "value"] +["store_put", "_store_gen", "value"] +["store_delete", "_store_id", "value"] +["store_delete", "_store_gen", "value"] +["store_query", "_store_id", "value"] +["store_query", "_store_gen", "value"] +["store_update", "_store_id", "value"] +["store_update", "_store_gen", "value"] diff --git a/tests/phase2/rows/N5.store-key-fraction-raises.eigs b/tests/phase2/rows/N5.store-key-fraction-raises.eigs new file mode 100644 index 000000000..0d41800b4 --- /dev/null +++ b/tests/phase2/rows/N5.store-key-fraction-raises.eigs @@ -0,0 +1,14 @@ +# ROW: N5.store-key-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +s is store_open of "store-key-fraction.db" +k is store_put of [s, "c", {"v": 1}] +try: + print of (store_get of [s, "c", 1.9]) +catch e: + print of e.kind +store_close of s +remove_file of "store-key-fraction.db" diff --git a/tests/phase2/rows/N5.store-key-fraction-raises.out b/tests/phase2/rows/N5.store-key-fraction-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N5.store-key-fraction-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N5.substr-fraction-raises.eigs b/tests/phase2/rows/N5.substr-fraction-raises.eigs new file mode 100644 index 000000000..eea972fb9 --- /dev/null +++ b/tests/phase2/rows/N5.substr-fraction-raises.eigs @@ -0,0 +1,10 @@ +# ROW: N5.substr-fraction-raises +# ITEMS: N5 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (substr of ["hello", 1.5, 2]) +catch e: + print of e.kind diff --git a/tests/phase2/rows/N5.substr-fraction-raises.out b/tests/phase2/rows/N5.substr-fraction-raises.out new file mode 100644 index 000000000..6d4e15079 --- /dev/null +++ b/tests/phase2/rows/N5.substr-fraction-raises.out @@ -0,0 +1 @@ +value diff --git a/tests/phase2/rows/N6.float-repr.eigs b/tests/phase2/rows/N6.float-repr.eigs new file mode 100644 index 000000000..0fbe3afa4 --- /dev/null +++ b/tests/phase2/rows/N6.float-repr.eigs @@ -0,0 +1,7 @@ +# ROW: N6.float-repr +# ITEMS: N6 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [3.0, 1e3, -0.0, 0.1 + 0.2, 1e15, 1e16, 1e-05, 0.0001] diff --git a/tests/phase2/rows/N6.float-repr.out b/tests/phase2/rows/N6.float-repr.out new file mode 100644 index 000000000..213c53546 --- /dev/null +++ b/tests/phase2/rows/N6.float-repr.out @@ -0,0 +1 @@ +[3.0, 1000.0, -0.0, 0.30000000000000004, 1000000000000000.0, 1e+16, 1e-05, 0.0001] diff --git a/tests/phase2/rows/N6.int-exact-digits.eigs b/tests/phase2/rows/N6.int-exact-digits.eigs new file mode 100644 index 000000000..290f0c3bf --- /dev/null +++ b/tests/phase2/rows/N6.int-exact-digits.eigs @@ -0,0 +1,7 @@ +# ROW: N6.int-exact-digits +# ITEMS: N6 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [9007199254740993, -9223372036854775807 - 1] diff --git a/tests/phase2/rows/N6.int-exact-digits.out b/tests/phase2/rows/N6.int-exact-digits.out new file mode 100644 index 000000000..182de25c9 --- /dev/null +++ b/tests/phase2/rows/N6.int-exact-digits.out @@ -0,0 +1 @@ +[9007199254740993, -9223372036854775808] diff --git a/tests/phase2/rows/N6.neg-zero-literal-dedup.eigs b/tests/phase2/rows/N6.neg-zero-literal-dedup.eigs new file mode 100644 index 000000000..933f2be95 --- /dev/null +++ b/tests/phase2/rows/N6.neg-zero-literal-dedup.eigs @@ -0,0 +1,9 @@ +# ROW: N6.neg-zero-literal-dedup +# ITEMS: N6 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +a is 0.0 +b is -0.0 +print of [a, b] diff --git a/tests/phase2/rows/N6.neg-zero-literal-dedup.out b/tests/phase2/rows/N6.neg-zero-literal-dedup.out new file mode 100644 index 000000000..c3b782610 --- /dev/null +++ b/tests/phase2/rows/N6.neg-zero-literal-dedup.out @@ -0,0 +1 @@ +[0.0, -0.0] diff --git a/tests/phase2/rows/N6.str-fstring.eigs b/tests/phase2/rows/N6.str-fstring.eigs new file mode 100644 index 000000000..2f71047e2 --- /dev/null +++ b/tests/phase2/rows/N6.str-fstring.eigs @@ -0,0 +1,8 @@ +# ROW: N6.str-fstring +# ITEMS: N6 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +x is 2.0 +print of [str of x, f"{x}", str of 2] diff --git a/tests/phase2/rows/N6.str-fstring.out b/tests/phase2/rows/N6.str-fstring.out new file mode 100644 index 000000000..9984edbf8 --- /dev/null +++ b/tests/phase2/rows/N6.str-fstring.out @@ -0,0 +1 @@ +["2.0", "2.0", "2"] diff --git a/tests/phase2/rows/N7.num-type-float.eigs b/tests/phase2/rows/N7.num-type-float.eigs new file mode 100644 index 000000000..484787f6b --- /dev/null +++ b/tests/phase2/rows/N7.num-type-float.eigs @@ -0,0 +1,7 @@ +# ROW: N7.num-type-float +# ITEMS: N7 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of 3.0, num_type of -0.0, num_type of 1e300, num_type of 2.5] diff --git a/tests/phase2/rows/N7.num-type-float.out b/tests/phase2/rows/N7.num-type-float.out new file mode 100644 index 000000000..70cb1df72 --- /dev/null +++ b/tests/phase2/rows/N7.num-type-float.out @@ -0,0 +1 @@ +["float", "float", "float", "float"] diff --git a/tests/phase2/rows/N7.num-type-int.eigs b/tests/phase2/rows/N7.num-type-int.eigs new file mode 100644 index 000000000..ae78c6d06 --- /dev/null +++ b/tests/phase2/rows/N7.num-type-int.eigs @@ -0,0 +1,7 @@ +# ROW: N7.num-type-int +# ITEMS: N7 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of 0, num_type of -1, num_type of 9223372036854775807, num_type of (2 * 3)] diff --git a/tests/phase2/rows/N7.num-type-int.out b/tests/phase2/rows/N7.num-type-int.out new file mode 100644 index 000000000..d353e7adf --- /dev/null +++ b/tests/phase2/rows/N7.num-type-int.out @@ -0,0 +1 @@ +["int", "int", "int", "int"] diff --git a/tests/phase2/rows/N7.num-type-non-number.eigs b/tests/phase2/rows/N7.num-type-non-number.eigs new file mode 100644 index 000000000..904f30e95 --- /dev/null +++ b/tests/phase2/rows/N7.num-type-non-number.eigs @@ -0,0 +1,7 @@ +# ROW: N7.num-type-non-number +# ITEMS: N7 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [num_type of "3", num_type of null, num_type of true, num_type of ([1])] diff --git a/tests/phase2/rows/N7.num-type-non-number.out b/tests/phase2/rows/N7.num-type-non-number.out new file mode 100644 index 000000000..9c12ec847 --- /dev/null +++ b/tests/phase2/rows/N7.num-type-non-number.out @@ -0,0 +1 @@ +[null, null, null, null] diff --git a/tests/phase2/rows/N8.bit-builtins-int64.eigs b/tests/phase2/rows/N8.bit-builtins-int64.eigs new file mode 100644 index 000000000..406d1d645 --- /dev/null +++ b/tests/phase2/rows/N8.bit-builtins-int64.eigs @@ -0,0 +1,7 @@ +# ROW: N8.bit-builtins-int64 +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [bit_or of [4611686018427387904, 1], bit_shl of [1, 64]] diff --git a/tests/phase2/rows/N8.bit-builtins-int64.out b/tests/phase2/rows/N8.bit-builtins-int64.out new file mode 100644 index 000000000..7efe080e3 --- /dev/null +++ b/tests/phase2/rows/N8.bit-builtins-int64.out @@ -0,0 +1 @@ +[4611686018427387905, 0] diff --git a/tests/phase2/rows/N8.bitwise-results-int.eigs b/tests/phase2/rows/N8.bitwise-results-int.eigs new file mode 100644 index 000000000..aa07bece7 --- /dev/null +++ b/tests/phase2/rows/N8.bitwise-results-int.eigs @@ -0,0 +1,7 @@ +# ROW: N8.bitwise-results-int +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [6 & 3, 6 | 3, 6 ^ 3, ~0, num_type of (6 & 3)] diff --git a/tests/phase2/rows/N8.bitwise-results-int.out b/tests/phase2/rows/N8.bitwise-results-int.out new file mode 100644 index 000000000..4e7c00124 --- /dev/null +++ b/tests/phase2/rows/N8.bitwise-results-int.out @@ -0,0 +1 @@ +[2, 7, 5, -1, "int"] diff --git a/tests/phase2/rows/N8.fn-bitwise-all.eigs b/tests/phase2/rows/N8.fn-bitwise-all.eigs new file mode 100644 index 000000000..be3a1367e --- /dev/null +++ b/tests/phase2/rows/N8.fn-bitwise-all.eigs @@ -0,0 +1,17 @@ +# ROW: N8.fn-bitwise-all +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop bits BOR +# OPS: BOR +define bits(a, c) as: + o is a | c + x is a ^ c + l is a << 1 + r is a >> 1 + n is ~a + return [o, x, l, r, n] +r is 0 +for i in range of 60: + r is bits of [9007199254740993, 2] +print of r diff --git a/tests/phase2/rows/N8.fn-bitwise-all.out b/tests/phase2/rows/N8.fn-bitwise-all.out new file mode 100644 index 000000000..a1a6264da --- /dev/null +++ b/tests/phase2/rows/N8.fn-bitwise-all.out @@ -0,0 +1 @@ +[9007199254740995, 9007199254740995, 18014398509481986, 4503599627370496, -9007199254740994] diff --git a/tests/phase2/rows/N8.fn-bitwise.eigs b/tests/phase2/rows/N8.fn-bitwise.eigs new file mode 100644 index 000000000..a7d568b51 --- /dev/null +++ b/tests/phase2/rows/N8.fn-bitwise.eigs @@ -0,0 +1,13 @@ +# ROW: N8.fn-bitwise +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: F +# WITNESS: fn-stop b BAND +# OPS: BAND +define b(a, c) as: + t is a & c + return t +r is 0 +for i in range of 60: + r is b of [9007199254740993, 9007199254740993] +print of r diff --git a/tests/phase2/rows/N8.fn-bitwise.out b/tests/phase2/rows/N8.fn-bitwise.out new file mode 100644 index 000000000..ba278ab07 --- /dev/null +++ b/tests/phase2/rows/N8.fn-bitwise.out @@ -0,0 +1 @@ +9007199254740993 diff --git a/tests/phase2/rows/N8.lua-shifts.eigs b/tests/phase2/rows/N8.lua-shifts.eigs new file mode 100644 index 000000000..ab45d0a7b --- /dev/null +++ b/tests/phase2/rows/N8.lua-shifts.eigs @@ -0,0 +1,7 @@ +# ROW: N8.lua-shifts +# ITEMS: N8 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [1 << 64, 1 << 63, 1 << -1, -1 >> 1, 8 >> -1] diff --git a/tests/phase2/rows/N8.lua-shifts.out b/tests/phase2/rows/N8.lua-shifts.out new file mode 100644 index 000000000..a093aa2e4 --- /dev/null +++ b/tests/phase2/rows/N8.lua-shifts.out @@ -0,0 +1 @@ +[0, -9223372036854775808, 0, 9223372036854775807, 16] diff --git a/tests/phase2/rows/N9.checked-raises.eigs b/tests/phase2/rows/N9.checked-raises.eigs new file mode 100644 index 000000000..335a7edde --- /dev/null +++ b/tests/phase2/rows/N9.checked-raises.eigs @@ -0,0 +1,10 @@ +# ROW: N9.checked-raises +# ITEMS: N9 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +try: + print of (u8_checked_add of [200, 100]) +catch e: + print of [e.kind, (index_of of [e.message, "u8_checked_add"]) >= 0] diff --git a/tests/phase2/rows/N9.checked-raises.out b/tests/phase2/rows/N9.checked-raises.out new file mode 100644 index 000000000..56efea05e --- /dev/null +++ b/tests/phase2/rows/N9.checked-raises.out @@ -0,0 +1 @@ +["value", true] diff --git a/tests/phase2/rows/N9.fround.eigs b/tests/phase2/rows/N9.fround.eigs new file mode 100644 index 000000000..0100a783f --- /dev/null +++ b/tests/phase2/rows/N9.fround.eigs @@ -0,0 +1,7 @@ +# ROW: N9.fround +# ITEMS: N9 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of (fround of 0.1) diff --git a/tests/phase2/rows/N9.fround.out b/tests/phase2/rows/N9.fround.out new file mode 100644 index 000000000..943c3e841 --- /dev/null +++ b/tests/phase2/rows/N9.fround.out @@ -0,0 +1 @@ +0.10000000149011612 diff --git a/tests/phase2/rows/N9.saturating.eigs b/tests/phase2/rows/N9.saturating.eigs new file mode 100644 index 000000000..bb7ad509e --- /dev/null +++ b/tests/phase2/rows/N9.saturating.eigs @@ -0,0 +1,7 @@ +# ROW: N9.saturating +# ITEMS: N9 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [i8_saturating_sub of [-100, 100], u64_saturating_add of [-1, 1]] diff --git a/tests/phase2/rows/N9.saturating.out b/tests/phase2/rows/N9.saturating.out new file mode 100644 index 000000000..adec0aa00 --- /dev/null +++ b/tests/phase2/rows/N9.saturating.out @@ -0,0 +1 @@ +[-128, -1] diff --git a/tests/phase2/rows/N9.unsigned-ops.eigs b/tests/phase2/rows/N9.unsigned-ops.eigs new file mode 100644 index 000000000..f8b9a156c --- /dev/null +++ b/tests/phase2/rows/N9.unsigned-ops.eigs @@ -0,0 +1,7 @@ +# ROW: N9.unsigned-ops +# ITEMS: N9 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [u64_compare of [-1, 1], u64_to_string of -1, u64_div of [-1, 2]] diff --git a/tests/phase2/rows/N9.unsigned-ops.out b/tests/phase2/rows/N9.unsigned-ops.out new file mode 100644 index 000000000..7c70a15f2 --- /dev/null +++ b/tests/phase2/rows/N9.unsigned-ops.out @@ -0,0 +1 @@ +[1, "18446744073709551615", 9223372036854775807] diff --git a/tests/phase2/rows/N9.wrapping.eigs b/tests/phase2/rows/N9.wrapping.eigs new file mode 100644 index 000000000..0178830d4 --- /dev/null +++ b/tests/phase2/rows/N9.wrapping.eigs @@ -0,0 +1,7 @@ +# ROW: N9.wrapping +# ITEMS: N9 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +print of [i64_wrapping_add of [9223372036854775807, 1], u8_wrapping_add of [255, 1]] diff --git a/tests/phase2/rows/N9.wrapping.out b/tests/phase2/rows/N9.wrapping.out new file mode 100644 index 000000000..075539a8c --- /dev/null +++ b/tests/phase2/rows/N9.wrapping.out @@ -0,0 +1 @@ +[-9223372036854775808, 0] diff --git a/tests/phase2/rows/O1.int-observed-numeric.eigs b/tests/phase2/rows/O1.int-observed-numeric.eigs new file mode 100644 index 000000000..7855a1022 --- /dev/null +++ b/tests/phase2/rows/O1.int-observed-numeric.eigs @@ -0,0 +1,18 @@ +# ROW: O1.int-observed-numeric +# ITEMS: O1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +a is 3 +b is 140737488355328 +af is 3.0 +bf is 140737488355328.0 +i is 0.0 +loop while i < 12.0: + a is a * 2 + b is b * 2 + af is af * 2.0 + bf is bf * 2.0 + i is i + 1.0 +print of [report of a, report of af, report of b, report of bf, num_type of b] diff --git a/tests/phase2/rows/O1.int-observed-numeric.out b/tests/phase2/rows/O1.int-observed-numeric.out new file mode 100644 index 000000000..908658527 --- /dev/null +++ b/tests/phase2/rows/O1.int-observed-numeric.out @@ -0,0 +1 @@ +["diverging", "diverging", "diverging", "diverging", "int"] diff --git a/tests/phase2/rows/T1.float-kind-token.out b/tests/phase2/rows/T1.float-kind-token.out new file mode 100644 index 000000000..ca98c9bc9 --- /dev/null +++ b/tests/phase2/rows/T1.float-kind-token.out @@ -0,0 +1,2 @@ +float tokens marked +float replay identical diff --git a/tests/phase2/rows/T1.float-kind-token.sh b/tests/phase2/rows/T1.float-kind-token.sh new file mode 100644 index 000000000..766ec2f93 --- /dev/null +++ b/tests/phase2/rows/T1.float-kind-token.sh @@ -0,0 +1,21 @@ +# ROW: T1.float-kind-token +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +cat > p.eigs <<'EOF' +x is 3.0 +z is -0.0 +print of [num_type of x, num_type of z] +EOF +EIGS_TRACE=t.tape "$bin" p.eigs > live.out +grep -q '^A [0-9][0-9]* x=3\.0$' t.tape +grep -q '^A [0-9][0-9]* z=-0\.0$' t.tape +printf 'float tokens marked\n' +EIGS_REPLAY=t.tape EIGS_REPLAY_STRICT=1 "$bin" p.eigs > replay.out +cmp live.out replay.out +printf 'float replay identical\n' diff --git a/tests/phase2/rows/T1.int-replay-kind.out b/tests/phase2/rows/T1.int-replay-kind.out new file mode 100644 index 000000000..6c1516f95 --- /dev/null +++ b/tests/phase2/rows/T1.int-replay-kind.out @@ -0,0 +1,2 @@ +int replay identical +int assignment exact diff --git a/tests/phase2/rows/T1.int-replay-kind.sh b/tests/phase2/rows/T1.int-replay-kind.sh new file mode 100644 index 000000000..f6b6f1d40 --- /dev/null +++ b/tests/phase2/rows/T1.int-replay-kind.sh @@ -0,0 +1,24 @@ +# ROW: T1.int-replay-kind +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +cat > p.eigs <<'EOF' +r is random_int of [1, 6] +x is 9007199254740993 +if r > 3: + x is 9223372036854775807 +print of [num_type of x, x == 9007199254740993 or x == 9223372036854775807] +EOF +EIGS_TRACE=t.tape "$bin" p.eigs > live.out +EIGS_REPLAY=t.tape EIGS_REPLAY_STRICT=1 "$bin" p.eigs > replay.out +cmp live.out replay.out +printf '["int", true]\n' > expect.out +cmp expect.out live.out +printf 'int replay identical\n' +grep -q '^A [0-9][0-9]* x=9007199254740993$' t.tape +printf 'int assignment exact\n' diff --git a/tests/phase2/rows/T1.twice-float.out b/tests/phase2/rows/T1.twice-float.out new file mode 100644 index 000000000..4db12f306 --- /dev/null +++ b/tests/phase2/rows/T1.twice-float.out @@ -0,0 +1,6 @@ +tapes identical +replay identical +A 0 x=3.0 +A 0 y=0.30000000000000004 +A 0 z=-0.0 +A 0 w=0.1 diff --git a/tests/phase2/rows/T1.twice-float.sh b/tests/phase2/rows/T1.twice-float.sh new file mode 100644 index 000000000..2b1bed0db --- /dev/null +++ b/tests/phase2/rows/T1.twice-float.sh @@ -0,0 +1,33 @@ +# ROW: T1.twice-float +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +scratch=$(mktemp -d /tmp/eigs-r17-tape.XXXXXX) +trap 'rm -rf "$scratch"' EXIT +cd "$scratch" +rm -f t1.tape t2.tape +cat > p.eigs <<'EOF' +x is 3.0 +y is 0.1 + 0.2 +z is -0.0 +w is 0.1 +print of [x, y, z, w] +EOF +"$bin" p.eigs > live.out +EIGS_TRACE=t1.tape "$bin" p.eigs > record.out +EIGS_TRACE=t2.tape "$bin" p.eigs > record2.out +cmp t1.tape t2.tape +cmp live.out record.out +cmp live.out record2.out +printf 'tapes identical +' +EIGS_REPLAY=t1.tape EIGS_REPLAY_STRICT=1 "$bin" p.eigs > replay.out +cmp live.out replay.out +printf 'replay identical +' +sed -n '/^A [0-9][0-9]* x=/p; /^A [0-9][0-9]* y=/p; /^A [0-9][0-9]* z=/p; /^A [0-9][0-9]* w=/p' t1.tape diff --git a/tests/phase2/rows/T1.twice-int.out b/tests/phase2/rows/T1.twice-int.out new file mode 100644 index 000000000..e2820a67c --- /dev/null +++ b/tests/phase2/rows/T1.twice-int.out @@ -0,0 +1,4 @@ +tapes identical +replay identical +A 0 x=9007199254740993 +A 0 y=9007199515875289 diff --git a/tests/phase2/rows/T1.twice-int.sh b/tests/phase2/rows/T1.twice-int.sh new file mode 100644 index 000000000..c8875d031 --- /dev/null +++ b/tests/phase2/rows/T1.twice-int.sh @@ -0,0 +1,27 @@ +# ROW: T1.twice-int +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +cat > p.eigs <<'EOF' +define sq(x) as: + return x * x +x is 9007199254740993 +y is sq of 94906267 +print of [x, y] +EOF +"$bin" p.eigs > live.out +EIGS_TRACE=t1.tape "$bin" p.eigs > record.out +EIGS_TRACE=t2.tape "$bin" p.eigs > record2.out +cmp t1.tape t2.tape +cmp live.out record.out +cmp live.out record2.out +printf 'tapes identical\n' +EIGS_REPLAY=t1.tape EIGS_REPLAY_STRICT=1 "$bin" p.eigs > replay.out +cmp live.out replay.out +printf 'replay identical\n' +sed -n '/^A [0-9][0-9]* x=/p; /^A [0-9][0-9]* y=/p' t1.tape diff --git a/tests/phase2/rows/T1.v6-refused.out b/tests/phase2/rows/T1.v6-refused.out new file mode 100644 index 000000000..a794dbe91 --- /dev/null +++ b/tests/phase2/rows/T1.v6-refused.out @@ -0,0 +1 @@ +rc!=0 names-6 diff --git a/tests/phase2/rows/T1.v6-refused.sh b/tests/phase2/rows/T1.v6-refused.sh new file mode 100644 index 000000000..87e633af5 --- /dev/null +++ b/tests/phase2/rows/T1.v6-refused.sh @@ -0,0 +1,21 @@ +# ROW: T1.v6-refused +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +printf 'print of 3\n' > p.eigs +# Start with a tape stamped by this binary so only the format differs. +EIGS_TRACE=current.tape "$bin" p.eigs > live.out +sed '1s/^V [0-9][0-9]* /V 6 /' current.tape > v6.tape +rc=0 +EIGS_REPLAY=v6.tape EIGS_REPLAY_STRICT=1 "$bin" p.eigs > replay.out 2> replay.err || rc=$? +if [ "$rc" -eq 0 ]; then + printf 'v6 accepted\n' + exit 1 +fi +grep -q 'format v6' replay.err +printf 'rc!=0 names-6\n' diff --git a/tests/phase2/rows/T1.v7-header.out b/tests/phase2/rows/T1.v7-header.out new file mode 100644 index 000000000..d69d4d4fe --- /dev/null +++ b/tests/phase2/rows/T1.v7-header.out @@ -0,0 +1 @@ +V 9 diff --git a/tests/phase2/rows/T1.v7-header.sh b/tests/phase2/rows/T1.v7-header.sh new file mode 100644 index 000000000..f9e84f0ce --- /dev/null +++ b/tests/phase2/rows/T1.v7-header.sh @@ -0,0 +1,22 @@ +# ROW: T1.v7-header +# Historical row id; sync #5 combined v7 schemas as v8; sync #10 combines +# phase2 kind tokens and main continuation framing as v9, rejecting both v8s. +# ITEMS: T1 +# EXPECT-RC: 0 +# SHAPE: S +# WITNESS: none +# OPS: +# TIERS: off default osr +set -eu +bin=$1 +printf 'x is 3\n' > p.eigs +EIGS_TRACE=t.tape "$bin" p.eigs > live.out +head -1 t.tape | cut -d' ' -f1-2 +# Reject a main/phase2 v8 tape before its untyped numeric tokens can be +# parsed with a different kind. Retain the separate v6 refusal fixture too. +sed '1s/^V 9 /V 8 /' t.tape > old-v8.tape +if EIGS_REPLAY=old-v8.tape "$bin" p.eigs > old-v8.out 2> old-v8.err; then + echo 'v8 replay unexpectedly accepted' >&2 + exit 1 +fi +grep -q 'format v8' old-v8.err diff --git a/tests/phase2/slot_layer.c b/tests/phase2/slot_layer.c new file mode 100644 index 000000000..91e16058f --- /dev/null +++ b/tests/phase2/slot_layer.c @@ -0,0 +1,909 @@ +/* tests/phase2/slot_layer.c -- #1758 R1.1, the C slot test (design 2.5). + * + * The classifier matrix exercises canonical and non-canonical states directly. + * This test builds SMI slots BY HAND (the + * literal tag 0xFFFA, never slot_from_int) and drives every slot classifier + * whose first parameter is an EigsSlot -- the population + * `tools/phase2_inventory.py --slot-population` derives -- with the SMI + * boundary values. It includes the single-file amalgamation + * (tools/amalgamate.sh) so the static classifiers are reachable. + * + * Parts: + * A every population entry x every SMI boundary value. An entry either + * RETURNS the int's answer (its truthiness, type name, double, digits, + * the slot unchanged), or ABORTS through a named refusal: the permanent + * reference-reader refusals (eigs_slot_refuse). An abort runs + * in a forked child, and its FATAL line must name the expected reader. + * B every population entry x twelve slot kinds: a double, the canonical + * NaN, -0.0, null, both bools, an SMI, a heap pointer, the reserved + * tags and the sentinel; each cell runs in a forked child. + * C slot_from_int, the one int writer (plant 1's named check, design 9.3): + * inside [-2^47, 2^47) the tag is 0xFFFA and the int reads back exactly; + * outside, an owned VAL_INT box preserves every int64 bit. + * D the VM's operator arms: a host builtin puts an SMI into a list and a + * program applies each operator to it. Every converted arm must compute its exact value, + * inside a `try` too; the same program with a double is the control. + * E ITER_NEXT index and snapshot metadata in both executors, with float, + * SMI and boxed-int metadata, including range and malformed controls. + * + * Output, every line in one grammar (tests/phase2/slot_layer.sh refuses any + * other line): `ENTRY : values= ok|FAIL` per Part A + * entry and `MATRIX : kinds= ok|FAIL` per Part B entry (the + * runner compares both sets with the derived population, both ways), + * `ARM ok|FAIL` per Part D arm, `slot-layer-test: FAIL ` lines, + * then `slot-layer-test: examined=N ... failures=F` and the verdict. Nothing + * goes to stderr. Exit 0 only for failures == 0 with every part complete. */ +#include "eigenscript_all.c" + +#include +#include +#include +#ifdef __linux__ +#include +#endif + +#ifndef EIGS_SLOT_CHECKS +#error "build with -DEIGS_SLOT_CHECKED (or ASan): slot_as_ptr's refusal is under test" +#endif + +static int failures; + +/* A child that is EXPECTED to abort must not dump core. On a box whose + * core_pattern pipes to a crash reporter (Ubuntu's apport), the kernel runs + * the reporter for every abort, RLIMIT_CORE notwithstanding: about 1.4 s + * each, which made this test take 224 s instead of seconds. A non-dumpable + * process skips the core dump entirely; the SIGABRT status is unchanged. */ +static void child_no_core(void) { +#ifdef __linux__ + (void)prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); +#endif +} + +/* The test's own encoding, independent of value_slot.h's writers. */ +#define T_SMI_TAG 0xFFFA000000000000ULL +#define T_PAYLOAD 0x0000FFFFFFFFFFFFULL +static EigsSlot bits(uint64_t u) { EigsSlot s; s.u = u; return s; } +static EigsSlot smi(int64_t v) { return bits(T_SMI_TAG | ((uint64_t)v & T_PAYLOAD)); } +static double f64_of(uint64_t u) { double d; memcpy(&d, &u, sizeof d); return d; } + +static const int64_t SMI_VALUES[] = { + 0, 1, -1, (INT64_C(1) << 47) - 1, -(INT64_C(1) << 47), +}; +#define N_SMI ((int)(sizeof SMI_VALUES / sizeof SMI_VALUES[0])) + +#define FAILF(...) do { printf("slot-layer-test: FAIL "); printf(__VA_ARGS__); printf("\n"); \ + failures++; } while (0) + +/* Run fn(s) in a child; it must die by SIGABRT with a FATAL line naming + * `who`. Returns 1 when it did. */ +static int aborts_naming(void (*fn)(EigsSlot), EigsSlot s, const char *who, const char *what) { + int p[2]; + if (pipe(p) != 0) { FAILF("%s: pipe", what); return 0; } + fflush(stdout); fflush(stderr); + pid_t pid = fork(); + if (pid < 0) { FAILF("%s: fork", what); return 0; } + if (pid == 0) { + child_no_core(); + close(p[0]); + dup2(p[1], 2); + fn(s); + _exit(0); /* returned: the refusal did not fire */ + } + close(p[1]); + char buf[1024]; + size_t n = 0; + ssize_t r; + while (n < sizeof buf - 1 && (r = read(p[0], buf + n, sizeof buf - 1 - n)) > 0) n += (size_t)r; + buf[n] = 0; + close(p[0]); + int st = 0; + waitpid(pid, &st, 0); + char want[160]; + snprintf(want, sizeof want, "EigenScript FATAL: %s:", who); + if (WIFSIGNALED(st) && WTERMSIG(st) == SIGABRT && strstr(buf, want)) return 1; + char *nl = strchr(buf, '\n'); + if (nl) *nl = 0; + FAILF("%s(0x%016llx): want an abort naming %s; got %s %d, stderr \"%s\"", what, + (unsigned long long)s.u, who, WIFSIGNALED(st) ? "signal" : "exit", + WIFSIGNALED(st) ? WTERMSIG(st) : WEXITSTATUS(st), buf); + return 0; +} + +/* ---- Part A: RETURN checks (0 = ok) ------------------------------------ */ + +static int r_eigs_slot_store_borrow(int64_t v) { return eigs_slot_store_borrow(smi(v)).u != smi(v).u; } +static int r_eigs_slot_store_owned(int64_t v) { return eigs_slot_store_owned(smi(v)).u != smi(v).u; } +static int r_ent_child_slot(int64_t v) { + return ent_child_slot(smi(v)) != ent_child_slot(slot_from_f64((double)v)); +} +static int r_store_json_encode_slot(int64_t v) { + strbuf sb; + strbuf_init(&sb); + store_json_encode_slot(smi(v), &sb); + char want[32]; + snprintf(want, sizeof want, "%lld", (long long)v); + int bad = !sb.data || strcmp(sb.data, want) != 0; + strbuf_free(&sb); + return bad; +} +static int r_slot_f64_value(int64_t v) { double d = -7.25; return slot_f64_value(smi(v), &d) != 0 || d != -7.25; } +static int r_slot_int_value(int64_t v) { + int64_t i = 7; + if (slot_int_value(smi(v), &i) != 1 || i != v) return 1; + /* The box arm is independent of SMI range and must not narrow through double. */ + static const int64_t boxed[] = {INT64_MIN, INT64_MAX, INT64_C(9007199254740993)}; + for (size_t n = 0; n < sizeof boxed / sizeof boxed[0]; n++) { + Value box = {.type = VAL_INT}; + VAL_I64_RAW(&box) = boxed[n]; + i = 7; + if (!slot_int_value(slot_from_heap(&box), &i) || i != boxed[n]) return 1; + } + return 0; +} +static int r_slot_type(int64_t v) { return slot_type(smi(v)) != VAL_INT; } +static int r_slot_view(int64_t v) { + EIGS_VIEW(view); + Value *got = slot_view(smi(v), &view); + return got != &view.v || got->type != VAL_INT || VAL_I64_RAW(got) != v || + got->refcount != EIGS_VIEW_RC; +} +static int r_slot_is_f64(int64_t v) { return slot_is_f64(smi(v)) != 0; } +static int r_slot_is_smi(int64_t v) { return slot_is_smi(smi(v)) != 1; } +static int r_slot_smi_val(int64_t v) { return slot_smi_val(smi(v)) != v; } +static int r_slot_truthy_immediate(int64_t v) { + int decided = -1; + int t = slot_truthy_immediate(smi(v), &decided); + return decided != 1 || t != (v != 0); +} +static int r_obs_dump_value(int64_t v) { + char buf[160], want[32]; + obs_dump_value(smi(v), buf, sizeof buf); + snprintf(want, sizeof want, "%lld", (long long)v); + return strcmp(buf, want) != 0; +} +static int r_obs_slot_num(int64_t v) { double d = -7.25; return obs_slot_num(smi(v), &d) != 1 || d != (double)v; } +static int r_slot_truthy(int64_t v) { return slot_truthy(smi(v)) != (v != 0); } +static int r_slot_type_name(int64_t v) { return strcmp(slot_type_name(smi(v)), "num") != 0; } +static int r_slot_decref(int64_t v) { slot_decref(smi(v)); return 0; } /* no refcount: no-op */ +static int r_slot_incref(int64_t v) { slot_incref(smi(v)); return 0; } +static int r_slot_is_bool(int64_t v) { return slot_is_bool(smi(v)) != 0; } +static int r_slot_is_heap(int64_t v) { return slot_is_heap(smi(v)) != 0; } +static int r_slot_is_null(int64_t v) { return slot_is_null(smi(v)) != 0; } +static int r_slot_is_ptr(int64_t v) { return slot_is_ptr(smi(v)) != 0; } + +/* ---- Part A: ABORT calls ------------------------------------------------ */ + +static int r_eigs_slot_box(int64_t v) { + Value *got = eigs_slot_box(smi(v)); + int bad = !got || got->type != VAL_INT || VAL_I64_RAW(got) != v; + val_decref(got); return bad; +} +static int r_slot_to_value(int64_t v) { + Value *got = slot_to_value(smi(v)); + int bad = !got || got->type != VAL_INT || VAL_I64_RAW(got) != v; + val_decref(got); return bad; +} +static int r_write_slot(int64_t v) { + size_t before = g_out_len; + write_slot(smi(v)); + char want[32], got[32]; + snprintf(want, sizeof want, "%lld", (long long)v); + snprintf(got, sizeof got, "%.*s", (int)(g_out_len - before), g_out + before); + return strcmp(want, got) != 0; +} +static int r_slot_bridge_unwrap(int64_t v) { + Value *got = slot_bridge_unwrap(smi(v)); + int bad = !got || got->type != VAL_INT || VAL_I64_RAW(got) != v; + val_decref(got); return bad; +} +static int r_vm_bit_operand(int64_t v) { + int64_t got = 7; return vm_bit_operand(smi(v), &got) != 1 || got != v; +} +static int r_vm_number_compare(int64_t v) { + int got = 7; return !vm_number_compare(smi(v), smi(v), &got) || got != 0; +} +static int r_vm_iter_index(int64_t v) { + int got = 7, valid = v >= INT_MIN && v <= INT_MAX; + return vm_iter_index(smi(v), &got) != valid || got != (valid ? (int)v : 7); +} +static int r_vm_iter_limit(int64_t v) { + int want = v < 0 ? 0 : v < 10 ? (int)v : 10; + return vm_iter_limit(smi(v), 10) != want; +} +static void a_obs_slot_observable(EigsSlot s) { (void)obs_slot_observable(s); } +static void a_eigs_slot_ref(EigsSlot s) { (void)eigs_slot_ref(s); } +static void a_slot_as_ptr(EigsSlot s) { (void)slot_as_ptr(s); } +static void a_obs_slot_value(EigsSlot s) { (void)obs_slot_value(s); } + +typedef struct { + const char *name; /* the inventory's file:function */ + int (*ret)(int64_t); /* returns the int's answer, or */ + void (*abort_call)(EigsSlot); /* aborts ... */ + const char *aborts; /* ... naming this reader */ + const char *why; +} Entry; + +static const Entry ENTRIES[] = { + {"vm.c:vm_bit_operand", r_vm_bit_operand, 0, 0, "exact int64 operand"}, + {"vm.c:vm_number_compare", r_vm_number_compare, 0, 0, "exact numeric comparison"}, + {"vm.c:vm_iter_index", r_vm_iter_index, 0, 0, "checked C int metadata"}, + {"vm.c:vm_iter_limit", r_vm_iter_limit, 0, 0, "bounded snapshot"}, + {"container.h:eigs_slot_store_borrow", r_eigs_slot_store_borrow, 0, 0, "canonical storage: unchanged"}, + {"container.h:eigs_slot_store_owned", r_eigs_slot_store_owned, 0, 0, "canonical storage: unchanged"}, + {"eigenscript.c:ent_child_slot", r_ent_child_slot, 0, 0, "the entropy of its double"}, + {"ext_store.c:store_json_encode_slot", r_store_json_encode_slot, 0, 0, "exact digits"}, + {"value_slot.h:slot_f64_value", r_slot_f64_value, 0, 0, "not a float: 0, out untouched"}, + {"value_slot.h:slot_int_value", r_slot_int_value, 0, 0, "the int"}, + {"value_slot.h:slot_is_f64", r_slot_is_f64, 0, 0, "0"}, + {"value_slot.h:slot_is_smi", r_slot_is_smi, 0, 0, "1"}, + {"value_slot.h:slot_smi_val", r_slot_smi_val, 0, 0, "the int, sign-extended"}, + {"value_slot.h:slot_truthy_immediate", r_slot_truthy_immediate, 0, 0, "decided: not 0"}, + {"vm.c:obs_dump_value", r_obs_dump_value, 0, 0, "exact digits"}, + {"vm.c:obs_slot_num", r_obs_slot_num, 0, 0, "the numeric channel: the int's double"}, + {"vm.c:slot_truthy", r_slot_truthy, 0, 0, "not 0"}, + {"vm.c:slot_type_name", r_slot_type_name, 0, 0, "\"num\""}, + {"value_slot.h:slot_decref", r_slot_decref, 0, 0, "no refcount"}, + {"value_slot.h:slot_incref", r_slot_incref, 0, 0, "no refcount"}, + {"value_slot.h:slot_is_bool", r_slot_is_bool, 0, 0, "0"}, + {"value_slot.h:slot_is_heap", r_slot_is_heap, 0, 0, "0"}, + {"value_slot.h:slot_is_null", r_slot_is_null, 0, 0, "0"}, + {"value_slot.h:slot_is_ptr", r_slot_is_ptr, 0, 0, "0"}, + {"container.h:eigs_slot_box", r_eigs_slot_box, 0, 0, "exact owned VAL_INT"}, + {"container.h:slot_type", r_slot_type, 0, 0, "VAL_INT"}, + {"container.h:slot_view", r_slot_view, 0, 0, "exact VAL_INT facade"}, + {"eigenscript.c:slot_to_value", r_slot_to_value, 0, 0, "exact owned VAL_INT"}, + {"trace.c:write_slot", r_write_slot, 0, 0, "v8 exact int digits"}, + {"vm.c:obs_slot_observable", 0, a_obs_slot_observable, "obs_slot_observable", "refuse"}, + {"vm.c:slot_bridge_unwrap", r_slot_bridge_unwrap, 0, 0, "exact owned VAL_INT"}, + {"container.h:eigs_slot_ref", 0, a_eigs_slot_ref, "eigs_slot_ref", "refuse"}, + {"value_slot.h:slot_as_ptr", 0, a_slot_as_ptr, "slot_as_ptr", "refuse (checked builds)"}, + {"vm.c:obs_slot_value", 0, a_obs_slot_value, "slot_as_ptr", "precondition: pointer or bool only"}, +}; +#define N_ENTRIES ((int)(sizeof ENTRIES / sizeof ENTRIES[0])) + +static int part_a(void) { + int calls = 0; + for (int i = 0; i < N_ENTRIES; i++) { + const Entry *e = &ENTRIES[i]; + int bad = 0; + for (int k = 0; k < N_SMI; k++) { + int64_t v = SMI_VALUES[k]; + calls++; + if (e->ret) { + if (e->ret(v)) { FAILF("%s(SMI %lld): want %s", e->name, (long long)v, e->why); bad++; } + } else if (!aborts_naming(e->abort_call, smi(v), e->aborts, e->name)) { + bad++; + } + } + printf("ENTRY %s %s values=%d %s\n", e->name, + e->ret ? "returns" : e->aborts, N_SMI, bad ? "FAIL" : "ok"); + } + return calls; +} + +/* ---- Part B: every entry against every tag ------------------------------ */ + +/* The twelve slot kinds: three doubles (an ordinary one, the canonical NaN, + * -0.0), null, both bools, an SMI, a heap pointer, the three reserved tags + * and the sentinel. */ +enum { K_F, K_NAN, K_NZ, K_NULL, K_FALSE, K_TRUE, K_SMI, K_HEAP, K_R1, K_R2, K_R3, K_SENT, N_KINDS }; +static const char *KIND_NAMES[N_KINDS] = { + "f64 1.5", "f64 canonical NaN", "f64 -0.0", "null", "false", "true", "SMI 5", + "heap str", "reserved 0xFFFC", "reserved 0xFFFD", "reserved 0xFFFE", "sentinel 0xFFFF", +}; +static uint64_t KB[N_KINDS]; /* each kind's slot bits (main fills K_HEAP) */ +static Value *g_str; /* the heap kind's Value, a string "x" */ +static double KD(int k) { return f64_of(KB[k]); } +enum { C_F64, C_NULL, C_BOOL, C_SMI, C_HEAP, C_RES }; +static int kclass(int k) { + switch (k) { + case K_F: case K_NAN: case K_NZ: return C_F64; + case K_NULL: return C_NULL; + case K_FALSE: case K_TRUE: return C_BOOL; + case K_SMI: return C_SMI; + case K_HEAP: return C_HEAP; + default: return C_RES; + } +} + +/* Renderers: every result becomes one comparable token. */ +static uint64_t u64_of(double d) { uint64_t u; memcpy(&u, &d, sizeof u); return u; } +static void r_d(char *b, size_t n, double d) { snprintf(b, n, "d:%016llx", (unsigned long long)u64_of(d)); } +static void r_v(char *b, size_t n, Value *v) { + if (!v) { snprintf(b, n, "v:NULL"); return; } + if (v == g_str) { snprintf(b, n, "v:H"); return; } + switch (v->type) { + case VAL_FLOAT: snprintf(b, n, "v:num:%016llx", (unsigned long long)u64_of(VAL_F64_RAW(v))); return; + case VAL_INT: snprintf(b, n, "v:int:%lld", (long long)VAL_I64_RAW(v)); return; /* #1758 R1.2a */ + case VAL_NULL: snprintf(b, n, "v:null"); return; + case VAL_BOOL: snprintf(b, n, "v:%s", v->data.boolean ? "true" : "false"); return; + case VAL_STR: case VAL_LIST: case VAL_DICT: case VAL_FN: case VAL_BUILTIN: + case VAL_BUFFER: case VAL_TEXT_BUILDER: + snprintf(b, n, "v:%s", val_type_name(v->type)); return; + } +} +static int is_heap_kind(EigsSlot s) { return s.u == KB[K_HEAP]; } + +/* Cells: call one entry and render its result. Each runs in a forked child. */ +#define CELL(name) static void c_##name(EigsSlot s, char *b, size_t n) +CELL(store_borrow) { EigsSlot r = eigs_slot_store_borrow(s); snprintf(b, n, "b:%016llx", (unsigned long long)r.u); + if (is_heap_kind(r)) slot_decref(r); } +CELL(store_owned) { if (is_heap_kind(s)) slot_incref(s); + EigsSlot r = eigs_slot_store_owned(s); snprintf(b, n, "b:%016llx", (unsigned long long)r.u); + if (is_heap_kind(r)) slot_decref(r); } +CELL(ent_child) { r_d(b, n, ent_child_slot(s)); } +CELL(store_json) { strbuf sb; strbuf_init(&sb); store_json_encode_slot(s, &sb); + snprintf(b, n, "s:%s", sb.data ? sb.data : ""); strbuf_free(&sb); } +CELL(write_slot) { size_t before = g_out_len; write_slot(s); + snprintf(b, n, "w:%.*s", (int)(g_out_len - before), g_out + before); } +CELL(f64_value) { double d = -7.25; int r = slot_f64_value(s, &d); + snprintf(b, n, "o:%d d:%016llx", r, (unsigned long long)u64_of(d)); } +CELL(int_value) { int64_t i = 7; int r = slot_int_value(s, &i); snprintf(b, n, "o:%d i:%lld", r, (long long)i); } +CELL(is_f64) { snprintf(b, n, "i:%d", slot_is_f64(s)); } +CELL(is_smi) { snprintf(b, n, "i:%d", slot_is_smi(s)); } +CELL(smi_val) { snprintf(b, n, "i:%lld", (long long)slot_smi_val(s)); } +CELL(truthy_imm) { int dec = -1; int t = slot_truthy_immediate(s, &dec); snprintf(b, n, "o:%d dec:%d", t, dec); } +CELL(dump_value) { char buf[160]; obs_dump_value(s, buf, sizeof buf); snprintf(b, n, "s:%s", buf); } +CELL(obs_num) { double d = -7.25; int r = obs_slot_num(s, &d); + snprintf(b, n, "o:%d d:%016llx", r, (unsigned long long)u64_of(d)); } +CELL(truthy) { snprintf(b, n, "i:%d", slot_truthy(s)); } +CELL(type_name) { snprintf(b, n, "s:%s", slot_type_name(s)); } +CELL(decref) { if (is_heap_kind(s)) slot_incref(s); slot_decref(s); snprintf(b, n, "ok"); } +CELL(incref) { slot_incref(s); if (is_heap_kind(s)) slot_decref(s); snprintf(b, n, "ok"); } +CELL(is_bool) { snprintf(b, n, "i:%d", slot_is_bool(s)); } +CELL(is_heap) { snprintf(b, n, "i:%d", slot_is_heap(s)); } +CELL(is_null) { snprintf(b, n, "i:%d", slot_is_null(s)); } +CELL(is_ptr) { snprintf(b, n, "i:%d", slot_is_ptr(s)); } +CELL(slot_box) { Value *v = eigs_slot_box(s); r_v(b, n, v); val_decref(v); } +CELL(slot_type) { snprintf(b, n, "t:%s", val_type_name(slot_type(s))); } +CELL(slot_view) { EIGS_VIEW_NOCHECK(view); r_v(b, n, slot_view(s, &view)); } +CELL(to_value) { Value *v = slot_to_value(s); r_v(b, n, v); val_decref(v); } +CELL(observable) { snprintf(b, n, "i:%d", obs_slot_observable(s)); } +CELL(unwrap) { Value *v = slot_bridge_unwrap(s); r_v(b, n, v); if (!slot_is_ptr(s)) val_decref(v); } +CELL(vm_bit_operand) { int64_t i = 7; int r = vm_bit_operand(s, &i); snprintf(b, n, "o:%d i:%lld", r, (long long)i); } +CELL(vm_number_compare) { int cmp = 7; int r = vm_number_compare(s, smi(1), &cmp); snprintf(b, n, "o:%d c:%d", r, cmp); } +CELL(vm_iter_index) { int i = 7; int r = vm_iter_index(s, &i); snprintf(b, n, "o:%d i:%d", r, i); } +CELL(vm_iter_limit) { snprintf(b, n, "i:%d", vm_iter_limit(s, 10)); } +CELL(slot_ref) { r_v(b, n, eigs_slot_ref(s)); } +CELL(as_ptr) { r_v(b, n, slot_as_ptr(s)); } +CELL(obs_value) { r_v(b, n, obs_slot_value(s)); } + +/* Expectations: what each entry must do with each kind, written from its + * contract (container.h, value_slot.h, the 2.5 dispositions). The tokens: + * `A:` an abort naming the reader; ` raise` a type error raised. */ +#define EXP(name) static void e_##name(int k, char *b, size_t n) +#define ABORT(who) snprintf(b, n, "A:%s", who) +static void e_bool(char *b, size_t n, int k) { snprintf(b, n, "v:%s", k == K_TRUE ? "true" : "false"); } +EXP(store_slot) { /* both normalisers: a double is guarded (NaN -> 0), the rest kept */ + snprintf(b, n, "b:%016llx", (unsigned long long)(k == K_NAN ? 0 : KB[k])); +} +EXP(ent_child) { + switch (kclass(k)) { + case C_F64: r_d(b, n, entropy_of_num(KD(k))); break; + case C_SMI: r_d(b, n, entropy_of_num(5.0)); break; + case C_NULL: r_d(b, n, compute_entropy_impl(make_null())); break; + case C_BOOL: r_d(b, n, compute_entropy_impl(make_bool(k == K_TRUE))); break; + case C_HEAP: r_d(b, n, compute_entropy_impl(g_str)); break; + default: ABORT("eigs_slot_ref"); + } +} +EXP(store_json) { + static const char *f64[3] = {"s:1.5", "s:nan", "s:-0.0"}; /* R1.7-D7: N6/N14 preserve signed-zero text. */ + switch (kclass(k)) { + case C_F64: snprintf(b, n, "%s", f64[k]); break; + case C_NULL: snprintf(b, n, "s:null"); break; + case C_BOOL: snprintf(b, n, "s:%s", k == K_TRUE ? "true" : "false"); break; + case C_SMI: snprintf(b, n, "s:5"); break; + case C_HEAP: snprintf(b, n, "s:\"x\""); break; + default: ABORT("eigs_slot_ref"); + } +} +static char g_heap_tape[64]; /* what the tape writer prints for g_str */ +EXP(write_slot) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "w:%s", k == K_NAN ? "nan" : k == K_NZ ? "-0.0" : "1.5"); break; + case C_SMI: snprintf(b, n, "w:5"); break; + case C_NULL: snprintf(b, n, "w:null"); break; + case C_BOOL: snprintf(b, n, "w:%s", k == K_TRUE ? "true" : "false"); break; + case C_HEAP: snprintf(b, n, "w:%s", g_heap_tape); break; + default: ABORT("write_slot"); /* a reserved tag refuses */ + } +} +EXP(f64_value) { + if (kclass(k) == C_F64) snprintf(b, n, "o:1 d:%016llx", (unsigned long long)KB[k]); + else snprintf(b, n, "o:0 d:%016llx", (unsigned long long)u64_of(-7.25)); +} +EXP(int_value) { snprintf(b, n, k == K_SMI ? "o:1 i:5" : "o:0 i:7"); } +EXP(is_f64) { snprintf(b, n, "i:%d", kclass(k) == C_F64); } +EXP(is_smi) { snprintf(b, n, "i:%d", k == K_SMI); } +EXP(smi_val) { /* the payload, sign-extended from bit 47, by the test's own arithmetic */ + uint64_t p = KB[k] & T_PAYLOAD; + int64_t v = (p & 0x0000800000000000ULL) ? (int64_t)p - (INT64_C(1) << 48) : (int64_t)p; + snprintf(b, n, "i:%lld", (long long)v); +} +EXP(truthy_imm) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "o:%d dec:1", KD(k) != 0.0); break; /* NaN is truthy, -0.0 is not */ + case C_NULL: snprintf(b, n, "o:0 dec:1"); break; + case C_BOOL: snprintf(b, n, "o:%d dec:1", k == K_TRUE); break; + case C_SMI: snprintf(b, n, "o:1 dec:1"); break; + default: snprintf(b, n, "o:0 dec:0"); /* undecided: no dereference */ + } +} +EXP(dump_value) { + char t[64]; + switch (kclass(k)) { + case C_F64: eigs_num_text(t, sizeof t, KD(k)); snprintf(b, n, "s:%s", t); break; + case C_NULL: snprintf(b, n, "s:null"); break; + case C_BOOL: snprintf(b, n, "s:%s", k == K_TRUE ? "true" : "false"); break; + case C_SMI: snprintf(b, n, "s:5"); break; + case C_HEAP: snprintf(b, n, "s:\"x\""); break; + default: snprintf(b, n, "s:?"); + } +} +EXP(obs_num) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "o:1 d:%016llx", (unsigned long long)KB[k]); break; + case C_SMI: snprintf(b, n, "o:1 d:%016llx", (unsigned long long)u64_of(5.0)); break; + default: snprintf(b, n, "o:0 d:%016llx", (unsigned long long)u64_of(-7.25)); + } +} +EXP(truthy) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "i:%d", KD(k) != 0.0); break; + case C_NULL: snprintf(b, n, "i:0"); break; + case C_BOOL: snprintf(b, n, "i:%d", k == K_TRUE); break; + case C_SMI: case C_HEAP: snprintf(b, n, "i:1"); break; + default: ABORT("slot_truthy"); + } +} +EXP(type_name) { + static const char *names[] = {"s:num", "s:none", "s:bool", "s:num", "s:str"}; + if (kclass(k) == C_RES) ABORT("slot_type_name"); else snprintf(b, n, "%s", names[kclass(k)]); +} +EXP(ok) { (void)k; snprintf(b, n, "ok"); } +EXP(is_bool) { snprintf(b, n, "i:%d", kclass(k) == C_BOOL); } +EXP(is_heap) { snprintf(b, n, "i:%d", k == K_HEAP); } +EXP(is_null) { snprintf(b, n, "i:%d", k == K_NULL); } +EXP(slot_box) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "v:num:%016llx", (unsigned long long)(k == K_NAN ? 0 : KB[k])); break; + case C_NULL: snprintf(b, n, "v:null"); break; + case C_BOOL: e_bool(b, n, k); break; + case C_HEAP: snprintf(b, n, "v:H"); break; + case C_SMI: snprintf(b, n, "v:int:5"); break; + default: ABORT("eigs_slot_ref"); + } +} +EXP(slot_type) { + static const char *names[] = {"t:num", "t:null", "t:bool", "t:num", "t:str"}; + if (kclass(k) == C_RES) ABORT("slot_type"); + else snprintf(b, n, "%s", names[kclass(k)]); +} +EXP(slot_view) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "v:num:%016llx", (unsigned long long)KB[k]); break; /* a facade: no guard */ + case C_NULL: snprintf(b, n, "v:null"); break; + case C_BOOL: e_bool(b, n, k); break; + case C_HEAP: snprintf(b, n, "v:H"); break; + case C_SMI: snprintf(b, n, "v:int:5"); break; + default: ABORT("slot_type"); + } +} +EXP(to_value) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "v:num:%016llx", (unsigned long long)(k == K_NAN ? 0 : KB[k])); break; + case C_NULL: snprintf(b, n, "v:null"); break; + case C_BOOL: e_bool(b, n, k); break; + case C_HEAP: snprintf(b, n, "v:H"); break; + case C_SMI: snprintf(b, n, "v:int:5"); break; + default: ABORT("slot_to_value"); + } +} +EXP(observable) { + switch (kclass(k)) { + case C_F64: case C_NULL: snprintf(b, n, "i:0"); break; + case C_BOOL: case C_HEAP: snprintf(b, n, "i:1"); break; + default: ABORT("obs_slot_observable"); + } +} +EXP(unwrap) { + switch (kclass(k)) { + case C_F64: snprintf(b, n, "v:num:%016llx", (unsigned long long)(k == K_NAN ? 0 : KB[k])); break; + case C_NULL: snprintf(b, n, "v:null"); break; + case C_BOOL: e_bool(b, n, k); break; + case C_HEAP: snprintf(b, n, "v:H"); break; + case C_SMI: snprintf(b, n, "v:int:5"); break; + default: ABORT("slot_as_ptr"); + } +} +EXP(slot_ref) { + switch (kclass(k)) { + case C_NULL: snprintf(b, n, "v:null"); break; + case C_BOOL: e_bool(b, n, k); break; + case C_HEAP: snprintf(b, n, "v:H"); break; + default: ABORT("eigs_slot_ref"); + } +} +EXP(as_ptr) { if (k == K_HEAP) snprintf(b, n, "v:H"); else ABORT("slot_as_ptr"); } +EXP(obs_value) { + if (kclass(k) == C_BOOL) e_bool(b, n, k); + else if (k == K_HEAP) snprintf(b, n, "v:H"); + else ABORT("slot_as_ptr"); +} + +typedef struct { + const char *name; + void (*cell)(EigsSlot, char *, size_t); + void (*expect)(int, char *, size_t); +} MatrixEntry; +EXP(vm_bit_operand) { + if (k == K_SMI) snprintf(b, n, "o:1 i:5"); + else if (k == K_NZ) snprintf(b, n, "o:1 i:0"); + else snprintf(b, n, "o:%d i:7", kclass(k) == C_F64 ? 0 : -1); +} +EXP(vm_number_compare) { + if (k == K_SMI) snprintf(b, n, "o:1 c:1"); + else if (k == K_NAN) snprintf(b, n, "o:1 c:2"); + else if (kclass(k) == C_F64) snprintf(b, n, "o:1 c:%d", k == K_NZ ? -1 : 1); + else snprintf(b, n, "o:0 c:7"); +} +EXP(vm_iter_index) { + if (k == K_SMI) snprintf(b, n, "o:1 i:5"); + else if (k == K_NZ) snprintf(b, n, "o:1 i:0"); + else if (k == K_F) snprintf(b, n, "o:1 i:1"); + else snprintf(b, n, "o:0 i:7"); +} +EXP(vm_iter_limit) { snprintf(b, n, "i:%d", k == K_SMI ? 5 : k == K_NZ ? 0 : k == K_F ? 1 : 10); } +static const MatrixEntry MATRIX[] = { + {"vm.c:vm_bit_operand", c_vm_bit_operand, e_vm_bit_operand}, + {"vm.c:vm_number_compare", c_vm_number_compare, e_vm_number_compare}, + {"vm.c:vm_iter_index", c_vm_iter_index, e_vm_iter_index}, + {"vm.c:vm_iter_limit", c_vm_iter_limit, e_vm_iter_limit}, + {"container.h:eigs_slot_store_borrow", c_store_borrow, e_store_slot}, + {"container.h:eigs_slot_store_owned", c_store_owned, e_store_slot}, + {"eigenscript.c:ent_child_slot", c_ent_child, e_ent_child}, + {"ext_store.c:store_json_encode_slot", c_store_json, e_store_json}, + {"trace.c:write_slot", c_write_slot, e_write_slot}, + {"value_slot.h:slot_f64_value", c_f64_value, e_f64_value}, + {"value_slot.h:slot_int_value", c_int_value, e_int_value}, + {"value_slot.h:slot_is_f64", c_is_f64, e_is_f64}, + {"value_slot.h:slot_is_smi", c_is_smi, e_is_smi}, + {"value_slot.h:slot_smi_val", c_smi_val, e_smi_val}, + {"value_slot.h:slot_truthy_immediate", c_truthy_imm, e_truthy_imm}, + {"vm.c:obs_dump_value", c_dump_value, e_dump_value}, + {"vm.c:obs_slot_num", c_obs_num, e_obs_num}, + {"vm.c:slot_truthy", c_truthy, e_truthy}, + {"vm.c:slot_type_name", c_type_name, e_type_name}, + {"value_slot.h:slot_decref", c_decref, e_ok}, + {"value_slot.h:slot_incref", c_incref, e_ok}, + {"value_slot.h:slot_is_bool", c_is_bool, e_is_bool}, + {"value_slot.h:slot_is_heap", c_is_heap, e_is_heap}, + {"value_slot.h:slot_is_null", c_is_null, e_is_null}, + {"value_slot.h:slot_is_ptr", c_is_ptr, e_is_heap}, + {"container.h:eigs_slot_box", c_slot_box, e_slot_box}, + {"container.h:slot_type", c_slot_type, e_slot_type}, + {"container.h:slot_view", c_slot_view, e_slot_view}, + {"eigenscript.c:slot_to_value", c_to_value, e_to_value}, + {"vm.c:obs_slot_observable", c_observable, e_observable}, + {"vm.c:slot_bridge_unwrap", c_unwrap, e_unwrap}, + {"container.h:eigs_slot_ref", c_slot_ref, e_slot_ref}, + {"value_slot.h:slot_as_ptr", c_as_ptr, e_as_ptr}, + {"vm.c:obs_slot_value", c_obs_value, e_obs_value}, +}; +#define N_MATRIX ((int)(sizeof MATRIX / sizeof MATRIX[0])) + +/* Run cell(s) in a child; return its outcome as a token: the rendered result + * (plus " raise" when the call raised), `A:` for an abort with a FATAL + * line naming the reader, `X:...` for anything else (a crash, a bad exit). + * `pre`, when given, receives what the child printed before its result. */ +static void run_cell(void (*cell)(EigsSlot, char *, size_t), EigsSlot s, char *got, size_t gn, + char *pre, size_t pn) { + if (pre) pre[0] = 0; + int p[2]; + if (pipe(p) != 0) { snprintf(got, gn, "X:pipe"); return; } + fflush(stdout); fflush(stderr); + pid_t pid = fork(); + if (pid < 0) { snprintf(got, gn, "X:fork"); return; } + if (pid == 0) { + child_no_core(); + close(p[0]); + dup2(p[1], 1); dup2(p[1], 2); + char b[512]; + g_has_error = 0; + cell(s, b, sizeof b); + printf("R:%s%s\n", b, g_has_error ? " raise" : ""); + fflush(stdout); + _exit(0); + } + close(p[1]); + char buf[2048]; + size_t len = 0; + ssize_t r; + while (len < sizeof buf - 1 && (r = read(p[0], buf + len, sizeof buf - 1 - len)) > 0) len += (size_t)r; + buf[len] = 0; + close(p[0]); + int st = 0; + waitpid(pid, &st, 0); + char *f = strstr(buf, "EigenScript FATAL: "); + if (WIFSIGNALED(st) && WTERMSIG(st) == SIGABRT && f) { + f += strlen("EigenScript FATAL: "); + char *end = strstr(f, ": "); + snprintf(got, gn, "A:%.*s", end ? (int)(end - f) : (int)strcspn(f, "\n"), f); + return; + } + char *rl = (strncmp(buf, "R:", 2) == 0) ? buf : strstr(buf, "\nR:"); + if (rl && rl != buf) rl++; + if (pre && rl) snprintf(pre, pn, "%.*s", (int)(rl - buf), buf); + if (WIFEXITED(st) && WEXITSTATUS(st) == 0 && rl) { + snprintf(got, gn, "%.*s", (int)strcspn(rl + 2, "\n"), rl + 2); + return; + } + snprintf(got, gn, "X:%s %d: %.*s", WIFSIGNALED(st) ? "signal" : "exit", + WIFSIGNALED(st) ? WTERMSIG(st) : WEXITSTATUS(st), (int)strcspn(buf, "\n"), buf); +} + +static int part_b(void) { + int cells = 0; + for (int i = 0; i < N_MATRIX; i++) { + const MatrixEntry *m = &MATRIX[i]; + int bad = 0; + for (int k = 0; k < N_KINDS; k++, cells++) { + char want[256], got[512]; + m->expect(k, want, sizeof want); + run_cell(m->cell, bits(KB[k]), got, sizeof got, NULL, 0); + if (strcmp(want, got) != 0) { + FAILF("%s(%s): got %s, want %s", m->name, KIND_NAMES[k], got, want); + bad++; + } + } + printf("MATRIX %s kinds=%d %s\n", m->name, N_KINDS, bad ? "FAIL" : "ok"); + } + return cells; +} + +/* ---- Part C: slot_from_int (plant 1's named check) ----------------------- */ + +static int part_c(void) { + static const int64_t inside[] = { + 0, 1, -1, 12345, -12345, (INT64_C(1) << 47) - 1, -(INT64_C(1) << 47), + }; + static const int64_t outside[] = { + INT64_C(1) << 47, -(INT64_C(1) << 47) - 1, INT64_MAX, INT64_MIN, + }; + int rows = 0; + for (size_t k = 0; k < sizeof inside / sizeof inside[0]; k++, rows++) { + int64_t v = inside[k]; + EigsSlot s = slot_from_int(v); + /* plant 1's named check: an int that fits is stored as an SMI */ + if (s.u != smi(v).u) + FAILF("slot_from_int(%lld): bits 0x%016llx (tag 0x%04llx), want the SMI 0x%016llx", + (long long)v, (unsigned long long)s.u, (unsigned long long)(s.u >> 48), + (unsigned long long)smi(v).u); + if (slot_smi_val(smi(v)) != v) + FAILF("slot_smi_val(SMI %lld) = %lld", (long long)v, (long long)slot_smi_val(smi(v))); + } + for (size_t k = 0; k < sizeof outside / sizeof outside[0]; k++, rows++) { + EigsSlot boxed = slot_from_int(outside[k]); + int64_t got = 0; + if (!slot_is_heap(boxed) || !slot_int_value(boxed, &got) || got != outside[k] || + slot_as_ptr(boxed)->type != VAL_INT) + FAILF("slot_from_int(%lld): box loses exact int64", (long long)outside[k]); + slot_decref(boxed); + } + return rows; +} + +/* ---- Part D: the VM's operator arms, with an int in a list ------------- */ + +/* A host builtin puts a hand-built SMI (or a float control) in a list. + * Every R1.3-converted arm computes its final value, including under try. */ +static int g_poke_smi; +static EigsValue *h_poke(EigsValue *arg) { + Value *t = list_get_ref(arg, 0); + if (!t || t->type != VAL_LIST || t->data.list.count < 1) { printf("POKE: bad target\n"); _exit(97); } + EigsSlot old = t->data.list.items[0]; + t->data.list.items[0] = g_poke_smi ? smi(5) : slot_from_f64(5.0); + slot_decref(old); + return make_null(); +} +typedef struct { const char *name, *expr, *f64_out, *int_out; int in_try; } Arm; +static const Arm ARMS[] = { + {"lt", "xs[0] < 6", "true", "true", 0}, + {"gt", "xs[0] > 6", "false", "false", 0}, + {"le", "xs[0] <= 6", "true", "true", 0}, + {"ge", "xs[0] >= 6", "false", "false", 0}, + {"lt_in_try", "xs[0] < 6", "true", "true", 1}, + {"band", "xs[0] & 3", "1", "1", 0}, + {"bor", "xs[0] | 3", "7", "7", 0}, + {"bxor", "xs[0] ^ 3", "6", "6", 0}, + {"shl", "xs[0] << 1", "10", "10", 0}, + {"shr", "xs[0] >> 1", "2", "2", 0}, + {"band_in_try", "xs[0] & 3", "1", "1", 1}, + {"neg", "-(xs[0])", "-5.0", "-5", 0}, + {"neg_in_try", "-(xs[0])", "-5.0", "-5", 1}, + {"bnot", "~(xs[0])", "-6", "-6", 0}, + {"eq", "xs[0] == 5", "true", "true", 0}, + {"add", "xs[0] + 1", "6.0", "6", 0}, + {"overflow_add", "9223372036854775807 + xs[0]", "9.223372036854776e+18", "value", 1}, +}; +#define N_ARMS ((int)(sizeof ARMS / sizeof ARMS[0])) +static const Arm *g_arm; +static void c_arm(EigsSlot s, char *b, size_t n) { + (void)s; + char prog[512]; + if (g_arm->in_try) + snprintf(prog, sizeof prog, "xs is [7.5]\nphase2_poke of ([xs])\ntry:\n print of (%s)\n" + "catch e:\n print of e.kind\n", g_arm->expr); + else + snprintf(prog, sizeof prog, "xs is [7.5]\nphase2_poke of ([xs])\nprint of (%s)\n", g_arm->expr); + EigsValue *r = eigs_eval_string(prog); + fflush(stdout); + snprintf(b, n, "%s", r ? "ran" : "eval-error"); + eigs_value_release(r); +} +static int part_d(void) { + eigs_register_function("phase2_poke", h_poke); + int rows = 0; + for (int i = 0; i < N_ARMS; i++) { + g_arm = &ARMS[i]; + int bad = 0; + char got[512], want[256], pre[512], wpre[64]; + g_poke_smi = 1; + run_cell(c_arm, slot_null(), got, sizeof got, pre, sizeof pre); + snprintf(want, sizeof want, "%s\n", g_arm->int_out); + if (strcmp(got, "ran") != 0 || strcmp(pre, want) != 0) { + FAILF("arm %s with an int: got %s printed %s, want ran and %s", g_arm->name, got, pre, want); + bad++; + } + g_poke_smi = 0; + run_cell(c_arm, slot_null(), got, sizeof got, pre, sizeof pre); + snprintf(wpre, sizeof wpre, "%s\n", g_arm->f64_out); + if (strcmp(got, "ran") != 0 || strcmp(pre, wpre) != 0) { + FAILF("arm %s control (a double): got %s, printed \"%.*s\", want ran and %s", g_arm->name, got, + (int)strcspn(pre, "\n"), pre, g_arm->f64_out); + bad++; + } + rows += 2; + printf("ARM %s %s\n", g_arm->name, bad ? "FAIL" : "ok"); + } + return rows; +} + +/* ---- Part E: the four ITER_NEXT metadata sites (#1827) ------------------ */ +/* Raw states retain float input controls alongside SMI/boxed inputs. + * S9-K1 (#1758) publishes every yielded successor index as an exact int. + * Exercise the interpreter through an assembled chunk and the real helper. */ +typedef struct { const char *name; int field, kind; int64_t value; int yields; } IterCase; +static const IterCase ITER_CASES[] = { + {"float_control", 1, 0, 0, 1}, + {"smi_index", 1, 1, 0, 1}, + {"boxed_index", 1, 2, 0, 1}, + {"smi_snapshot", 2, 1, 1, 1}, + {"boxed_snapshot", 2, 2, 1, 1}, + {"large_index", 1, 2, INT64_MAX, 0}, + {"negative_index", 1, 2, INT64_MIN, 0}, + {"large_snapshot", 2, 2, INT64_MAX, 1}, + {"negative_snapshot", 2, 2, INT64_MIN, 0}, + {"smi_negative_snapshot", 2, 1, -1, 0}, + {"malformed_index", 1, 3, 0, 0}, + {"malformed_snapshot", 2, 3, 0, 1}, + {"malformed_iterable", 0, 2, 42, 0}, +}; +#define N_ITER_CASES ((int)(sizeof ITER_CASES / sizeof ITER_CASES[0])) +static const IterCase *g_iter_case; +static int g_iter_helper; +static void c_iter(EigsSlot unused, char *b, size_t n) { + (void)unused; + const IterCase *t = g_iter_case; + Value *state = make_list(0), *items = make_list(0); + list_append_slot(items, slot_from_f64(42.0)); + list_append_slot(state, slot_from_heap(items)); + val_decref(items); + list_append_slot(state, slot_from_f64(0.0)); + list_append_slot(state, slot_from_f64(1.0)); + EigsSlot replacement = t->kind == 0 ? slot_from_f64((double)t->value) : + t->kind == 1 ? smi(t->value) : + t->kind == 2 ? slot_from_heap(make_int(t->value)) : + slot_from_heap(make_str("bad")); + /* Raw states can hold non-canonical small boxes. Bypass writers here + * deliberately, and prove which representation reaches each reader. */ + EigsSlot old_metadata = state->data.list.items[t->field]; + state->data.list.items[t->field] = replacement; + slot_decref(old_metadata); + EigsSlot stored_metadata = list_slot(state, t->field); + int representation_ok = t->kind == 0 ? slot_is_f64(stored_metadata) : + t->kind == 1 ? slot_is_smi(stored_metadata) : + t->kind == 2 ? (slot_is_heap(stored_metadata) && + slot_as_ptr(stored_metadata)->type == VAL_INT && + VAL_I64_RAW(slot_as_ptr(stored_metadata)) == t->value) : + (slot_is_heap(stored_metadata) && + slot_as_ptr(stored_metadata)->type == VAL_STR); + if (!representation_ok) { + snprintf(b, n, "wrong-metadata-representation"); + val_decref(state); + return; + } + Value *result; + if (g_iter_helper) { + vm_init(); /* direct helper calls need the VM allocated, as vm_execute does */ + val_incref(state); + vm_push(state); + int rc = jit_helper_iter_next(); + result = rc == 0 ? vm_pop() : make_null(); + Value *popped_state = vm_pop(); + val_decref(popped_state); + } else { + EigsChunk *chunk = chunk_new(""); + int ci = chunk_add_constant(chunk, state); + chunk_emit(chunk, OP_CONST, 1); + chunk_emit_u16(chunk, (uint16_t)ci, 1); + chunk_emit(chunk, OP_ITER_NEXT, 1); + chunk_emit_u16(chunk, 1, 1); /* exhausted: skip the element RETURN */ + chunk_emit(chunk, OP_RETURN, 1); + chunk_emit(chunk, OP_NULL, 1); + chunk_emit(chunk, OP_RETURN, 1); + result = vm_execute(chunk, g_global_env); + chunk_free(chunk); + } + if (result && result->type == VAL_FLOAT && VAL_F64_RAW(result) == 42.0) { + int64_t next = -1; + int int_next = slot_int_value(list_slot(state, 1), &next); + snprintf(b, n, "yield:42 int_next:%d", int_next && next == 1); + } else if (result && result->type == VAL_NULL) snprintf(b, n, "exhausted"); + else snprintf(b, n, "unexpected"); + val_decref(result); + val_decref(state); +} +static int part_e(void) { + int rows = 0; + for (int i = 0; i < N_ITER_CASES; i++) { + g_iter_case = &ITER_CASES[i]; + for (g_iter_helper = 0; g_iter_helper < 2; g_iter_helper++) { + char got[512]; + run_cell(c_iter, slot_null(), got, sizeof got, NULL, 0); + const char *want = g_iter_case->yields ? "yield:42 int_next:1" : "exhausted"; + int bad = strcmp(got, want) != 0; + if (bad) FAILF("ITER_NEXT %s %s: got %s, want %s", + g_iter_helper ? "jit_helper" : "interpreter", g_iter_case->name, got, want); + printf("ARM iter_%s_%s %s\n", g_iter_helper ? "jit" : "vm", + g_iter_case->name, bad ? "FAIL" : "ok"); + rows++; + } + } + return rows; +} + +int main(void) { + EigsState *st = eigs_open(); + if (!st) { printf("slot-layer-test: FAIL eigs_open\n"); return 1; } + /* Part B reads the canonical NaN back through make_float, whose guard + * raises under strict mode; the test pins the soft read instead. */ + eigs_state_set_strict(st, 0); + g_str = make_str("x"); + static const uint64_t kinds[N_KINDS] = { + 0x3FF8000000000000ULL, 0x7FF8000000000000ULL, 0x8000000000000000ULL, + 0xFFF8000000000000ULL, 0xFFF9000000000000ULL, 0xFFF9000000000001ULL, + T_SMI_TAG | 5, 0, 0xFFFC000000000000ULL, 0xFFFD000000000000ULL, + 0xFFFE000000000000ULL, 0xFFFF000000000000ULL, + }; + memcpy(KB, kinds, sizeof KB); + KB[K_HEAP] = slot_from_heap(g_str).u; + size_t before = g_out_len; + write_value_ptr(g_str); + snprintf(g_heap_tape, sizeof g_heap_tape, "%.*s", (int)(g_out_len - before), g_out + before); + int calls = part_a(); + int matrix_cells = part_b(); + int from_int_rows = part_c(); + int arm_rows = part_d(); + int iter_rows = part_e(); + printf("slot-layer-test: examined=%d smi_values=%d calls=%d matrix=%d kinds=%d matrix_cells=%d " + "from_int_rows=%d arm_rows=%d iter_rows=%d failures=%d\n", N_ENTRIES, N_SMI, calls, N_MATRIX, N_KINDS, + matrix_cells, from_int_rows, arm_rows, iter_rows, failures); + val_decref(g_str); + eigs_close(st); + int ok = failures == 0 && N_ENTRIES > 0 && calls > 0 && N_MATRIX == N_ENTRIES && + matrix_cells == N_MATRIX * N_KINDS && from_int_rows > 0 && arm_rows == 2 * N_ARMS && iter_rows == 2 * N_ITER_CASES; + printf("slot-layer-test: %s\n", ok ? "PASS" : "FAIL"); + return ok ? 0 : 1; +} diff --git a/tests/phase2/slot_layer.sh b/tests/phase2/slot_layer.sh new file mode 100755 index 000000000..d487bbda1 --- /dev/null +++ b/tests/phase2/slot_layer.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# tests/phase2/slot_layer.sh [release|asan] -- #1758 R1.1: the C slot test +# (docs/design/phase2-numbers.md 2.5), with its population checked. +# +# 1. Derive the population: `tools/phase2_inventory.py --slot-population` +# lists every slot classifier whose first parameter is an EigsSlot. +# 2. Build tests/phase2/slot_layer.c against the amalgamation +# (make phase2-slot-test; -O0 for release, -O1 for asan, both -Wall +# -Wextra) and run it. Under gcc, the reference toolchain for #1775's +# class, a build warning is a FAIL. Under any other compiler (clang on +# macOS) the slot test follows the main build's policy there: warnings +# print, and only the -Werror set fails (the compile itself stops). +# 3. The ENTRY lines (Part A) and the MATRIX lines (Part B) must each equal +# the derived population, both ways; the examined count must equal its +# size, which must be above zero; the test's own verdict must be PASS. +# +# Every output this script PARSES is read on its own channel and against its +# own grammar (the R1.1 critic: a Python DeprecationWarning on the scan's +# stderr was read as two classifiers). A tool that writes to stderr, or a line +# outside the grammar, is a FAIL that prints the line; nothing is skipped. +# Python runs with `-W error`, so a new warning fails here, loudly. +# Prints the test's whole output, then `slot-layer-population: ...` and a +# final `slot-layer: PASS|FAIL` line. Exit 0 only for PASS. +set -u +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +VARIANT="${1:-release}" +case "$VARIANT" in release|asan) ;; *) echo "slot-layer: FAIL: unknown variant $VARIANT"; exit 2 ;; esac +TMP=$(mktemp -d "${TMPDIR:-/tmp}/eigs-slot-layer.XXXXXX") || { echo "slot-layer: FAIL: mktemp"; exit 2; } +trap 'rm -rf "$TMP"' EXIT +fail() { echo "slot-layer: FAIL: $*"; exit 1; } +# show_stderr FILE WHO: a tool that wrote to stderr is a FAIL, with its words +show_stderr() { + if [ -s "$1" ]; then + sed "s/^/ $2 stderr: /" "$1" + fail "$2 wrote to stderr (a population read beside its diagnostics is not trusted)" + fi +} + +command -v python3 >/dev/null 2>&1 || fail "python3 is required (the population and the amalgamation need it)" +python3 -W error "$ROOT/tools/phase2_inventory.py" --no-write --slot-population > "$TMP/pop.txt" 2> "$TMP/pop.err" +prc=$? +show_stderr "$TMP/pop.err" "the population scan" +if [ "$prc" -ne 0 ]; then + sed 's/^/ inventory: /' "$TMP/pop.txt" + fail "the population scan exited $prc" +fi +# The scan's grammar: `file:function` lines, then one `slot-population: entries=N`. +ENTRY_RE='^[A-Za-z0-9_]+[.][ch]:[A-Za-z_][A-Za-z0-9_]*$' +grep -Ev "$ENTRY_RE" "$TMP/pop.txt" | grep -Ev '^slot-population: entries=[0-9]+$' > "$TMP/pop.bad" +if [ -s "$TMP/pop.bad" ]; then + sed 's/^/ UNPARSED population line: /' "$TMP/pop.bad" + fail "the population scan printed lines outside its grammar" +fi +grep -E "$ENTRY_RE" "$TMP/pop.txt" | LC_ALL=C sort > "$TMP/want" +n_want=$(grep -c . "$TMP/want") +declared=$(awk '/^slot-population: entries=/ { sub(/^slot-population: entries=/, ""); print }' "$TMP/pop.txt") +[ -n "$declared" ] && [ "$(grep -c '^slot-population:' "$TMP/pop.txt")" -eq 1 ] || + fail "the population scan printed no single summary line" +[ "$declared" = "$n_want" ] || fail "the population scan declared $declared entries and listed $n_want" +[ "$n_want" -gt 0 ] || fail "the population scan listed no entry" + +make -s -C "$ROOT" PHASE2_SLOT_VARIANT="$VARIANT" phase2-slot-test > "$TMP/build.txt" 2>&1 +brc=$? +BIN="$ROOT/build/phase2/slot_layer_$VARIANT" +if [ "$brc" -ne 0 ]; then + sed 's/^/ build: /' "$TMP/build.txt" + fail "make phase2-slot-test exited $brc" +fi +# #1775: the amalgamation compiles warning-free at the slot test's level (-O0 +# for release, ASan's -O1 for asan, both with -Wall -Wextra). The rule was +# established on gcc, the reference toolchain for that class, and holds only +# there: R1.1's macOS lane failed it with 103 clang-only warnings +# (-Wdefault-const-init-field-unsafe on EigsView) that the main clang build +# prints too. The compiler is asked, not guessed: gcc defines __GNUC__ and +# not __clang__. Under gcc a warning fails and the binary is removed, so the +# next run compiles, and warns, again; under another compiler it prints. +SLOT_CC=$(make -s -C "$ROOT" --no-print-directory print-CC) +SLOT_CC_MACROS=$(printf '' | $SLOT_CC -dM -E -x c - 2> /dev/null) +case "$SLOT_CC_MACROS" in + *"#define __clang__"*) SLOT_CC_KIND=clang ;; + *"#define __GNUC__"*) SLOT_CC_KIND=gcc ;; + *) SLOT_CC_KIND=other ;; +esac +if grep -q 'warning:' "$TMP/build.txt"; then + if [ "$SLOT_CC_KIND" = gcc ]; then + grep 'warning:' "$TMP/build.txt" | sed 's/^/ build warning: /' + rm -f "$BIN" + fail "the slot test build printed $(grep -c 'warning:' "$TMP/build.txt") warning(s) (#1775)" + fi + grep 'warning:' "$TMP/build.txt" | sed "s/^/ build warning ($SLOT_CC_KIND: printed, not failing): /" +fi +[ -x "$BIN" ] || fail "no binary at $BIN after the build" + +"$BIN" > "$TMP/out.txt" 2> "$TMP/out.err" +trc=$? +cat "$TMP/out.txt" +show_stderr "$TMP/out.err" "the slot test" +# The test's grammar (its header comment); anything else is refused. +NAME_RE='[A-Za-z0-9_]+[.][ch]:[A-Za-z_][A-Za-z0-9_]*' +grep -Ev "^ENTRY $NAME_RE [A-Za-z_]+ values=[0-9]+ (ok|FAIL)$" "$TMP/out.txt" | + grep -Ev "^MATRIX $NAME_RE kinds=[0-9]+ (ok|FAIL)$" | + grep -Ev '^ARM [a-z_]+ (ok|FAIL)$' | + grep -Ev '^slot-layer-test: (FAIL .+|examined=[0-9]+ .*failures=[0-9]+|PASS|FAIL)$' > "$TMP/out.bad" +if [ -s "$TMP/out.bad" ]; then + sed 's/^/ UNPARSED slot test line: /' "$TMP/out.bad" + fail "the slot test printed lines outside its grammar" +fi +awk '$1 == "ENTRY" { print $2 }' "$TMP/out.txt" | LC_ALL=C sort > "$TMP/got" +awk '$1 == "MATRIX" { print $2 }' "$TMP/out.txt" | LC_ALL=C sort > "$TMP/gotm" +n_got=$(grep -c . "$TMP/got") +n_gotm=$(grep -c . "$TMP/gotm") +examined=$(awk '/^slot-layer-test: examined=/ { sub(/^slot-layer-test: examined=/, ""); sub(/ .*/, ""); print }' "$TMP/out.txt") +LC_ALL=C comm -23 "$TMP/want" "$TMP/got" > "$TMP/missing" +LC_ALL=C comm -13 "$TMP/want" "$TMP/got" > "$TMP/extra" +LC_ALL=C comm -3 "$TMP/want" "$TMP/gotm" > "$TMP/mdiff" +n_missing=$(grep -c . "$TMP/missing") +n_extra=$(grep -c . "$TMP/extra") +n_mdiff=$(grep -c . "$TMP/mdiff") +sed 's/^/ MISSING (a slot classifier the test never called): /' "$TMP/missing" +sed 's/^/ EXTRA (a test entry that is not a derived slot classifier): /' "$TMP/extra" +sed 's/^/ MATRIX differs from the population: /' "$TMP/mdiff" +echo "slot-layer-population: derived=$n_want examined=${examined:-none} entries=$n_got matrix=$n_gotm missing=$n_missing extra=$n_extra matrix_diff=$n_mdiff test_rc=$trc" +[ "$trc" -eq 0 ] || fail "the test exited $trc" +grep -q '^slot-layer-test: PASS$' "$TMP/out.txt" || fail "the test printed no PASS verdict" +[ "${examined:-x}" = "$n_want" ] || fail "examined=${examined:-none} differs from the derived population ($n_want)" +[ "$n_got" -eq "$n_want" ] && [ "$n_missing" -eq 0 ] && [ "$n_extra" -eq 0 ] || + fail "the examined entries differ from the derived population" +[ "$n_gotm" -eq "$n_want" ] && [ "$n_mdiff" -eq 0 ] || + fail "the matrix entries differ from the derived population" +echo "slot-layer: PASS" diff --git a/tests/phase2/test_cost_methods.py b/tests/phase2/test_cost_methods.py new file mode 100644 index 000000000..5e34ee770 --- /dev/null +++ b/tests/phase2/test_cost_methods.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""One-time calibration for #1939's method-specific temporary cost bounds.""" +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / 'tools')) +import phase2_cost_declarations as costs + +class CostMethods(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix='phase2-cost-methods-') + self.root = Path(self.temporary.name) + for name in ('tools', 'tests/phase2', 'bench'): + (self.root / name).mkdir(parents=True, exist_ok=True) + for name in ('tools/phase2_cost.sh', 'tools/phase2_cost_declarations.py', + 'bench/check_regression.sh'): + source = ROOT / name + if source.is_file(): + shutil.copy2(source, self.root / name) + (self.root / 'codspeed.yml').write_text('exec: ./src/eigenscript bench/scalar_loop.eigs\n') + (self.root / 'bench/scalar_loop.eigs').write_text('') + subprocess.run(['git', 'init', '-q', self.root], check=True) + subprocess.run(['git', 'add', 'bench/scalar_loop.eigs'], cwd=self.root, check=True) + + def tearDown(self): + self.temporary.cleanup() + + def declaration(self, local='27.0', ci='27.3'): + (self.root / 'tests/phase2/cost_expect.txt').write_text( + 'bench/scalar_loop.eigs\tjit\t%s\t%s\tR5\tmeasured by each gate\n' % (local, ci)) + + def run_gate(self, *args): + return subprocess.run(['bash', self.root / 'tools/phase2_cost.sh', *args], + cwd=self.root, capture_output=True, text=True) + + def test_independent_column_export(self): + self.declaration() + for method, maximum in (('local', '27.0'), ('ci', '27.3')): + with self.subTest(method=method): + result = self.run_gate('--declared', 'jit', '--method', method) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, 'bench/scalar_loop.eigs\t%s\tR5\n' % maximum) + + def test_all_export_retains_declared_tiers(self): + self.declaration() + table = self.root / 'tests/phase2/cost_expect.txt' + table.write_text(table.read_text() + + 'bench/scalar_loop.eigs\toff\t9.0\t8.0\tR5\tmeasured off\n') + result = self.run_gate('--declared', 'all', '--method', 'ci') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, 'bench/scalar_loop.eigs\tjit\t27.3\tR5\n' + 'bench/scalar_loop.eigs\toff\t8.0\tR5\n') + + def test_ci_population_is_union_of_existing_and_all_declared_pairs(self): + workload = 'tests/bench_probe.eigs' + (self.root / workload).write_text('') + with (self.root / 'codspeed.yml').open('a') as stream: + stream.write('exec: ./src/eigenscript ' + workload + '\n') + subprocess.run(['git', 'add', workload], cwd=self.root, check=True) + (self.root / 'tests/phase2/cost_expect.txt').write_text( + 'bench/scalar_loop.eigs\toff\t9.0\t8.0\tR5\tmeasured off\n' + 'tests/bench_probe.eigs\tjit\t1.0\t2.0\tR5\tmeasured jit\n' + 'tests/bench_probe.eigs\toff\t3.0\t4.0\tR5\tmeasured off\n') + result = subprocess.run(['bash', self.root / 'bench/check_regression.sh', '--list-vs'], + cwd=self.root, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + self.assertEqual(result.stdout.splitlines(), ['bench/scalar_loop.eigs\tjit', + 'bench/scalar_loop.eigs\toff', 'tests/bench_probe.eigs\tjit', + 'tests/bench_probe.eigs\toff']) + + def run_row(self, method, base, cand): + return subprocess.run(['python3', self.root / 'tools/phase2_cost_declarations.py', + '--method', method, '--row', 'bench/scalar_loop.eigs', + 'jit', str(base), str(cand)], cwd=self.root, + capture_output=True, text=True) + + def test_local_and_ci_bounds_are_independent(self): + # The issue's two method readings cannot share one tight maximum. + for method, local, ci, cand in (('local', '27.0', '0.2', 12672000), + ('ci', '0.2', '27.3', 12704780)): + with self.subTest(method=method): + self.declaration(local, ci) + result = self.run_row(method, 10000000, cand) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn('DECLARED:', result.stdout) + self.assertIn('max_' + method + '=', result.stdout) + other = 'ci' if method == 'local' else 'local' + result = self.run_row(other, 10000000, cand) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn('REGRESSION:', result.stdout) + + def test_stale_checks_only_own_column(self): + for method, local, ci in (('local', '10.3', '90'), ('ci', '90', '10.3')): + with self.subTest(method=method): + self.declaration(local, ci) + result = self.run_row(method, 10000, 11000) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + other = 'ci' if method == 'local' else 'local' + result = self.run_row(other, 10000, 11000) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn('STALE DECLARATION:', result.stdout) + + def test_exact_boundaries_and_no_stale_for_undeclared_rows(self): + self.declaration('10.3', '10.3') + for method in costs.METHODS: + for cand, verdict in ((11030, 'DECLARED:'), (11031, 'REGRESSION:'), + (10980, 'DECLARED:'), (10979, 'STALE DECLARATION:')): + with self.subTest(method=method, cand=cand): + result = self.run_row(method, 10000, cand) + self.assertEqual(result.returncode, int(verdict != 'DECLARED:'), result.stdout) + self.assertIn(verdict, result.stdout) + (self.root / 'tests/phase2/cost_expect.txt').write_text('') + for method in costs.METHODS: + result = self.run_row(method, 10000, 1) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn('ok:', result.stdout) + self.assertEqual(self.run_row('local', 10000, 10400).returncode, 1) + self.assertEqual(self.run_row('ci', 10000, 10400).returncode, 0) + + def test_shared_predicate_reproduces_issue(self): + # Exercise the production function with independent columns, both ways. + table = costs.parse_declarations('bench/scalar_loop.eigs\tjit\t27.0\t27.3\tR5\tmeasured\n') + self.assertEqual(costs.assess_row(table, 'bench/scalar_loop.eigs', 'jit', 'local', + 10000000, 12672000)[2], 'ok') + self.assertEqual(costs.assess_row(table, 'bench/scalar_loop.eigs', 'jit', 'ci', + 10000000, 12704780)[2], 'ok') + self.assertEqual(costs.assess_row(table, 'bench/scalar_loop.eigs', 'jit', 'local', + 10000000, 12704780)[2], 'OVER') + self.assertEqual(costs.assess_row(table, 'bench/scalar_loop.eigs', 'jit', 'ci', + 10000000, 12672000)[2], 'STALE') + + def test_schema_rejects_invalid_columns_and_legacy_shape(self): + good = 'bench/scalar_loop.eigs\tjit\t27.0\t27.3\tR5\tmeasured\n' + bad = [good.replace('\t27.3', ''), good + good] + for column in ('27.0', '27.3'): + bad.extend(good.replace(column, value) for value in ('-1', 'NaN', 'Infinity', '')) + for text in bad: + with self.subTest(text=text): + with self.assertRaises(ValueError): + costs.parse_declarations(text) + with self.assertRaises(ValueError): + costs.parse_declarations(good, {'bench/missing.eigs'}) + + def test_method_selection_cannot_change_local_measurement(self): + self.declaration() + result = self.run_gate('--method', 'ci', '/bin/true', '/bin/true') + self.assertEqual(result.returncode, 2) + self.assertIn('cost runs enforce max_local', result.stderr) + +if __name__ == '__main__': + if '--self-test' in sys.argv: + sys.argv.remove('--self-test') + unittest.main(verbosity=2) diff --git a/tests/replay_diff_expected.txt b/tests/replay_diff_expected.txt index 3bc0596d9..73bf91dc4 100644 --- a/tests/replay_diff_expected.txt +++ b/tests/replay_diff_expected.txt @@ -1 +1,4 @@ +# tests/replay_diff_expected.txt -- the programs whose record-vs-replay output +# diverges reproducibly (tools/replay_diff.sh). A ledger to work down: a row +# is a fidelity hole, with its issue on the line above it. test_store_corruption.eigs diff --git a/tests/roads/native_alternate.eigs b/tests/roads/native_alternate.eigs index 422afbc35..95c141942 100644 --- a/tests/roads/native_alternate.eigs +++ b/tests/roads/native_alternate.eigs @@ -1,13 +1,13 @@ # road-bind: v total # road-native: required -v is 100 -total is 0 -for i in range of 1200: - if i % 2 == 0: - local v is 10 - j is 0 - loop while j < 12: - v is v + 1 +v is 100.0 +total is 0.0 +for i in range of 1200.0: + if i % 2.0 == 0.0: + local v is 10.0 + j is 0.0 + loop while j < 12.0: + v is v + 1.0 total is total + v - j is j + 1 + j is j + 1.0 print of [v, total] diff --git a/tests/roads/native_alternate.out b/tests/roads/native_alternate.out index 1e387a0ac..152435c74 100644 --- a/tests/roads/native_alternate.out +++ b/tests/roads/native_alternate.out @@ -1,3 +1,3 @@ -[7300, 26762400] -["v", 1, 7300] -["total", 1, 26762400] +[7300.0, 26762400.0] +["v", 1, 7300.0] +["total", 1, 26762400.0] diff --git a/tests/roads/native_catch.eigs b/tests/roads/native_catch.eigs index 811943c1a..750d301e1 100644 --- a/tests/roads/native_catch.eigs +++ b/tests/roads/native_catch.eigs @@ -1,14 +1,14 @@ # road-bind: v total # road-native: required -v is 100 -total is 0 -for i in range of 1200: +v is 100.0 +total is 0.0 +for i in range of 1200.0: try: - throw of 10 + throw of 10.0 catch v: - k is 0 - loop while k < 12: - v is v + 1 + k is 0.0 + loop while k < 12.0: + v is v + 1.0 total is total + v - k is k + 1 + k is k + 1.0 print of [v, total] diff --git a/tests/roads/native_catch.out b/tests/roads/native_catch.out index 83a84dffc..0511910b1 100644 --- a/tests/roads/native_catch.out +++ b/tests/roads/native_catch.out @@ -1,3 +1,3 @@ -[100, 237600] -["v", 1, 100] -["total", 1, 237600] +[100.0, 237600.0] +["v", 1, 100.0] +["total", 1, 237600.0] diff --git a/tests/roads/native_inline.eigs b/tests/roads/native_inline.eigs index 473b53533..c2bbdc480 100644 --- a/tests/roads/native_inline.eigs +++ b/tests/roads/native_inline.eigs @@ -1,11 +1,11 @@ # road-bind: v # road-native: required -v is 100 -for i in [1]: - j is 0 - loop while j < 20000: - if j == 15000: - eval of "local v is 10" +v is 100.0 +for i in [1.0]: + j is 0.0 + loop while j < 20000.0: + if j == 15000.0: + eval of "local v is 10.0" v is j - j is j + 1 + j is j + 1.0 print of v diff --git a/tests/roads/native_inline.out b/tests/roads/native_inline.out index 12f180dd9..f96655935 100644 --- a/tests/roads/native_inline.out +++ b/tests/roads/native_inline.out @@ -1,2 +1,2 @@ -14999 -["v", 1, 14999] +14999.0 +["v", 1, 14999.0] diff --git a/tests/roads/native_late.eigs b/tests/roads/native_late.eigs index fb728a2b5..d7f0fbace 100644 --- a/tests/roads/native_late.eigs +++ b/tests/roads/native_late.eigs @@ -1,13 +1,13 @@ # road-bind: v total # road-native: required -v is 100 -total is 0 -for i in range of 1200: - if i > 500: - local v is 10 - j is 0 - loop while j < 12: - v is v + 1 +v is 100.0 +total is 0.0 +for i in range of 1200.0: + if i > 500.0: + local v is 10.0 + j is 0.0 + loop while j < 12.0: + v is v + 1.0 total is total + v - j is j + 1 + j is j + 1.0 print of [v, total] diff --git a/tests/roads/native_late.out b/tests/roads/native_late.out index fbeebb42a..3534abdc6 100644 --- a/tests/roads/native_late.out +++ b/tests/roads/native_late.out @@ -1,3 +1,3 @@ -[6112, 18814680] -["v", 1, 6112] -["total", 1, 18814680] +[6112.0, 18814680.0] +["v", 1, 6112.0] +["total", 1, 18814680.0] diff --git a/tests/roads/native_match.eigs b/tests/roads/native_match.eigs index d1f97d3fd..6492c6b55 100644 --- a/tests/roads/native_match.eigs +++ b/tests/roads/native_match.eigs @@ -1,14 +1,14 @@ # road-bind: v total # road-native: required -v is 100 -total is 0 -for i in range of 1200: - local v is 10 - match 10: +v is 100.0 +total is 0.0 +for i in range of 1200.0: + local v is 10.0 + match 10.0: case v: - k is 0 - loop while k < 12: - v is v + 1 + k is 0.0 + loop while k < 12.0: + v is v + 1.0 total is total + v - k is k + 1 + k is k + 1.0 print of [v, total] diff --git a/tests/roads/native_match.out b/tests/roads/native_match.out index 83a84dffc..0511910b1 100644 --- a/tests/roads/native_match.out +++ b/tests/roads/native_match.out @@ -1,3 +1,3 @@ -[100, 237600] -["v", 1, 100] -["total", 1, 237600] +[100.0, 237600.0] +["v", 1, 100.0] +["total", 1, 237600.0] diff --git a/tests/roads/native_outer.eigs b/tests/roads/native_outer.eigs index c63abc7c8..60ac5381a 100644 --- a/tests/roads/native_outer.eigs +++ b/tests/roads/native_outer.eigs @@ -1,14 +1,14 @@ # road-bind: v total # road-native: required -v is 100 -total is 0 -for i in range of 1200: - if i % 2 == 0: - local v is 10 - for b in [0, 1]: - k is 0 - loop while k < 12: - v is v + 1 +v is 100.0 +total is 0.0 +for i in range of 1200.0: + if i % 2.0 == 0.0: + local v is 10.0 + for b in [0.0, 1.0]: + k is 0.0 + loop while k < 12.0: + v is v + 1.0 total is total + v - k is k + 1 + k is k + 1.0 print of [v, total] diff --git a/tests/roads/native_outer.out b/tests/roads/native_outer.out index a0fef0be5..7cb75fa38 100644 --- a/tests/roads/native_outer.out +++ b/tests/roads/native_outer.out @@ -1,3 +1,3 @@ -[14500, 105451200] -["v", 1, 14500] -["total", 1, 105451200] +[14500.0, 105451200.0] +["v", 1, 14500.0] +["total", 1, 105451200.0] diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh index 886ab0c6b..cd46b9fa3 100755 --- a/tests/run_all_tests.sh +++ b/tests/run_all_tests.sh @@ -135,6 +135,14 @@ echo() { EIGS_SUITE_SECTION="$__EIGS_SEC_LABEL" export EIGS_SUITE_SECTION __EIGS_SEC_T0=$(__eigs_now_us) + if [ "${EIGS_BINARY_TRACE:-0}" = 1 ]; then + # Diagnostic only: retain alias identity at every section seam. + # The #681 checksum guard below remains the verdict. + local identity + identity=$(stat -L -c '%d:%i size=%s mtime=%y' ./eigenscript 2>/dev/null) || + identity=$(stat -L -f '%d:%i size=%z mtime=%m' ./eigenscript 2>/dev/null) || identity=missing + builtin printf 'BINARY_SEAM: time_us=%s section=%s %s\n' "$__EIGS_SEC_T0" "$__EIGS_SEC_LABEL" "$identity" + fi ;; esac builtin echo "$@" @@ -633,7 +641,7 @@ else # follows the store). EL_LIVE=$(sed -n 's/^LIVE: errline_1434=2 line \([0-9]*\)$/\1/p' <<< "$ERRLINE_OUT") EL_STEP=$(printf 'j 95\nt errline_1434\nq\n' | $EIGS_TMO ./eigenscript --step "$EL_TAPE" 2>&1 | - sed -n 's/^ #[0-9]* *line \([0-9]*\) *2 .*/\1/p') + sed -n 's/^ #[0-9]* *line \([0-9]*\) *2[.]0 .*/\1/p') rm -f "$EL_TAPE" TOTAL=$((TOTAL + 1)) if [ -n "$EL_LIVE" ] && [ "$EL_LIVE" = "$EL_STEP" ]; then @@ -859,7 +867,7 @@ check "T02 String Assignment" "$(echo "$OUTPUT" | grep -A1 'T02' | tail -1)" "he check "T03 Addition" "$(echo "$OUTPUT" | grep -A1 'T03' | tail -1)" "30" check "T04 Subtraction" "$(echo "$OUTPUT" | grep -A1 'T04' | tail -1)" "63" check "T05 Multiplication" "$(echo "$OUTPUT" | grep -A1 'T05' | tail -1)" "42" -check "T06 Division" "$(echo "$OUTPUT" | grep -A1 'T06' | tail -1)" "25" +check "T06 Division" "$(echo "$OUTPUT" | grep -A1 'T06' | tail -1)" "25.0" check "T07 String Concat" "$(echo "$OUTPUT" | grep -A1 'T07' | tail -1)" "hello world" check "T08 Boolean And" "$(echo "$OUTPUT" | grep -A1 'T08' | tail -1)" "1" check "T09 Boolean Or" "$(echo "$OUTPUT" | grep -A1 'T09' | tail -1)" "1" @@ -885,13 +893,13 @@ echo "" echo "[2/15] Interrogative Spec Compliance (LLVM IR parity)" OUTPUT=$(./eigenscript ../tests/test_interrogative_spec.eigs 2>&1) check "WHAT scalar (42)" "$(echo "$OUTPUT" | grep -A1 'eigen_get_value' | tail -1)" "42" -check "WHAT list length (5)" "$(echo "$OUTPUT" | grep -A1 'list length' | tail -1)" "5" -check "WHAT string length (5)" "$(echo "$OUTPUT" | grep -A1 'string length' | tail -1)" "5" +check "WHAT list length (5)" "$(echo "$OUTPUT" | grep -A1 'list length' | tail -1)" "5" # S9-K1 (#1758): published counts are ints. +check "WHAT string length (5)" "$(echo "$OUTPUT" | grep -A1 'string length' | tail -1)" "5" # S9-K1 (#1758): published counts are ints. check "WHAT computed (42)" "$(echo "$OUTPUT" | grep -A1 'computed value' | tail -1)" "42" check "WHO name (myvar)" "$(echo "$OUTPUT" | grep -A1 'variable name' | tail -1)" "myvar" check "WHO name (counter)" "$(echo "$OUTPUT" | grep -A1 'different variable' | tail -1)" "counter" -check "WHEN step (1)" "$(echo "$OUTPUT" | grep -A1 'temporal step' | tail -1)" "1" -check "WHEN multi-assign (3)" "$(echo "$OUTPUT" | grep -A1 'multiple assignments' | tail -1)" "3" +check "WHEN step (1)" "$(echo "$OUTPUT" | grep -A1 'temporal step' | tail -1)" "1" # S9-K1 (#1758): published counts are ints. +check "WHEN multi-assign (3)" "$(echo "$OUTPUT" | grep -A1 'multiple assignments' | tail -1)" "3" # S9-K1 (#1758): published counts are ints. WHERE_VAL=$(echo "$OUTPUT" | grep -A1 'WHERE returns entropy' | tail -1) check_numeric "WHERE entropy >= 0" "$WHERE_VAL" "0" "10" @@ -923,7 +931,7 @@ echo "[4/15] Keyword Reservation" OUTPUT=$(./eigenscript ../tests/test_keyword_reservation.eigs 2>&1) check "what is keyword" "$(echo "$OUTPUT" | grep -A1 '^what:' | tail -1)" "42" check "who is keyword" "$(echo "$OUTPUT" | grep -A1 '^who:' | tail -1)" "x" -check "when is keyword" "$(echo "$OUTPUT" | grep -A1 '^when:' | tail -1)" "1" +check "when is keyword" "$(echo "$OUTPUT" | grep -A1 '^when:' | tail -1)" "1" # S9-K1 (#1758): published counts are ints. CONVERGED=$(echo "$OUTPUT" | grep 'converged=' | head -1) echo " INFO: Predicate values: $CONVERGED" @@ -1059,7 +1067,7 @@ else fi HS_DH=$(echo "$HS_OUTPUT" | grep -A2 'HS2:' | tail -1) -check "HS4 dH~0" "$HS_DH" "0" +check "HS4 dH~0" "$HS_DH" "0.0" HS_REPORT=$(echo "$HS_OUTPUT" | grep -A3 'HS2:' | tail -1) check "HS5 report=converged (#861)" "$HS_REPORT" "converged" @@ -1179,7 +1187,9 @@ check "LE1 runaway exit=stalled (#861: false-converged exit gone)" "$LE1_EXIT" " LE1_ITERS=$(echo "$LE_OUTPUT" | grep -A2 'LE1:' | tail -1) TOTAL=$((TOTAL + 1)) -if [ -n "$LE1_ITERS" ] && [ "$LE1_ITERS" -gt 0 ] 2>/dev/null; then +# N6 prints this whole observer count with .0; retain the positive-count check. +LE1_ITERS_WHOLE=${LE1_ITERS%.0} +if [ -n "$LE1_ITERS" ] && [ "$LE1_ITERS_WHOLE" -gt 0 ] 2>/dev/null; then echo " PASS: LE1 iterations=$LE1_ITERS" PASS=$((PASS + 1)) else @@ -3531,7 +3541,7 @@ rm -f "$AUDIO_PROBE_FILE" echo "[62] Audio Synthesis" if ! echo "$AUDIO_PROBE_OUT" | grep -q "undefined variable"; then # #1361 EIGS_STRICT=0: pins the rejected-argument stand-ins audio_sine/audio_mix -> [], audio_play_loop -> 0, audio_stream_push -> 0. - AU_OUTPUT=$(EIGS_STRICT=0 ./eigenscript ../tests/test_audio.eigs 2>&1); AU_OUTPUT_RC=$? + AU_OUTPUT=$(suite_program_run test_audio.eigs ./eigenscript ../tests/test_audio.eigs 2>&1); AU_OUTPUT_RC=$? if rc_ok "$AU_OUTPUT_RC" "$AU_OUTPUT" && echo "$AU_OUTPUT" | grep -q "All tests passed"; then TOTAL=$((TOTAL + 1)) PASS=$((PASS + 1)) @@ -3564,8 +3574,8 @@ rm -f "$GT_PROBE_FILE" echo "[120b] Gfx Text Metrics" if ! echo "$GT_PROBE_OUT" | grep -q "undefined variable"; then # #1361 EIGS_STRICT=0: pins gfx_text_width of a non-string answering 0 (line 38), in both renderer modes. - GT_FB=$(gfx_headless_run 30 env EIGS_STRICT=0 EIGS_GFX_FONT=/nonexistent/eigs-no-font.ttf ./eigenscript ../tests/test_gfx_text.eigs 2>&1); GT_FB_RC=$? - GT_DEF=$(gfx_headless_run 30 env EIGS_STRICT=0 ./eigenscript ../tests/test_gfx_text.eigs 2>&1); GT_DEF_RC=$? + GT_FB=$(suite_program_run test_gfx_text.eigs gfx_headless_run 30 env EIGS_STRICT=0 EIGS_GFX_FONT=/nonexistent/eigs-no-font.ttf ./eigenscript ../tests/test_gfx_text.eigs 2>&1); GT_FB_RC=$? + GT_DEF=$(suite_program_run test_gfx_text.eigs gfx_headless_run 30 env EIGS_STRICT=0 ./eigenscript ../tests/test_gfx_text.eigs 2>&1); GT_DEF_RC=$? if rc_ok "$GT_FB_RC" "$GT_FB" && echo "$GT_FB" | grep -q "All tests passed" \ && echo "$GT_FB" | grep -q "fallback-mode: 1" \ && rc_ok "$GT_DEF_RC" "$GT_DEF" && echo "$GT_DEF" | grep -q "All tests passed"; then @@ -3634,8 +3644,8 @@ rm -f "$GA_PROBE_FILE" echo "[133] Gfx Argument-Type Guards" if ! echo "$GA_PROBE_OUT" | grep -q "undefined variable"; then # #1361 EIGS_STRICT=0: the plain pass pins the ext_gfx wrong-type stand-ins (0/null); the strict pass below raises. - GA_PLAIN=$(gfx_headless_run 30 env EIGS_STRICT=0 SDL_AUDIODRIVER=dummy ./eigenscript ../tests/test_gfx_argtypes.eigs 2>&1); GA_PLAIN_RC=$? - GA_STRICT=$(gfx_headless_run 30 env EIGS_STRICT=1 SDL_AUDIODRIVER=dummy ./eigenscript ../tests/test_gfx_argtypes.eigs 2>&1); GA_STRICT_RC=$? + GA_PLAIN=$(suite_program_run test_gfx_argtypes.eigs gfx_headless_run 30 env EIGS_STRICT=0 SDL_AUDIODRIVER=dummy ./eigenscript ../tests/test_gfx_argtypes.eigs 2>&1); GA_PLAIN_RC=$? + GA_STRICT=$(suite_program_run test_gfx_argtypes.eigs gfx_headless_run 30 env EIGS_STRICT=1 SDL_AUDIODRIVER=dummy ./eigenscript ../tests/test_gfx_argtypes.eigs 2>&1); GA_STRICT_RC=$? # THIRD PASS: the tape. audio_capture_open is trace-recorded, so a guard # placed below TRACE_NONDET_TAKE returns before TRACE_NONDET_RECORD and @@ -3962,7 +3972,7 @@ rm -f "$UC_PROBE_FILE" echo "[132] UI Containment Render-Decode Oracle" if ! echo "$UC_PROBE_OUT" | grep -q "undefined variable"; then - UC_OUTPUT=$(gfx_headless_run 30 ./eigenscript ../tests/test_ui_containment_gfx.eigs 2>&1); UC_RC=$? + UC_OUTPUT=$(suite_program_run test_ui_containment_gfx.eigs gfx_headless_run 30 ./eigenscript ../tests/test_ui_containment_gfx.eigs 2>&1); UC_RC=$? UC_N=$(derive_count "$UC_OUTPUT" "[132] UI Containment Render-Decode Oracle") if rc_ok "$UC_RC" "$UC_OUTPUT" && echo "$UC_OUTPUT" | grep -q "All tests passed"; then TOTAL=$((TOTAL + UC_N)) @@ -5184,26 +5194,26 @@ obs_hoist_verdict() { OBS_HOIST_JIT_EXPECT=compiled; [ "$(uname -m)" = x86_64 ] || OBS_HOIST_JIT_EXPECT="n/a" # 45. Interpreter, module-level names (OBSERVE_NAME_POST): closed -> no calls. OBS_G44=$(obs_hoist_verdict interp "$OBS_GATE_TMP/hoist_mod.eigs") -check "gate closed: OBSERVE_NAME_POST never enters the observer (interpreter)" "$OBS_G44" "1500/calls=0/jit=off" +check "gate closed: OBSERVE_NAME_POST never enters the observer (interpreter)" "$OBS_G44" "1500.0/calls=0/jit=off" # 46. Interpreter, fn-local slots (OBSERVE_ASSIGN_LOCAL): closed -> no calls. OBS_G45=$(obs_hoist_verdict interp "$OBS_GATE_TMP/hoist_fn.eigs") -check "gate closed: OBSERVE_ASSIGN_LOCAL never enters the observer (interpreter)" "$OBS_G45" "1500/calls=0/jit=off" +check "gate closed: OBSERVE_ASSIGN_LOCAL never enters the observer (interpreter)" "$OBS_G45" "1500.0/calls=0/jit=off" # 47. JIT, module-level names: the emitter's inline gate test skips the helper. OBS_G46=$(obs_hoist_verdict jit "$OBS_GATE_TMP/hoist_mod.eigs") -check "gate closed: the JIT skips jit_helper_observe_name_post (thunk witnessed)" "$OBS_G46" "1500/calls=0/jit=$OBS_HOIST_JIT_EXPECT" +check "gate closed: the JIT skips jit_helper_observe_name_post (thunk witnessed)" "$OBS_G46" "1500.0/calls=0/jit=$OBS_HOIST_JIT_EXPECT" # 48. JIT, fn-local slots. OBS_G47=$(obs_hoist_verdict jit "$OBS_GATE_TMP/hoist_fn.eigs") -check "gate closed: the JIT skips jit_helper_observe_assign_local (thunk witnessed)" "$OBS_G47" "1500/calls=0/jit=$OBS_HOIST_JIT_EXPECT" +check "gate closed: the JIT skips jit_helper_observe_assign_local (thunk witnessed)" "$OBS_G47" "1500.0/calls=0/jit=$OBS_HOIST_JIT_EXPECT" # 49. Control — a reader opens the gate at compile time and the same JIT'd loop # must then RECORD (populated tally, `diverging` verdict on the ramp). A # do-nothing counter or a gate test that skips the call unconditionally # scores 0 here and goes red. OBS_G48=$(obs_hoist_verdict jit "$OBS_GATE_TMP/hoist_reader.eigs") -check "control: with a reader the JIT'd loop still records every assignment" "$OBS_G48" "1500/calls=populated/jit=$OBS_HOIST_JIT_EXPECT" +check "control: with a reader the JIT'd loop still records every assignment" "$OBS_G48" "1500.0/calls=populated/jit=$OBS_HOIST_JIT_EXPECT" # 50. Control — EIGS_OBS_FORCE=1 opens the gate from process start on the # read-free program; the interpreter's hoisted test must see it open. OBS_G49=$(obs_hoist_verdict interp "$OBS_GATE_TMP/hoist_mod.eigs" EIGS_OBS_FORCE=1) -check "control: EIGS_OBS_FORCE=1 still records the read-free program (interpreter)" "$OBS_G49" "1500/calls=populated/jit=off" +check "control: EIGS_OBS_FORCE=1 still records the read-free program (interpreter)" "$OBS_G49" "1500.0/calls=populated/jit=off" # The count pin itself (§37). Also the vacuity floor: a section that ran zero # checks is not a section that passed. TOTAL=$((TOTAL + 1)) @@ -6962,7 +6972,7 @@ else echo " FAIL: a guard went silent, raised from the wrong place, a pin broke," echo " a guard has no probe, a gfx half failed, or a probe did not run (exit $STRICT_DIFF_RC)" echo " --- output of the run that failed (not a re-run) ---" - printf '%s\n' "$STRICT_DIFF_OUT" | sed -n '1,80p' + printf '%s\n' "$STRICT_DIFF_OUT" fi fi echo "" diff --git a/tests/run_fmt_parent_differential.sh b/tests/run_fmt_parent_differential.sh index 51c80510b..989ed6a28 100755 --- a/tests/run_fmt_parent_differential.sh +++ b/tests/run_fmt_parent_differential.sh @@ -8,35 +8,73 @@ TEMP= cleanup() { [ -z "$TEMP" ] || rm -rf "$TEMP"; } trap cleanup EXIT if [ -z "$PARENT" ]; then - PARENT_REF=$(git -C "$ROOT" merge-base HEAD origin/main 2>/dev/null) || { - echo "formatter differential: the parent commit is not in this clone; fetch history (cannot merge-base HEAD origin/main)" >&2 - exit 1 - } - git -C "$ROOT" cat-file -e "$PARENT_REF^{commit}" 2>/dev/null || { - echo "formatter differential: the parent commit is not in this clone; fetch history ($PARENT_REF)" >&2 - exit 1 - } - TEMP=$(mktemp -d "$(dirname "$ROOT")/.eigs-fmt-parent-XXXXXX") - git -C "$ROOT" archive "$PARENT_REF" | tar -x -C "$TEMP" || { - echo "formatter differential: could not obtain incumbent $PARENT_REF" >&2 - exit 1 - } - # This binary is a formatter oracle, not a performance result. Avoid the - # release build's expensive optimisation so the <=60s source-gate row - # spends its budget comparing output rather than optimizing the VM/JIT. - # Overriding CFLAGS replaces the Makefile's own, so carry the -Werror trio - # from the incumbent's tools/werror_flags.txt (CI's cc-guard rejects a - # compile without it) and size -j to the machine, not to a CI runner. - werror=$(cat "$TEMP/tools/werror_flags.txt") && [ -n "$werror" ] || { - echo "formatter differential: incumbent $PARENT_REF has no tools/werror_flags.txt" >&2 + # A language branch may have syntax absent from its main merge-base. + # Prefer an explicit reviewed incumbent, then this numeric branch's pin; + # ordinary branches retain merge-base. Never compare unrelated trees. + if [ -n "${FMT_PARENT_REF:-}" ]; then + PARENT_REF=$FMT_PARENT_REF + PARENT_SOURCE=FMT_PARENT_REF + elif [ -e "$ROOT/tests/phase2/fmt_parent_ref.txt" ]; then + PARENT_REF=$(python3 -c 'import re,sys; from pathlib import Path; lines=[line.strip() for line in Path(sys.argv[1]).read_text().splitlines() if line.strip() and not line.lstrip().startswith("#")]; (len(lines)==1 and re.fullmatch(r"[0-9a-f]{40}",lines[0])) or sys.exit("formatter differential: Phase 2 incumbent pin must contain exactly one 40-hex commit"); print(lines[0])' "$ROOT/tests/phase2/fmt_parent_ref.txt") || exit 1 + PARENT_SOURCE=tests/phase2/fmt_parent_ref.txt + else + PARENT_REF=$(git -C "$ROOT" merge-base HEAD origin/main 2>/dev/null) || { + echo "formatter differential: the parent commit is not in this clone; fetch history (cannot merge-base HEAD origin/main)" >&2 + exit 1 + } + PARENT_SOURCE=merge-base + fi + PARENT_REF=$(git -C "$ROOT" rev-parse --verify --end-of-options "$PARENT_REF^{commit}" 2>/dev/null) || { + echo "formatter differential: incumbent ref is not a commit in this clone" >&2 exit 1 } - make -C "$TEMP" -j"$(nproc 2>/dev/null || echo 2)" \ - CFLAGS="-Wall -Wextra -Werror=implicit-function-declaration $werror -O0 -fPIE" >/dev/null || { - echo "formatter differential: could not build incumbent $PARENT_REF" >&2 + git -C "$ROOT" merge-base --is-ancestor "$PARENT_REF" HEAD || { + echo "formatter differential: incumbent $PARENT_REF is not an ancestor of HEAD" >&2 exit 1 } - PARENT="$TEMP/src/eigenscript" + echo "formatter differential: incumbent=$PARENT_REF source=$PARENT_SOURCE" + if [ -n "${FMT_PARENT_BINARY:-}" ]; then + # Borrow the already-built reviewed oracle in its owning checkout; + # source identity and cleanliness must agree with the resolved ref. + PARENT=$(python3 -c 'import os,sys; from pathlib import Path; p=Path(sys.argv[1]).resolve(strict=True); (p.is_file() and os.access(p,os.X_OK)) or sys.exit("formatter differential: cached incumbent is not an executable file"); magic=p.open("rb").read(4); (magic.hex() in ("7f454c46","feedface","feedfacf","cefaedfe","cffaedfe","cafebabe","bebafeca","cafebabf","bfbafeca") or magic[:2]==b"MZ") or sys.exit("formatter differential: cached incumbent must be a native binary"); print(p)' "$FMT_PARENT_BINARY") || exit 1 + PARENT_OWNER=$(git --no-optional-locks -C "$(dirname "$PARENT")" rev-parse --show-toplevel 2>/dev/null) || { + echo "formatter differential: cached incumbent has no owning Git checkout" >&2 + exit 1 + } + PARENT_HEAD=$(git --no-optional-locks -C "$PARENT_OWNER" rev-parse --verify 'HEAD^{commit}') || exit 1 + [ "$PARENT_HEAD" = "$PARENT_REF" ] || { + echo "formatter differential: cached incumbent HEAD $PARENT_HEAD differs from $PARENT_REF" >&2 + exit 1 + } + PARENT_TRACKED=$(git --no-optional-locks -C "$PARENT_OWNER" status --porcelain --untracked-files=no) || exit 1 + [ -z "$PARENT_TRACKED" ] || { + echo "formatter differential: cached incumbent has dirty tracked source: $PARENT_OWNER" >&2 + exit 1 + } + echo "formatter differential: cached incumbent binary=$PARENT root=$PARENT_OWNER" + else + TEMP=$(mktemp -d "$(dirname "$ROOT")/.eigs-fmt-parent-XXXXXX") + git -C "$ROOT" archive "$PARENT_REF" | tar -x -C "$TEMP" || { + echo "formatter differential: could not obtain incumbent $PARENT_REF" >&2 + exit 1 + } + # This binary is a formatter oracle, not a performance result. Avoid the + # release build's expensive optimisation so the <=60s source-gate row + # spends its budget comparing output rather than optimizing the VM/JIT. + # Overriding CFLAGS replaces the Makefile's own, so carry the -Werror trio + # from the incumbent's tools/werror_flags.txt (CI's cc-guard rejects a + # compile without it). The incumbent build is serial on the same machine. + werror=$(cat "$TEMP/tools/werror_flags.txt") && [ -n "$werror" ] || { + echo "formatter differential: incumbent $PARENT_REF has no tools/werror_flags.txt" >&2 + exit 1 + } + make -C "$TEMP" \ + CFLAGS="-Wall -Wextra -Werror=implicit-function-declaration $werror -O0 -fPIE" >/dev/null || { + echo "formatter differential: could not build incumbent $PARENT_REF" >&2 + exit 1 + } + PARENT="$TEMP/src/eigenscript" + fi fi python3 tests/fmt_parent_differential.py "$PARENT" "$ROOT/src/eigenscript" "$ROOT" diff --git a/tests/section_weights.txt b/tests/section_weights.txt index 3331e56a9..d7d1d45cb 100644 --- a/tests/section_weights.txt +++ b/tests/section_weights.txt @@ -1,270 +1,313 @@ # tests/section_weights.txt — per-section wall seconds, summed per label. # -# MEASURED ON THE CI RUNNER, not on the dev box: run 36102915685, -# head 05bda90, the `asan + ubsan / shard k/N (http+model build)` +# MEASURED ON THE CI RUNNER, not on the dev box: run 38652383231, +# head 6fe6f0a3, the `asan + ubsan / shard k/N (http+model build)` # job logs, concatenated and fed to `--print-weights`. -# 260 SECTION_TIME lines, 260 distinct sections. +# 293 SECTION_TIME lines, 293 distinct sections. # # Refresh from every shard log of the same CI run. Different binary # builds and runner hosts have different section costs. # # Refresh — see docs/CI.md for the gh api recipe: # tools/section_plan.sh --print-weights \ -# --run 36102915685 --head 05bda90 > tests/section_weights.txt -[0] 0.04 -[0a] 23.07 -[0b] 0.27 -[0c] 26.24 -[0d] 0.04 -[0e] 0.15 -[0f] 0.55 -[1/15] 0.07 -[10/15] 0.03 -[100] 0.26 -[101] 0.06 -[102] 0.06 -[103] 0.04 -[103a] 3.54 -[104] 1.49 -[104b] 2.46 -[105] 0.04 -[105b] 0.07 -[105c] 3.17 -[106] 0.09 -[107] 0.87 -[108] 3.47 -[109] 0.12 -[11/15] 0.03 +# --run 38652383231 --head 6fe6f0a3 > tests/section_weights.txt +# +# One row is not from that run: [0fk] (#1758 R1.1, the C slot test) did not +# exist at that head. Its 53.33 s is SECTION_TIME [0fk] from the PR #1776 +# `linux / gcc` lane (job log of 2026-10-14, the release flavour at -O2; the +# slot test now builds at -O0, and the ASan shards run its ASan flavour). +# The next refresh from an ASan run replaces it (#1784). +[0] 0.03 +[0a] 23.67 +[0b] 43.43 +[0c] 0.50 +[0d] 0.02 +[0f] 0.44 +[0f2] 1.03 +[0f4] 0.44 +[0f5] 0.39 +[0f6] 0.42 +[0f7b] 0.01 +[0f8] 0.02 +[0f9] 0.41 +[0fb] 26.68 +[0fc] 22.45 +[0fd] 1.13 +[0fe] 32.19 +[0ff] 0.93 +[0fg] 0.47 +[0fh] 0.03 +[0fi] 1.48 +[0fj] 0.08 +[0fk] 53.33 +[0g] 1.14 +[0h] 0.05 +[100] 0.09 +[101] 0.03 +[10/15] 0.02 +[101a] 1.67 +[102] 0.04 +[102a] 0.03 +[102aa] 0.87 +[103] 0.03 +[103a] 2.28 +[104] 1.11 +[105] 0.02 +[105b] 0.05 +[105c] 2.42 +[105d] 1.54 +[106] 2.35 +[107] 0.73 +[107a] 0.09 +[107b] 0.12 +[108] 2.11 +[108a] 0.03 +[108b] 0.13 +[109] 0.13 [110] 0.03 -[111] 0.11 -[112] 0.04 -[113] 0.09 -[114] 0.27 -[114b] 0.17 -[115] 0.08 -[115b] 0.05 -[116] 0.05 -[117] 0.04 -[118] 0.05 -[119] 0.62 -[12/15] 0.03 -[120] 0.75 -[120b] 0.02 -[121] 0.02 -[122] 0.03 -[123] 0.04 -[124] 13.67 -[124b] 0.00 -[125] 0.31 -[126] 26.26 -[127] 0.04 -[128] 0.04 -[129] 0.05 -[13/15] 0.03 +[111] 0.07 +[11/15] 0.02 +[112] 0.03 +[113] 0.06 +[114] 0.23 +[114b] 0.67 +[1/15] 0.06 +[115] 0.07 +[115b] 0.04 +[116] 0.03 +[117] 0.05 +[118] 0.03 +[119] 0.39 +[120] 0.00 +[120b] 0.07 +[121] 0.04 +[12/15] 0.02 +[122] 0.02 +[123] 0.03 +[124] 6.43 +[124b] 0.02 +[125] 0.28 +[126] 20.20 +[127] 0.03 +[128] 0.03 +[129] 0.04 [130] 0.03 -[131] 0.09 -[132] 0.00 -[133] 0.03 -[134] 0.00 -[135] 0.04 -[137] 367.78 -[14/15] 0.20 -[15/15] 0.07 -[16/16] 0.96 -[17/17] 0.07 +[131] 0.07 +[13/15] 0.06 +[132] 0.20 +[133] 0.73 +[134] 0.44 +[135] 0.02 +[137] 276.41 +[15/15] 0.04 +[16/16] 1.17 +[17/17] 0.06 [18/18] 1.04 -[19/19] 0.05 -[2/15] 0.09 +[19/19] 0.03 [20/21] 0.04 -[22/27] 0.04 -[23/27] 0.03 -[24/27] 0.03 -[25/27] 0.05 +[2/15] 0.06 +[22/27] 0.06 +[23/27] 0.04 +[24/27] 0.02 +[25/27] 0.02 [26/27] 0.03 [27/27] 0.03 -[27b/27] 0.03 -[29/31] 0.03 -[3/15] 0.00 +[27b/27] 0.02 +[29/31] 0.02 [30/31] 0.04 [31/31] 0.03 -[33/33] 0.03 -[35/36] 0.04 +[3/15] 0.00 +[33/33] 0.02 +[35/36] 0.03 [36/36] 0.19 -[38/38] 0.03 -[4/15] 0.04 -[40/40] 0.05 +[38/38] 0.02 +[40/40] 0.03 [41/47] 0.03 -[42/47] 0.90 -[42a/47] 2.30 +[4/15] 0.03 +[42/47] 0.61 [42a2] 0.18 -[42a] 10.30 +[42a/47] 2.16 +[42a] 8.80 +[42b] 0.19 [42b/47] 0.02 -[42b] 0.22 -[42c/47] 0.02 -[42c] 2.69 +[42c] 1.87 +[42c/47] 0.72 [42d/47] 0.05 -[42e/47] 0.03 -[42f2] 2.10 -[42f] 0.51 -[42g] 1.03 -[42h] 0.39 -[42i] 0.78 -[42j] 1.73 -[42k] 1.80 -[42l] 1.23 -[43/47] 0.03 -[43a2] 0.20 -[43a2b] 0.14 -[43a2c] 0.12 -[43a3] 0.07 -[43b/47] 0.15 -[43c/47] 0.08 -[43d/47] 0.06 -[44/47] 0.04 -[45/47] 10.73 -[45a/47] 28.32 +[42e/47] 0.04 +[42f] 0.42 +[42g] 1.08 +[42h] 0.55 +[42i] 0.77 +[42j] 1.56 +[42k] 1.31 +[42l] 0.86 +[42m] 0.02 +[42n] 0.00 +[42p] 0.26 +[43/47] 0.02 +[43a2] 0.16 +[43a2b] 0.09 +[43a2c] 0.10 +[43a3] 0.05 +[43b/47] 0.11 +[43c/47] 0.06 +[43d/47] 0.02 +[44/47] 0.02 +[45/47] 10.29 +[45a/47] 28.15 [45b/47] 9.11 -[45c/47] 0.01 -[46/47] 0.03 -[47/47] 0.30 -[47b/47] 0.11 -[47c/47] 0.09 -[47d/47] 0.27 -[47e/47] 0.29 -[47f/47] 0.07 -[48a] 0.98 -[48b] 0.17 -[48c] 0.10 -[48d] 0.08 -[49] 0.05 -[5/15] 0.05 -[50] 0.03 -[50b] 0.13 -[50c] 0.05 -[50d] 0.05 -[50e] 0.04 -[50f] 0.04 -[50g] 0.04 -[50h] 0.05 -[50i] 0.04 -[50j2] 0.04 -[50j3] 0.04 -[50j4] 0.08 -[50j5] 0.04 -[50j6] 0.05 -[50j] 0.04 -[50k] 0.05 -[50l] 0.05 -[51] 0.04 -[51a] 0.06 -[51b] 0.05 -[52] 0.03 +[45c/47] 0.05 +[46/47] 0.02 +[47/47] 0.25 +[47b/47] 0.09 +[47c/47] 9.33 +[47d/47] 0.21 +[47e/47] 0.21 +[47f/47] 0.06 +[47g/47] 0.05 +[47h/47] 0.00 +[47i] 0.42 +[48a] 0.68 +[48b] 0.10 +[48c] 0.08 +[48d] 0.05 +[49] 0.03 +[50] 0.18 +[50b] 0.08 +[50c] 0.03 +[50d] 0.03 +[50e] 0.03 +[50f] 0.03 +[50g] 0.03 +[50h] 0.03 +[50i] 0.02 +[50j] 0.03 +[50j2] 0.03 +[50j3] 0.02 +[50j4] 0.05 +[50j6] 0.03 +[50k] 0.03 +[50l] 0.03 +[51] 0.03 +[5/15] 0.03 +[51a] 0.05 +[52] 0.02 [53] 0.03 -[543] 0.05 -[543b] 0.06 -[54] 1.08 -[55] 0.06 -[56] 0.07 -[57] 0.04 -[58] 0.03 -[59] 0.03 -[59a] 0.15 -[6/15] 0.03 -[60] 0.06 -[61] 0.03 -[62] 0.00 -[63] 0.66 -[64] 0.03 -[65] 0.03 -[66] 0.03 -[67] 0.03 -[68] 0.14 -[69] 32.49 -[7/15] 0.04 -[70] 0.02 -[70b] 1.06 -[70d] 0.09 -[71] 0.02 -[72] 0.03 +[54] 0.84 +[543] 0.02 +[543b] 0.02 +[55] 0.03 +[55a] 0.03 +[56] 0.05 +[57] 0.03 +[58] 0.02 +[59] 0.02 +[59a] 0.12 +[61] 0.04 +[6/15] 0.02 +[62] 0.07 +[63] 0.48 +[64] 0.02 +[65] 0.02 +[66] 0.02 +[67] 0.02 +[68] 0.13 +[69] 24.11 +[70] 0.39 +[70b] 0.15 +[70d] 0.01 +[70e1] 1.25 +[70e2] 0.07 +[70j1] 0.28 +[71] 0.04 +[7/15] 0.02 +[72] 0.02 [73] 0.03 -[74] 0.03 -[75] 0.06 -[76] 0.03 -[77] 0.14 -[78] 0.06 -[8/15] 0.03 -[80] 46.97 -[81] 8.06 -[81b] 0.67 -[81c] 0.54 -[81u] 122.67 -[82] 0.26 -[83] 0.05 -[84] 0.04 -[85] 0.46 -[86] 0.04 -[87] 0.04 -[88] 24.39 -[89] 7.02 -[8b] 0.04 -[8c] 0.05 +[74] 0.02 +[76] 0.02 +[77] 0.13 +[78] 0.02 +[80] 40.18 +[80b] 0.01 +[8/15] 0.02 +[81] 6.64 +[81b] 0.48 +[81c] 0.43 +[81u] 0.11 +[82] 0.56 +[83] 0.02 +[84] 0.03 +[85] 0.34 +[86] 0.03 +[87] 0.03 +[88] 22.90 +[89] 5.75 +[8b] 0.02 +[8c] 0.03 [8d] 0.03 -[8e] 0.04 -[8f] 0.04 -[8g] 0.04 -[8h] 0.08 -[9/15] 0.07 -[90] 0.71 -[91] 0.03 -[92] 0.04 -[93] 0.04 -[93b] 0.10 -[94] 0.41 -[95] 0.81 -[96] 0.89 -[96b] 0.21 -[97] 43.03 -[98] 0.03 -[99] 0.04 +[8e] 0.03 +[8f] 0.03 +[8g] 0.03 +[8h] 0.06 +[90] 0.49 +[91] 0.02 +[9/15] 0.05 +[92] 0.03 +[93] 0.03 +[93b] 0.06 +[94] 0.26 +[95] 0.72 +[96] 0.66 +[96b] 0.18 +[97] 57.75 +[98] 0.02 +[99] 0.03 [99a] 0.03 -[99aa] 1.53 -[99ab] 0.23 -[99b] 0.16 +[99aa] 0.97 +[99ab] 0.16 +[99ac] 0.02 +[99acb] 2.01 +[99ad] 0.72 +[99b] 0.12 [99c] 2.01 -[99d] 0.53 -[99e] 1.00 -[99f] 0.10 -[99g] 0.10 -[99h] 0.09 -[99i2] 5.10 -[99i] 0.09 -[99j] 0.15 -[99k] 0.08 -[99l] 0.01 -[99m] 0.46 -[99n] 0.02 -[99o] 0.23 -[99p] 0.01 -[99s] 4.15 -[99t] 0.05 -[99u+] 0.77 -[99u] 4.15 +[99d] 0.47 +[99e] 0.87 +[99f] 0.07 +[99g] 0.00 +[99h] 0.06 +[99i] 0.07 +[99i2] 2.79 +[99j] 0.11 +[99k] 0.06 +[99m] 0.34 +[99n] 0.01 +[99n2] 0.02 +[99o] 0.19 +[99p] 0.00 +[99s] 48.37 +[99t] 0.03 +[99u+] 0.63 +[99u] 3.12 [99w] 0.10 -[99x] 0.26 -[99y] 0.43 -[99z] 13.62 -[99za] 0.24 +[99x] 0.19 +[99y] 0.33 +[99yc] 1.04 +[99z] 9.35 +[99za] 0.17 +[99zb0] 0.05 [99zb] 0.01 -[99zc] 26.41 -[99zd] 0.89 -[Call Semantics] 0.04 -[Calls] 0.04 -[Coercion] 0.03 -[JSON Depth / DoS guard] 0.06 -[Number Formatting] 0.02 -[Observer] 0.45 -[STEM Accuracy] 0.12 +[99zc] 17.72 +[99zd] 0.10 +[99zdc] 0.11 +[99ze] 0.06 +[Calls] 0.03 +[Call Semantics] 0.03 +[Coercion] 0.02 +[JSON Depth / DoS guard] 0.04 +[loop-halting] 0.31 +[Number Formatting] 0.01 +[Observer] 0.29 [Security Hardening] 0.05 -[Structural Equality] 0.03 -[loop-halting] 0.34 -[stdlib] 9.50 +[stdlib] 7.12 +[STEM Accuracy] 0.09 +[Structural Equality] 0.02 diff --git a/tests/sections/0f5-trace-context.sh b/tests/sections/0f5-trace-context.sh index f67da13f0..5616c6fd9 100644 --- a/tests/sections/0f5-trace-context.sh +++ b/tests/sections/0f5-trace-context.sh @@ -2,7 +2,7 @@ echo "[0f5] Owned replay contexts and explicit host session advance (#1286)" check_binary_fingerprint TOTAL=$((TOTAL + 1)) TRACE_CONTEXT_OUT=$(bash "$TESTS_DIR/test_trace_context.sh" 2>&1); TRACE_CONTEXT_RC=$? -if rc_ok "$TRACE_CONTEXT_RC" "$TRACE_CONTEXT_OUT" && grep -qx 'trace context: 44 passed, 0 failed (44 declared)' <<< "$TRACE_CONTEXT_OUT"; then +if rc_ok "$TRACE_CONTEXT_RC" "$TRACE_CONTEXT_OUT" && grep -qx 'trace context: 53 passed, 0 failed (53 declared)' <<< "$TRACE_CONTEXT_OUT"; then PASS=$((PASS + 1)); echo " PASS: replay source/session ownership" else FAIL=$((FAIL + 1)); echo " FAIL: stream context contract (rc=$TRACE_CONTEXT_RC)" diff --git a/tests/sections/0f8-coalesce-legacy.sh b/tests/sections/0f8-coalesce-legacy.sh index 0288aaec5..5694260b6 100644 --- a/tests/sections/0f8-coalesce-legacy.sh +++ b/tests/sections/0f8-coalesce-legacy.sh @@ -1,3 +1,3 @@ echo "[0f8] Coalesce explicit compatibility values (#1398)" -EIGS_STRICT=0 check_eigs_suite "coalesce compatibility retains the legacy result" test_coalesce_legacy.eigs "All tests passed" +suite_program_run test_coalesce_legacy.eigs check_eigs_suite "coalesce compatibility retains the legacy result" test_coalesce_legacy.eigs "All tests passed" echo "" diff --git a/tests/sections/0fb-bool-type.sh b/tests/sections/0fb-bool-type.sh index 65e2dd4c9..4ed231ac4 100644 --- a/tests/sections/0fb-bool-type.sh +++ b/tests/sections/0fb-bool-type.sh @@ -36,7 +36,7 @@ else fi unset BOOL_JIT_OUT BOOL_JIT_RC # The class gate (#1637 round 4): the number members are renamed, so a raw -# read is a VAL_NUM_RAW / SLOT_NUM_RAW token; every token sits in a reviewed +# read is a VAL_NUM_RAW / SLOT_F64_RAW token; every token sits in a reviewed # function with its exact count, and every builtin call passes the bool gate # (tools/num_read_check.sh). TOTAL=$((TOTAL + 1)) @@ -48,7 +48,7 @@ else printf '%s\n' "$BOOL_NR_OUT" fi unset BOOL_NR_OUT BOOL_NR_RC -# Tape (#1637, format v7): a recorded bool replays as a bool, from the tape — +# Tape (#1637, format v9): a recorded bool replays as a bool, from the tape — # the probed file is deleted between record and replay, so a live re-probe # would answer false. A prior-format header is refused (exit 3). check_binary_fingerprint @@ -66,16 +66,19 @@ if [ "$BOOL_TP_RC" = 0 ] && [ "$BOOL_TP_REC" = '[true, "bool", false]' ] && else FAIL=$((FAIL + 1)); echo " FAIL: a recorded bool replays as a bool (rc=$BOOL_TP_RC rec='$BOOL_TP_REC' rep='$BOOL_TP_REP')" fi -sed '1s/^V 8 /V 7 /' "$BOOL_TP_DIR/p.tape" > "$BOOL_TP_DIR/p7.tape" -BOOL_TP_OUT=$(EIGS_REPLAY="$BOOL_TP_DIR/p7.tape" $EIGS_TMO ./eigenscript "$BOOL_TP_DIR/p.eigs" &1); BOOL_TP_RC=$? -TOTAL=$((TOTAL + 1)) -if [ "$BOOL_TP_RC" = 3 ] && grep -q "tape format v7, this binary reads v8" <<< "$BOOL_TP_OUT"; then - PASS=$((PASS + 1)); echo " PASS: a v7 tape is refused with exit 3" -else - FAIL=$((FAIL + 1)); echo " FAIL: a v7 tape is refused with exit 3 (rc=$BOOL_TP_RC)" - printf '%s\n' "$BOOL_TP_OUT" | eigs_failure_output -fi -# Tamper: a v8 tape whose bool record was hand-edited to a number (and a +# v9 combines the bank and main's incompatible v8 contracts; all prior formats are refused. +for BOOL_TP_OLD in 5 6 7 8; do + sed "1s/^V 9 /V $BOOL_TP_OLD /" "$BOOL_TP_DIR/p.tape" > "$BOOL_TP_DIR/old.tape" + BOOL_TP_OUT=$(EIGS_REPLAY="$BOOL_TP_DIR/old.tape" $EIGS_TMO ./eigenscript "$BOOL_TP_DIR/p.eigs" &1); BOOL_TP_RC=$? + TOTAL=$((TOTAL + 1)) + if [ "$BOOL_TP_RC" = 3 ] && grep -q "tape format v$BOOL_TP_OLD, this binary reads v9" <<< "$BOOL_TP_OUT"; then + PASS=$((PASS + 1)); echo " PASS: a v$BOOL_TP_OLD tape is refused with exit 3" + else + FAIL=$((FAIL + 1)); echo " FAIL: a v$BOOL_TP_OLD tape is refused with exit 3 (rc=$BOOL_TP_RC)" + printf '%s\n' "$BOOL_TP_OUT" | eigs_failure_output + fi +done +# Tamper: a v9 tape whose bool record was hand-edited to a number (and a # number record edited to a bool) is refused with exit 3, naming the builtin, # the record and the tape version -- never replayed as the other kind. printf 'x is file_exists of "%s"\nr is random of null\nn is random_normal of [2, 1.0]\nprint of [x, type of x, type of r, type of n]\n' \ @@ -90,7 +93,7 @@ for BOOL_TP_CASE in "t1:file_exists returns a bool, the tape holds a num" "t2:ra BOOL_TP_T=${BOOL_TP_CASE%%:*}; BOOL_TP_WANT=${BOOL_TP_CASE#*:} BOOL_TP_OUT=$(EIGS_REPLAY="$BOOL_TP_DIR/$BOOL_TP_T.tape" $EIGS_TMO ./eigenscript "$BOOL_TP_DIR/t.eigs" &1); BOOL_TP_RC=$? if ! cmp -s "$BOOL_TP_DIR/t.tape" "$BOOL_TP_DIR/$BOOL_TP_T.tape" && [ "$BOOL_TP_RC" = 3 ] && - grep -q "tape format v8 record 'N [0-9]* .*': $BOOL_TP_WANT; refusing to replay" <<< "$BOOL_TP_OUT"; then + grep -q "tape format v9 record 'N [0-9]* .*': $BOOL_TP_WANT; refusing to replay" <<< "$BOOL_TP_OUT"; then PASS=$((PASS + 1)); echo " PASS: a tampered tape is refused: $BOOL_TP_WANT" else FAIL=$((FAIL + 1)); echo " FAIL: a tampered tape is refused: $BOOL_TP_WANT (rc=$BOOL_TP_RC)" diff --git a/tests/sections/0fc-db-params.sh b/tests/sections/0fc-db-params.sh index 605464367..03ec9a5dc 100644 --- a/tests/sections/0fc-db-params.sh +++ b/tests/sections/0fc-db-params.sh @@ -9,7 +9,7 @@ else DBP_BUILD=$(make --no-print-directory -C "$TESTS_DIR/.." build/server-db/test_db_params 2>&1); DBP_RC=$? DBP_OUT="" [ "$DBP_RC" = 0 ] && { DBP_OUT=$($EIGS_TMO "$TESTS_DIR/../build/server-db/test_db_params" 2>&1); DBP_RC=$?; } - if rc_ok "$DBP_RC" "$DBP_OUT" && grep -qx 'db params: 7 passed, 0 failed (7 declared)' <<< "$DBP_OUT"; then + if rc_ok "$DBP_RC" "$DBP_OUT" && grep -qx 'db params: 14 passed, 0 failed (14 declared)' <<< "$DBP_OUT"; then PASS=$((PASS + 1)); echo " PASS: a bool db parameter binds as SQL boolean text with OID 16" else FAIL=$((FAIL + 1)); echo " FAIL: db parameter binding (rc=$DBP_RC)" diff --git a/tests/sections/0ff-container-slots-1665.sh b/tests/sections/0ff-container-slots-1665.sh index be1273234..78e9968f5 100644 --- a/tests/sections/0ff-container-slots-1665.sh +++ b/tests/sections/0ff-container-slots-1665.sh @@ -1,9 +1,11 @@ echo "[0ff] Container slot API (#1665 B0, slot storage B10)" TOTAL=$((TOTAL + 1)) -variant=release -if [ -e ../build/asan/eigenscript ] && [ ./eigenscript -ef ../build/asan/eigenscript ]; then - variant=asan -fi +# #1783: the variant the suite binary IS (asan or asan-server in the CI ASan +# shards), through the one predicate [0fk] shares; anything else is release. +. "$TESTS_DIR/suite_variant.sh" +variant=$(suite_binary_variant) +suite_variant_is_asan "$variant" || variant=release +echo " test binary variant: $variant" if make -s -C .. SLOT_TEST_VARIANT="$variant" container-slot-test && \ ../build/$variant/test_container_slots; then # Planted fault: a builtin result that is a numeric view must abort in diff --git a/tests/sections/0fk-phase2-slot-layer.sh b/tests/sections/0fk-phase2-slot-layer.sh new file mode 100644 index 000000000..6653aaf55 --- /dev/null +++ b/tests/sections/0fk-phase2-slot-layer.sh @@ -0,0 +1,25 @@ +# #1758 R1.1: the C slot test (docs/design/phase2-numbers.md 2.5). Nothing +# produces an int before R1.3, so this is the only witness of what every slot +# classifier does with one; tests/phase2/slot_layer.sh also checks that it +# examined exactly the population tools/phase2_inventory.py derives. +echo "[0fk] Phase 2 slot layer: every slot classifier against an SMI (#1758)" +TOTAL=$((TOTAL + 1)) +# #1783: an ASan suite binary (asan, or asan-server in the CI shards) runs +# the slot test under ASan/UBSan; anything else runs the release flavour. +. "$TESTS_DIR/suite_variant.sh" +slot_variant=release +suite_variant_is_asan "$(suite_binary_variant)" && slot_variant=asan +slot_rc=0 +bash ../tests/phase2/slot_layer.sh "$slot_variant" > /tmp/eigs-slot-layer.$$ 2> /tmp/eigs-slot-layer-err.$$ || slot_rc=$? +slot_last=$(tail -1 /tmp/eigs-slot-layer.$$) +if [ "$slot_rc" -eq 0 ] && [ "$slot_last" = "slot-layer: PASS" ] && [ ! -s /tmp/eigs-slot-layer-err.$$ ]; then + PASS=$((PASS + 1)) + echo " slot test variant: $slot_variant" + grep '^slot-layer' /tmp/eigs-slot-layer.$$ | sed 's/^/ /' +else + FAIL=$((FAIL + 1)); echo " FAIL: slot layer ($slot_variant) rc=$slot_rc" + sed 's/^/ | /' /tmp/eigs-slot-layer.$$ + sed 's/^/ | stderr: /' /tmp/eigs-slot-layer-err.$$ +fi +rm -f /tmp/eigs-slot-layer.$$ /tmp/eigs-slot-layer-err.$$ +echo "" diff --git a/tests/sections/0fq-floor-division-1758.sh b/tests/sections/0fq-floor-division-1758.sh new file mode 100644 index 000000000..612836880 --- /dev/null +++ b/tests/sections/0fq-floor-division-1758.sh @@ -0,0 +1,13 @@ +# #1758 N4: integer/float floor division, floor modulo, compound targets, +# boxed-int success and raise ownership. Strictly leak-clean, as [87]. +echo "[0fq] Floor division and modulo (#1758 N4)" +check_binary_fingerprint +TOTAL=$((TOTAL + 1)) +FLOOR_OUT=$(suite_program_run test_floor_division.eigs $EIGS_TMO ./eigenscript "$TESTS_DIR/test_floor_division.eigs" &1); FLOOR_RC=$? +if [ "$FLOOR_RC" -eq 0 ] && [ "$(lsan_classify_name "$FLOOR_OUT")" = none ] && grep -q '^All tests passed' <<< "$FLOOR_OUT"; then + PASS=$((PASS + 1)); echo " PASS: floor arithmetic and boxed ownership (leak-clean)" +else + FAIL=$((FAIL + 1)); echo " FAIL: floor arithmetic rc=$FLOOR_RC" + printf '%s\n' "$FLOOR_OUT" | eigs_failure_output +fi +echo "" diff --git a/tests/sections/0fqb-r16b-builtin-ownership.sh b/tests/sections/0fqb-r16b-builtin-ownership.sh new file mode 100644 index 000000000..1c8bad8cf --- /dev/null +++ b/tests/sections/0fqb-r16b-builtin-ownership.sh @@ -0,0 +1,15 @@ +# #1758 R1.6b: raised reads must release heap operands and partial lists. +echo "[0fqb] R1.6b builtin ownership" +check_binary_fingerprint +TOTAL=$((TOTAL + 1)) +R16B_OUT=$(suite_program_run test_r16b_builtin_ownership.eigs $EIGS_TMO ./eigenscript "$TESTS_DIR/test_r16b_builtin_ownership.eigs" &1); R16B_RC=$? +if [ "$R16B_RC" -eq 0 ] && [ "$(lsan_classify_name "$R16B_OUT")" = none ] && grep -Fxq 'All tests passed.' <<< "$R16B_OUT"; then + PASS=$((PASS + 1)) + echo " PASS: R1.6b exact builtin reads and raises are leak-clean" +else + FAIL=$((FAIL + 1)) + echo " FAIL: R1.6b builtin ownership (rc=$R16B_RC)" + printf '%s\n' "$R16B_OUT" | eigs_failure_output +fi +unset R16B_OUT R16B_RC +echo "" diff --git a/tests/sections/0fr-boxed-int-ownership.sh b/tests/sections/0fr-boxed-int-ownership.sh new file mode 100644 index 000000000..75a390974 --- /dev/null +++ b/tests/sections/0fr-boxed-int-ownership.sh @@ -0,0 +1,31 @@ +# #1758 R1.3: an int outside the SMI range is a BOXED heap value with a +# refcount, so every path that used to carry only immediate numbers can now +# leak or over-release one. The R1.3 memory review planted two such faults +# (an overflow raise that skips a decref; an append that stores without an +# incref) and the whole ASan suite stayed green. Each program here pushes boxed +# ints through one family of those paths, and each check is rc 0, strictly +# leak-clean (no rc_ok tolerance, like [87] and [0fn]) and its exact PASS line +# with its check count. +# test_boxed_int_arith.eigs VM arithmetic and its raise paths +# test_boxed_int_containers.eigs container stores, copies, json, sort +echo "[0fr] Boxed-int ownership (#1758 R1.3)" +check_binary_fingerprint +for __bio in "test_boxed_int_arith.eigs:boxed int arith: PASS checks=41" \ + "test_boxed_int_containers.eigs:boxed int containers: PASS checks=10"; do + __bio_f=${__bio%%:*} + __bio_m=${__bio#*:} + TOTAL=$((TOTAL + 1)) + BIO_OUTPUT=$(suite_program_run "$__bio_f" $EIGS_TMO ./eigenscript "$TESTS_DIR/$__bio_f" &1); BIO_RC=$? + if [ "$BIO_RC" -eq 0 ] && + [ "$(lsan_classify_name "$BIO_OUTPUT")" = none ] && + grep -Fxq "$__bio_m" <<< "$BIO_OUTPUT"; then + PASS=$((PASS + 1)) + echo " PASS: $__bio_f: boxed ints keep exact reference counts (leak-clean)" + else + FAIL=$((FAIL + 1)) + echo " FAIL: $__bio_f (rc=$BIO_RC, sanitizer=$(lsan_classify_name "$BIO_OUTPUT") — must exit 0, leak-clean, with '$__bio_m')" + printf '%s\n' "$BIO_OUTPUT" | eigs_failure_output + fi +done +unset __bio __bio_f __bio_m +echo "" diff --git a/tests/sections/0fs-n9-contract.sh b/tests/sections/0fs-n9-contract.sh new file mode 100644 index 000000000..708b7979d --- /dev/null +++ b/tests/sections/0fs-n9-contract.sh @@ -0,0 +1,15 @@ +# #1758 N9: Rust double-rounding and exact zero-divisor diagnostics. +echo "[0fs] N9 width arithmetic contract" +check_binary_fingerprint +TOTAL=$((TOTAL + 1)) +N9_OUT=$(suite_program_run test_n9_contract.eigs $EIGS_TMO ./eigenscript "$TESTS_DIR/test_n9_contract.eigs" &1); N9_RC=$? +if [ "$N9_RC" -eq 0 ] && [ "$(lsan_classify_name "$N9_OUT")" = none ] && grep -Fxq 'All tests passed.' <<< "$N9_OUT"; then + PASS=$((PASS + 1)) + echo " PASS: N9 exact builtin reads and raises are leak-clean" +else + FAIL=$((FAIL + 1)) + echo " FAIL: N9 width arithmetic contract (rc=$N9_RC)" + printf '%s\n' "$N9_OUT" | eigs_failure_output +fi +unset N9_OUT N9_RC +echo "" diff --git a/tests/sections/0ft-stable-sort-1758.sh b/tests/sections/0ft-stable-sort-1758.sh new file mode 100644 index 000000000..bb9dc4654 --- /dev/null +++ b/tests/sections/0ft-stable-sort-1758.sh @@ -0,0 +1,3 @@ +echo "[0fu] Stable sort across platforms (#1758)" +check_eigs_suite "stable sort and sort_by ties" test_sort_stable.eigs "All tests passed." +echo "" diff --git a/tests/sections/104ze-phase2-gfx-q7.sh b/tests/sections/104ze-phase2-gfx-q7.sh new file mode 100644 index 000000000..1a83b8672 --- /dev/null +++ b/tests/sections/104ze-phase2-gfx-q7.sh @@ -0,0 +1,17 @@ +echo "[104ze] Phase 2 Q7 geometry pixels (#1758)" +# Real pixels need SDL2. Where it is absent (the macOS runner), the test prints SKIP and exits 0, +# like [132]'s containment oracle; the exact geometry is still proven without SDL by +# gate.N5.q7-readers (tests/phase2/q7_readers.c). +check_binary_fingerprint +Q7P_OUT=$(suite_program_run test_phase2_gfx_q7.eigs $EIGS_TMO ./eigenscript ../tests/test_phase2_gfx_q7.eigs &1); Q7P_RC=$? +if rc_ok "$Q7P_RC" "$Q7P_OUT" && echo "$Q7P_OUT" | grep -q "All tests passed"; then + TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1)) + echo " PASS: whole-shape clipping and framebuffer source mapping" +elif [ "$Q7P_RC" = 0 ] && echo "$Q7P_OUT" | grep -q "^SKIP:"; then + section_skip "$(echo "$Q7P_OUT" | grep "^SKIP:" | head -1)" +else + TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) + echo " FAIL: whole-shape clipping and framebuffer source mapping (rc=$Q7P_RC)" + printf '%s\n' "$Q7P_OUT" | eigs_failure_output +fi +echo "" diff --git a/tests/sections/11b-named-loop-verifier-1977.sh b/tests/sections/11b-named-loop-verifier-1977.sh index 44c93adac..c2a1893ea 100644 --- a/tests/sections/11b-named-loop-verifier-1977.sh +++ b/tests/sections/11b-named-loop-verifier-1977.sh @@ -1,5 +1,5 @@ echo "[11b] Private-loop-counter opcodes are compiler-only (#1977; interpreter/default/OSR)" -# A sandbox descriptor that abuses OP_LOOP_EXIT_NAMED (101) must be refused by +# A sandbox descriptor that abuses OP_LOOP_EXIT_NAMED (105) must be refused by # the verifier, not run: before the fix the standalone form double-freed the # host's value (SIGABRT) and the nested form corrupted the host loop's own # private counter ("loop counter depth 0 out of range"). Run across all three diff --git a/tests/sections/1908-reserved-dunder-binders.sh b/tests/sections/1908-reserved-dunder-binders.sh index cd90afecb..01d82a41b 100644 --- a/tests/sections/1908-reserved-dunder-binders.sh +++ b/tests/sections/1908-reserved-dunder-binders.sh @@ -38,10 +38,10 @@ else FAIL=$((FAIL + 1)); echo " FAIL: REPL entry point (rc=$DU_RC)"; printf '%s\n' "$DU_OUT" | eigs_failure_output fi -printf '%s\n' 'i is 0' 'loop while i < 2:' ' i is i + 1' 'print of __loop_iterations__' > "$DU_DIR/read.eigs" +printf '%s\n' 'i is 0' 'loop while i < 2:' ' i is i + 1' 'print of __loop_iterations__' 'print of (num_type of __loop_iterations__)' > "$DU_DIR/read.eigs" DU_OUT=$($EIGS_TMO ./eigenscript "$DU_DIR/read.eigs" &1); DU_RC=$? TOTAL=$((TOTAL + 1)) -if [ "$DU_RC" = 0 ] && [ "$DU_OUT" = 2 ]; then +if [ "$DU_RC" = 0 ] && [ "$DU_OUT" = "$(printf '2\nint')" ]; then PASS=$((PASS + 1)); echo " PASS: loop while synthesizes a legally readable runtime binding" else FAIL=$((FAIL + 1)); echo " FAIL: runtime dunder read (rc=$DU_RC out='$DU_OUT')" diff --git a/tests/sections/1908a-runtime-count-kinds.sh b/tests/sections/1908a-runtime-count-kinds.sh new file mode 100644 index 000000000..66e3cb8ba --- /dev/null +++ b/tests/sections/1908a-runtime-count-kinds.sh @@ -0,0 +1,3 @@ +echo "[1908a] Runtime-published counts and indices are ints (S9-K1, #1758)" +check_eigs_suite "runtime count and index kinds" test_runtime_counts.eigs "All tests passed" +EIGS_STRICT=0 check_eigs_suite "runtime count and index kinds, non-strict" test_runtime_counts.eigs "All tests passed" diff --git a/tests/sections/2120-seed-random.sh b/tests/sections/2120-seed-random.sh new file mode 100755 index 000000000..494629a6a --- /dev/null +++ b/tests/sections/2120-seed-random.sh @@ -0,0 +1 @@ +bash ../tests/test_seed_random_env.sh diff --git a/tests/sections/70e6-observer-snapshot-fix2.sh b/tests/sections/70e6-observer-snapshot-fix2.sh index 830494a4f..a13d6b721 100644 --- a/tests/sections/70e6-observer-snapshot-fix2.sh +++ b/tests/sections/70e6-observer-snapshot-fix2.sh @@ -19,8 +19,8 @@ fi if make -s -C .. NUM_GUARD_VALUE_VARIANT="$variant" num-guard-value-test && \ ../build/$variant/test_num_guard_value; then PASS=$((PASS + 1)) - echo " PASS: observer normalization matches num_guard values" + echo " PASS: pure scalar guard matches num_guard values without changing math flags" else FAIL=$((FAIL + 1)) - echo " FAIL: observer normalization diverges from num_guard values" + echo " FAIL: pure scalar guard diverges from num_guard values" fi diff --git a/tests/strict_shape_cases.json b/tests/strict_shape_cases.json index 94aef03d0..94334d43b 100644 --- a/tests/strict_shape_cases.json +++ b/tests/strict_shape_cases.json @@ -269,14 +269,14 @@ "0.001", "0" ], - "valid_assert": "(len of shape_result) == 44 and (sum of shape_result) == 0", + "valid_assert": "(len of shape_result) == 44 and (len of ([shape_sample for shape_sample in shape_result if shape_sample != 0])) == 0", "setup": "", - "observe": "print of shape_result", + "observe": "print of [len of shape_result, len of ([shape_sample for shape_sample in shape_result if shape_sample != 0])]", "cleanup": "", "unchanged": "# No observable mutable argument; entry-guard ordering is independently checked from source.", "optional_args": [], "requirements": [], - "resource_notes": "Zero amplitude makes output deterministic; checks shape acceptance, not random distribution.", + "resource_notes": "N6 exposes randomly signed zeros at zero amplitude. Check every sample equals numeric zero and retain exact length; observe length/nonzero count, not random zero sign bits. This checks shape acceptance, not random distribution.", "source": { "file": "src/ext_gfx.c", "line": 2056, diff --git a/tests/strict_shape_descriptor.c b/tests/strict_shape_descriptor.c index ec7bb83bd..47f412712 100644 --- a/tests/strict_shape_descriptor.c +++ b/tests/strict_shape_descriptor.c @@ -7,7 +7,7 @@ #include "vm.h" int main(void) { - const char *source = "return 42\n"; + const char *source = "return 42.0\n"; EigsState *state = eigs_open(); if (!state) return 1; TokenList tokens = tokenize(source); @@ -24,17 +24,17 @@ int main(void) { if (chunk->code_len <= 0 || chunk->code_len > 64 || chunk->fn_count || chunk->local_count || chunk->param_count || chunk->const_count != 1 || !chunk->constants[0] || - chunk->constants[0]->type != VAL_NUM || - VAL_NUM_RAW(chunk->constants[0]) != 42) goto done; + chunk->constants[0]->type != VAL_FLOAT || + VAL_F64_RAW(chunk->constants[0]) != 42) goto done; descriptor = make_list(3); Value *code = make_list(chunk->code_len); Value *constants = make_list(chunk->const_count); for (int i = 0; i < chunk->code_len; i++) - list_append_owned(code, make_num(chunk->code[i])); + list_append_owned(code, make_float(chunk->code[i])); /* list_append takes its own reference before chunk_free releases its copy. */ list_append(constants, chunk->constants[0]); - list_append_owned(descriptor, make_num(EIGS_BYTECODE_ABI)); + list_append_owned(descriptor, make_float(EIGS_BYTECODE_ABI)); list_append_owned(descriptor, code); list_append_owned(descriptor, constants); json = eigs_json_encode(descriptor); diff --git a/tests/suite_program_env.sh b/tests/suite_program_env.sh index 91245f72d..c08ec0a18 100644 --- a/tests/suite_program_env.sh +++ b/tests/suite_program_env.sh @@ -1,7 +1,22 @@ #!/usr/bin/env bash -# One lookup for per-program suite environment. Both run_all_tests.sh and -# tools/jit_diff.sh source this file; suite_program_env.txt is the policy. +# One loader for per-program run conditions. tests/run_all_tests.sh, +# tools/jit_diff.sh, tools/replay_diff.sh and tools/phase2_corpus_diff.sh all +# source this file; tests/suite_program_env.txt is the policy, and nothing +# else spells a program's conditions (#1395; #1758's R1.1 critics found a +# corpus differential that ignored it and compared two early deaths). +# +# A row is a program's basename and its conditions: environment assignments, +# each one of SUITE_PROGRAM_FORMS. The forms are a whitelist: any other word +# is refused by name, never passed through. The suite runs every corpus +# program from src/ with stdin /dev/null and no arguments, so a row has no +# argument, stdin or working-directory field; a program that needs one (a +# generator fed by its child script, a fixture of another host) does not run +# standalone, and the corpus differential's ledger declares it +# (tests/phase2/corpus_nonzero.txt). +SUITE_PROGRAM_FORMS=" EIGS_STRICT=0 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy " + +# suite_program_env NAME: print NAME's row (its conditions, possibly empty). suite_program_env() { local name="${1##*/}" file="${EIGS_TEST_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}/suite_program_env.txt" awk -v name="$name" ' @@ -14,16 +29,34 @@ suite_program_env() { ' "$file" } -suite_program_run() { - local name="$1" setting - shift +# suite_program_check NAME: print NAME's conditions after checking every word +# against SUITE_PROGRAM_FORMS; rc 2, with the reason on stderr, for a +# duplicate row or a word outside the forms. +suite_program_check() { + local name="$1" setting w setting=$(suite_program_env "$name") || { echo "suite-program-env: invalid duplicate entry for ${name##*/}" >&2 return 2 } - case "$setting" in - "") "$@" ;; - EIGS_STRICT=0) EIGS_STRICT=0 "$@" ;; - *) echo "suite-program-env: unsupported environment for ${name##*/}: $setting" >&2; return 2 ;; - esac + for w in $setting; do + case "$SUITE_PROGRAM_FORMS" in + *" $w "*) ;; + *) echo "suite-program-env: unsupported condition for ${name##*/}: $w" >&2; return 2 ;; + esac + done + printf '%s\n' "$setting" +} + +# suite_program_run NAME CMD...: run CMD (a command or a shell function) +# under NAME's conditions, as prefix assignments, so they reach CMD's +# children. Every word was checked against the whitelist before the eval. +suite_program_run() { + local name="$1" setting + shift + setting=$(suite_program_check "$name") || return 2 + if [ -z "$setting" ]; then + "$@" + return + fi + eval "$setting"' "$@"' } diff --git a/tests/suite_program_env.txt b/tests/suite_program_env.txt index a9dc0cbe5..bcd4c591c 100644 --- a/tests/suite_program_env.txt +++ b/tests/suite_program_env.txt @@ -1,6 +1,8 @@ -# Per-program environment shared by the suite and differential runners. -# Format: test basename, followed by environment assignments understood by -# suite_program_run. Keep policy here rather than in either caller. +# Per-program run conditions: the ONE source, read by tests/suite_program_env.sh +# for the suite, tools/jit_diff.sh, tools/replay_diff.sh and +# tools/phase2_corpus_diff.sh. Format: a test basename, then its conditions, +# each one of the loader's SUITE_PROGRAM_FORMS (anything else is refused). +# A program listed here must run to completion (rc 0) under its conditions. test_file_io.eigs EIGS_STRICT=0 test_numeric_guard.eigs EIGS_STRICT=0 test_stdlib_fixes.eigs EIGS_STRICT=0 @@ -11,3 +13,9 @@ test_file_rename.eigs EIGS_STRICT=0 test_json_hard.eigs EIGS_STRICT=0 test_tensor_buffer_ops.eigs EIGS_STRICT=0 test_buffer_nonfinite_read.eigs EIGS_STRICT=0 +test_coalesce_legacy.eigs EIGS_STRICT=0 +test_audio.eigs EIGS_STRICT=0 SDL_AUDIODRIVER=dummy +test_gfx_text.eigs EIGS_STRICT=0 SDL_VIDEODRIVER=dummy +test_gfx_argtypes.eigs EIGS_STRICT=0 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy +test_ui_containment_gfx.eigs SDL_VIDEODRIVER=dummy +test_phase2_gfx_q7.eigs SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy diff --git a/tests/suite_variant.sh b/tests/suite_variant.sh new file mode 100644 index 000000000..f5461427e --- /dev/null +++ b/tests/suite_variant.sh @@ -0,0 +1,32 @@ +# tests/suite_variant.sh -- which build the suite binary is (sourced; cwd is +# src/, as for every section). +# +# One predicate for the sections that build a C test against the suite +# binary's own variant ([0ff] test_container_slots, [0fk] the C slot test). +# Each used to look only for build/asan, so in the CI ASan shards, which +# build `asan-server`, both compiled their RELEASE variant: no ASan, no LSan, +# no UBSan (#1783, found by the #1758 R1.1 critic). +# +# suite_binary_variant: print the build/ whose eigenscript the suite +# binary ./eigenscript is (a hard link: the same file); print nothing when +# it is none of them. +# suite_variant_is_asan VARIANT: the Makefile builds VARIANT with +# AddressSanitizer -- asked of the build system (`make print-FLAGS_`), +# not inferred from the name. +suite_binary_variant() { + for sbv_cand in ../build/*/eigenscript; do + if [ -f "$sbv_cand" ] && [ ./eigenscript -ef "$sbv_cand" ]; then + basename "$(dirname "$sbv_cand")" + return 0 + fi + done + return 0 +} +suite_variant_is_asan() { + [ -n "$1" ] || return 1 + sva_flags=$(make -s --no-print-directory -C .. "print-FLAGS_$1" 2>/dev/null) + case "$sva_flags" in + *sanitize=address*) return 0 ;; + esac + return 1 +} diff --git a/tests/test_amalgamation.sh b/tests/test_amalgamation.sh index f417ca205..5476b4bd2 100755 --- a/tests/test_amalgamation.sh +++ b/tests/test_amalgamation.sh @@ -45,8 +45,8 @@ else fi # Embedders also use debug and lightly optimized builds (#1775). A successful -# compiler exit alone does not reject warnings at either optimization level. -for opt in 0 1; do +# compiler exit alone does not reject warnings at all three optimization levels. +for opt in 0 1 2; do if ( cd "$WORK" && LC_ALL=C "$CC" -Wall -Wextra $WERROR_FLAGS -O"$opt" \ -c eigenscript_all.c -o "amalgamation-O$opt.o" ) >"$WORK/O$opt.err" 2>&1; then if grep -q 'warning:' "$WORK/O$opt.err"; then diff --git a/tests/test_audio.eigs b/tests/test_audio.eigs index 87e7089e2..12e1e9322 100644 --- a/tests/test_audio.eigs +++ b/tests/test_audio.eigs @@ -104,7 +104,13 @@ if ok != 0: # #152: hard ceiling at 10000 loops rejects huge counts (and prevents # the (int) cast UB that hit at INT_MAX+). assert_eq of [audio_play_loop of [short_clip, 10001], 0, "loops>10000 rejected (#152 hard cap)"] - assert_eq of [audio_play_loop of [short_clip, 1e20], 0, "loops huge double rejected (#152 cast UB)"] + # #1758 N5: an unrepresentable loop count raises before the mixer. + huge_loop_kind is "none" + try: + audio_play_loop of [short_clip, 1e20] + catch err: + huge_loop_kind is err.kind + assert_eq of [huge_loop_kind, "value", "N5: unrepresentable loop count raises value"] # The mixer holds ONE copy per channel and rewinds — loop count no # longer multiplies memory, so 800 loops of a 5s clip now just plays # (the old queue mode had to reject it at the 256 MiB byte budget). @@ -258,12 +264,14 @@ else: assert_eq of [1, 1, "stream skipped (no device)"] assert_eq of [1, 1, "stream skipped (no device)"] -test_summary of null - # ---- Mixer channels (GAP-002/GAP-003): device-gated — SDL_AUDIODRIVER=dummy # in CI opens a silent device; with no device at all these skip. ---- d is audio_open of [44100, 1] if d != 0: + # Queue accounting measures unplayed samples, so hold the public mixer + # clock still while checking exact lengths. An unpaused SDL callback + # can consume 1024 samples (2048 bytes) between channel installations. + audio_pause of 1 clip is audio_sine of [440, 0.05, 0.5] ch1 is audio_play of clip assert_eq of [(ch1 > 0), true, "audio_play returns a channel id"] @@ -271,6 +279,9 @@ if d != 0: assert_eq of [(chinf > 0), true, "loops == -1 (infinite) accepted"] ch3 is audio_play_loop of [clip, 3] assert_eq of [(ch3 > 0) and (ch3 != ch1) and (ch3 != chinf), true, "channels are distinct"] + # Cross callback periods while paused; these exact counts must survive + # elapsed host time rather than relying on a fast assertion schedule. + usleep of 50000 q is audio_queue_size of null assert_eq of [q, (len of clip) * 2 * 4, "queue counts finite channels only (1 + 3 passes)"] assert_eq of [audio_volume of [chinf, 0.25], 1, "live volume on an active channel"] @@ -312,3 +323,5 @@ if d != 0: print of "mixer channel tests: ok" else: print of "mixer channel tests skipped (no audio device)" + +test_summary of null diff --git a/tests/test_bitwise.eigs b/tests/test_bitwise.eigs index bba438818..475a2e6c9 100644 --- a/tests/test_bitwise.eigs +++ b/tests/test_bitwise.eigs @@ -32,22 +32,20 @@ assert_eq of [bit_and of [unlocked, 1], 1, "bitmask bit 0 set"] assert_eq of [bit_and of [unlocked, 2], 0, "bitmask bit 1 clear"] assert_eq of [bit_and of [unlocked, 4], 4, "bitmask bit 2 set"] -# Shift amount masking — out-of-range shifts are defined, not UB -# int64 semantics (aligned with the infix operators): counts are real -# up to 63 — the old int32-era &31 masking (1<<32 == 1) is gone. -assert_eq of [bit_shl of [1, 32], 4294967296, "SHL 1 << 32 is 2^32 (int64, no 31-masking)"] -assert_eq of [bit_shl of [1, 33], 8589934592, "SHL 1 << 33 is 2^33"] -assert_eq of [bit_shr of [8, 35], 0, "SHR 8 >> 35 shifts out to 0"] - -# Out-of-range shift COUNTS are defined (masked & 63), not C UB — for BOTH the -# infix operators and the bit_* builtins. Count 64 == count 0 (64 & 63 == 0), -# count 65 == count 1, count 100 == count 36. Without the mask the infix path -# was undefined behavior (only untested counts kept UBSan quiet). -assert_eq of [1 << 64, 1, "infix << 64 masks to << 0 (defined, not UB)"] -assert_eq of [1 << 65, 2, "infix << 65 masks to << 1"] -assert_eq of [1 << 100, 68719476736, "infix << 100 masks to << 36 == 2^36"] -assert_eq of [bit_shl of [1, 64], 1, "bit_shl count 64 masks to 0 (matches infix)"] -assert_eq of [256 >> 64, 256, "infix >> 64 masks to >> 0"] +# N8: Lua 5.4 counts outside (-64,64) yield zero; negatives reverse. +assert_eq of [bit_shl of [1, 32], 4294967296, "N8 count 32"] +assert_eq of [bit_shl of [1, 33], 8589934592, "N8 count 33"] +assert_eq of [bit_shr of [8, 35], 0, "N8 shifts out to zero"] +assert_eq of [1 << 64, 0, "N8 << 64 is zero"] +assert_eq of [1 << 65, 0, "N8 << 65 is zero"] +assert_eq of [1 << 100, 0, "N8 << 100 is zero"] +assert_eq of [bit_shl of [1, 64], 0, "N8 builtin << 64 is zero"] +assert_eq of [256 >> 64, 0, "N8 >> 64 is zero"] +assert_eq of [-1 >> 1, 9223372036854775807, "N8 logical right shift"] +assert_eq of [8 >> -1, 16, "N8 negative right count shifts left"] +assert_eq of [-1 << -1, 9223372036854775807, "N8 negative left count shifts logically right"] +assert_eq of [1 >> -64, 0, "N8 negative wide count is zero"] +assert_eq of [1 << (-9223372036854775807 - 1), 0, "N8 minimum count cannot overflow"] # Type safety — non-numeric args raise (strict error model, not UB, not 0). # Builtins: @@ -181,22 +179,22 @@ assert_eq of [bw_raise of ["bit_shr", 1, big], "value", "bit_shr of [1, 1e30] ra assert_eq of [bw_raise of ["bit_not", 1.5, 0], "value", "bit_not of 1.5 raises value"] assert_eq of [bw_raise of ["bit_not", 9223372036854775808, 0], "value", "bit_not of 2^63 raises value"] # Exact integers at the edges stay valid, unchanged from #1739. -int64_min is -9223372036854775808 +int64_min is -9223372036854775808.0 assert_eq of [bw_raise of ["|", int64_min, 0], "no raise", "-2^63 | 0 is valid"] assert_eq of [int64_min | 0, int64_min, "-2^63 | 0 returns itself"] assert_eq of [bit_or of [int64_min, 0], int64_min, "bit_or of [-2^63, 0] returns itself"] assert_eq of [9223372036854774784 | 0, 9223372036854774784, "largest double below 2^63 is valid"] -assert_eq of [~int64_min, 9223372036854775807, "~(-2^63) is INT64_MAX"] +# N8 retires the float result bridge; all 64 result bits survive. +assert_eq of [~int64_min, 9223372036854775807, "N8 complement is exact int64"] assert_eq of [(0 - 0) | 0, 0, "zero is valid"] assert_eq of [(0 - 1) << 1, 0 - 2, "SHL of a negative is defined (unsigned shift)"] assert_eq of [bit_shl of [0 - 1, 1], (0 - 1) << 1, "bit_shl matches infix on a negative"] -assert_eq of [1 << (0 - 1), int64_min, "a negative exact count is masked & 63"] +assert_eq of [1 << (0 - 1), 0, "N8 negative exact count reverses direction"] -# --- JIT tier: the native path must raise exactly where the VM does --- -# Each callee is called > EIGS_JIT_ENTRY_THRESHOLD (50) times with integers -# first, so its thunk exists before a non-integer operand arrives; the OSR -# loop runs past EIGS_JIT_OSR_THRESHOLD (5000) back-edges before its bad -# iteration. tools/jit_diff.sh runs this file JIT-off, default and forced OSR. +# --- All execution settings must raise exactly where the VM does --- +# R1.3 intentionally scanner-refuses these operators until R5. The hot +# entry/OSR scaffolds below verify interpreted fallback in each setting; +# Phase 2's ctl.scan.* rows independently witness the scanner refusals. define hot_or(a, b) as: return a | b define hot_shl(a, b) as: @@ -292,4 +290,23 @@ osr_ok is osr_loop of [9000, 0 - 1] assert_eq of [osr_ok[0], 9000, "OSR loop with exact integers completes"] assert_eq of [osr_ok[1], osr_oracle of 9000, "OSR loop with exact integers equals the builtin oracle"] +# N8: boxed operands, boxed results, and both operand positions on raises. +define n8_keep(x) as: + define n8_inner(dummy) as: + return x + return n8_inner +for n8_big in [9007199254740993, 9223372036854775807, (-9223372036854775807 - 1)]: + for n8_op in ["&", "|", "^", "<<", ">>", "~", "bit_and", "bit_or", "bit_xor", "bit_shl", "bit_shr", "bit_not"]: + if n8_op != "~" and n8_op != "bit_not": + assert_eq of [bw_raise of [n8_op, n8_big, 0.5], "value", "N8 boxed lhs released on count/value raise"] + assert_eq of [bw_raise of [n8_op, 0.5, n8_big], "value", "N8 boxed rhs released on lhs raise"] + n8_box is n8_big | 0 + n8_holder is {"values": [n8_box, ~n8_big, bit_or of [n8_big, 0]]} + n8_closure is n8_keep of n8_holder.values[0] + assert_eq of [n8_closure of null, n8_big, "N8 boxed bitwise result survives containers and closure"] + for n8_value in n8_holder.values: + assert_eq of [num_type of n8_value, "int", "N8 boxed result kind"] +for n8_value in [6 & 3, 6 | 3, 6 ^ 3, ~0, 1 << 63, -1 >> 1, bit_and of [6, 3], bit_or of [6, 3], bit_xor of [6, 3], bit_not of 0, bit_shl of [1, 63], bit_shr of [-1, 1], 3.0 & 1.0]: + assert_eq of [num_type of n8_value, "int", "N8 every operator and builtin returns int"] + test_summary of null diff --git a/tests/test_boxed_int_arith.eigs b/tests/test_boxed_int_arith.eigs new file mode 100644 index 000000000..13fc89d0e --- /dev/null +++ b/tests/test_boxed_int_arith.eigs @@ -0,0 +1,247 @@ +# Boxed ints (|i| >= 2^47, heap values with a refcount since R1.3) through VM +# arithmetic: the SMI boundary, the raise paths (overflow, division by zero, +# type errors) inside try/catch, mixed int/float operands, bitwise operators +# and comparisons. Trimmed from the R1.3 memory review's probe p01_arith.eigs +# (#1758). Section [0fr] runs it strictly leak-clean under ASan, so a raise path +# that forgets to release a boxed operand leaks and fails the suite. Every +# check asserts (a failed one exits nonzero); the last line pins the count. +CK is 0 +define ck(cond, msg) as: + assert of [cond, msg] + CK is CK + 1 + +P47 is 140737488355328 +SMAX is 140737488355327 +IMAX is 9223372036854775807 +IMIN is -9223372036854775807 - 1 +F47 is 140737488355328.0 +N is 200 + +ck of [(num_type of P47) == "int", "P47 int"] +ck of [(num_type of IMIN) == "int", "IMIN int"] +ck of [(num_type of F47) == "float", "F47 float"] + +# the SMI/boxed boundary, both directions +a1 is SMAX + 1 +ck of [a1 == P47, "smi+1 boxes"] +a2 is P47 - 1 +ck of [a2 == SMAX, "boxed-1 unboxes"] +a3 is P47 * 2 +ck of [a3 == 281474976710656, "boxed*2"] +a4 is 0 - P47 +ck of [a4 == -140737488355328, "0-P47 is SMI min"] +a5 is a4 - 1 +ck of [a5 == -140737488355329, "below SMI min boxes"] +a6 is IMAX - IMAX +ck of [a6 == 0, "boxed-boxed to 0"] +ck of [(IMIN + IMAX) == -1, "IMIN+IMAX"] +ck of [(str of IMIN) == "-9223372036854775808", "IMIN text"] +ck of [(str of IMAX) == "9223372036854775807", "IMAX text"] + +# overflow raises with boxed operands, N times per shape +define ov_plain(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local t is (IMAX - k) + (P47 + k) + print of "NOT REACHED plain" + catch err: + if err.kind == "value": + caught is caught + 1 + k is k + 1 + return caught + +define ov_listlit(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local xs is [P47 + k, IMAX - k, "s", IMAX + k + 1] + print of "NOT REACHED listlit" + catch err: + caught is caught + 1 + k is k + 1 + return caught + +define id3(a, b, c) as: + return [a, b, c] + +define ov_callarg(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local r is id3 of [P47 + k, (IMAX - k) * 2, k] + print of "NOT REACHED callarg" + catch err: + caught is caught + 1 + k is k + 1 + return caught + +define inner_ov(x) as: + local y is x + P47 + local z is [y, x, y] + local w is {"y": y, "x": x} + return y * IMAX + +define mid_ov(x) as: + local m is x - 1 + return inner_ov of (m + IMAX - P47 - P47) + +define ov_frames(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local r is mid_ov of (P47 + k) + print of "NOT REACHED frames" + catch err: + caught is caught + 1 + k is k + 1 + return caught + +define ov_sub_mul(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local t is (IMIN + k) - (P47 + 1) + catch err: + caught is caught + 1 + try: + local u is (P47 + k) * (P47 - k) + catch err: + caught is caught + 1 + try: + local v is SMAX * SMAX + catch err: + caught is caught + 1 + k is k + 1 + return caught + +define ov_compound(n) as: + local caught is 0 + local k is 0 + local acc is P47 + loop while k < n: + try: + acc += IMAX + catch err: + caught is caught + 1 + acc is P47 + k + k is k + 1 + return caught + +define ov_neg_abs(n) as: + local caught is 0 + local k is 0 + local mn is IMIN + loop while k < n: + try: + local q is -mn + catch err: + caught is caught + 1 + try: + local q2 is abs of mn + catch err: + caught is caught + 1 + local ok1 is -(P47 + k) + local ok2 is abs of (0 - P47 - k) + if ok1 != 0 - P47 - k: + print of "BAD neg" + if ok2 != P47 + k: + print of "BAD abs" + k is k + 1 + return caught + +define ov_divmod(n) as: + local caught is 0 + local k is 0 + loop while k < n: + try: + local q is (P47 + k) / 0 + catch err: + caught is caught + 1 + try: + local q2 is (IMAX - k) % 0 + catch err: + caught is caught + 1 + try: + local q3 is 1.5 | (P47 + k) + catch err: + caught is caught + 1 + try: + local q4 is (P47 + k) + "s" + catch err: + caught is caught + 1 + try: + local q5 is (P47 + k) < "s" + catch err: + caught is caught + 1 + k is k + 1 + return caught + +c1 is ov_plain of N +ck of [c1 == N, f"ov_plain caught {c1}"] +c2 is ov_listlit of N +ck of [c2 == N, f"ov_listlit caught {c2}"] +c3 is ov_callarg of N +ck of [c3 == N, f"ov_callarg caught {c3}"] +c4 is ov_frames of N +ck of [c4 == N, f"ov_frames caught {c4}"] +c5 is ov_sub_mul of N +ck of [c5 == 3 * N, f"ov_sub_mul caught {c5}"] +c6 is ov_compound of N +ck of [c6 == N, f"ov_compound caught {c6}"] +c7 is ov_neg_abs of N +ck of [c7 == 2 * N, f"ov_neg_abs caught {c7}"] +c8 is ov_divmod of N +ck of [c8 == 5 * N, f"ov_divmod caught {c8}"] + +# module scope (the global env, not a frame) +mk is 0 +mcaught is 0 +loop while mk < N: + try: + mt is (P47 + mk) * IMAX + catch err: + mcaught is mcaught + 1 + mk is mk + 1 +ck of [mcaught == N, "module-scope overflow caught"] + +# mixed int/float, the transitional / and %, bitwise operators, comparisons +ck of [(IMAX + 0.5) > 9.2e18, "IMAX+0.5 float"] +ck of [(num_type of (P47 + 0.5)) == "float", "mixed is float"] +ck of [(P47 * 1.5) == 211106232532992.0, "P47*1.5"] +ck of [(F47 + P47) == 281474976710656.0, "F47+P47"] +ck of [(num_type of (IMAX / 2)) == "float", "div float"] +ck of [((P47 + 7) % 10) == 5, "mod"] +ck of [(P47 & 0xFF) == 0, "band"] +ck of [(IMAX & 1) == 1, "band IMAX"] +ck of [((P47 + 3) | 0) == 140737488355331, "bor"] +ck of [(P47 << 1) == 281474976710656, "shl"] +ck of [(P47 >> 47) == 1, "shr"] +ck of [(~P47) == -140737488355329, "bnot"] +ck of [P47 < IMAX, "lt"] +ck of [IMIN < SMAX, "lt2"] +ck of [P47 == F47, "eq cross kind"] +ck of [(P47 + 1) > F47, "gt cross kind"] +ck of [9007199254740993 > 9007199254740992.0, "exact N2 past 2^53"] +ck of [9007199254740993 != 9007199254740992.0, "exact ne past 2^53"] +ck of [not (IMAX == 9223372036854775808), "IMAX vs 2^63 float"] + +define cmp_churn(n) as: + local k is 0 + local hits is 0 + loop while k < n: + local x is P47 + k + local y is IMAX - k + local f is F47 + k + if x < y and x >= f and x <= f and y > x and x == f and not (x != f): + hits is hits + 1 + k is k + 1 + return hits +ck of [(cmp_churn of N) == N, "cmp churn"] + +print of f"boxed int arith: PASS checks={CK}" diff --git a/tests/test_boxed_int_containers.eigs b/tests/test_boxed_int_containers.eigs new file mode 100644 index 000000000..72a0d2a1d --- /dev/null +++ b/tests/test_boxed_int_containers.eigs @@ -0,0 +1,150 @@ +# Boxed ints (|i| >= 2^47, heap values with a refcount since R1.3) through +# containers: list literals, append, index and slot stores, slices, copies, +# destructuring, nested lists and dicts, dict stores and removal, membership, +# reductions, sort and sort_by (exact order), json build/encode/decode/path, +# string formatting, and the error paths (int dict keys, out-of-range index +# and slice) looped under try/catch. Trimmed from the R1.3 memory review's +# probe p02_containers.eigs (#1758). Section [0fr] runs it strictly leak-clean +# under ASan, so a store that forgets to take a boxed int's reference (or one +# that takes it twice) fails the suite. Every check asserts (a failed one +# exits nonzero); the last line pins the count. +CK is 0 +define ck(cond, msg) as: + assert of [cond, msg] + CK is CK + 1 + +P47 is 140737488355328 +SMAX is 140737488355327 +IMAX is 9223372036854775807 +IMIN is -9223372036854775807 - 1 +B53 is 9007199254740993 +F47 is 140737488355328.0 +N is 100 + +define work(base, n) as: + local k is 0 + local total is 0 + loop while k < n: + local b is base + k + # list build (literal) with boxed, SMI, float, string + local xs is [b, SMAX, IMAX, IMIN, 1, 2.5, "s"] + # append boxed and SMI + append of [xs, b + 1] + append of [xs, k] + # set: overwrite boxed with boxed, with SMI, with float, with string, and back + xs[0] is b + 2 + xs[0] is 7 + xs[0] is 7.5 + xs[0] is "str" + xs[0] is b + 3 + set_at of [xs, 1, b] + set_at of [xs, 1, IMIN] + xs[2] += 1 - 1 + xs[5] is b + # index read + local r0 is xs[0] + local r1 is xs[-2] + local r2 is get_at of [xs, 3] + # slice and copies + local sl is xs[0:4] + local sl2 is xs[2:] + local cp is concat of [xs, [b]] + local cmp2 is [v for v in xs if v != "s"] + local dst is [0, 0, 0] + copy_into of [dst, 0, [b, IMAX, IMIN]] + local nc is num_copy of b + # destructure + [da, db] is [b, IMAX] + # nested containers + local nest is [[b, [IMAX, b]], {"k": [IMIN, {"z": b}]}] + nest[0][1][0] is b + 5 + nest[1]["k"][1]["z"] is IMIN + # dict values + local d is {"a": b, "b": SMAX} + d.c is IMAX + d["e"] is IMIN + dict_set of [d, "f", b] + d.a is 3 + d.a is b + 9 + dict_remove of [d, "b"] + local ks is keys of d + local vs is values of d + # membership and reductions + local has is list_contains of [xs, IMAX] + local at is list_index_of of [xs, IMIN] + local mx is max of [b, IMAX - 1] + local sm is sum of [b, b] + # json + local js is json_encode of [nest, d, xs] + local back is json_decode of js + local jb is json_build of ["a", b, "b", IMIN] + local jp is json_path of [js, "0"] + # formatting + local s1 is f"{b}|{IMIN}|{IMAX}|{nest}" + local s2 is str of xs + total is total + len of s1 + len of s2 - len of s1 - len of s2 + if not (has and at == 1 and r2 == IMIN and nc == b and da == b and len of cp == 10): + print of ["BAD", has, at, r2, nc, da, len of cp] + assert of [false, "work invariant"] + k is k + 1 + return total + +ck of [(work of [P47, N]) == 0, "work"] + +# exact checks once +xs is [P47, SMAX, IMAX, IMIN, 1] +append of [xs, P47 + 1] +ck of [xs[5] == 140737488355329, "append boxed"] +xs[1] is IMIN +ck of [xs[1] == IMIN, "set boxed over smi"] +ck of [(str of xs[0:3]) == "[140737488355328, -9223372036854775808, 9223372036854775807]", "slice text"] +srt is sort of [B53, 9007199254740992.0, 9007199254740992, IMAX, IMIN, F47, P47, -1] +ck of [(str of srt) == "[-9223372036854775808, -1, 140737488355328.0, 140737488355328, 9007199254740992.0, 9007199254740992, 9007199254740993, 9223372036854775807]", "sort exact order"] +ck of [(num_type of srt[2]) != (num_type of srt[3]), "sort keeps both kinds at the tie"] +sb is sort_by of [[P47, 3, IMIN, IMAX], (v) => 0.0 - v] +ck of [sb[0] == IMAX and sb[3] == IMIN, "sort_by order"] +ck of [(json_encode of [3, IMAX, {"min": IMIN}]) == "[3,9223372036854775807,{\"min\":-9223372036854775808}]", "json exact"] +ck of [(f"{P47}") == "140737488355328", "fstring digits"] + +# error paths, looped: int dict keys (refused), boxed index, boxed slice +# bounds, a raise inside a sort_by key +define errs(n) as: + local caught is 0 + local k is 0 + local ys is [P47, IMAX, IMIN, "s"] + local dd is {"a": P47} + loop while k < n: + try: + dd[P47 + k] is IMAX + catch e: + caught is caught + 1 + try: + local q is {P47: IMIN} + catch e: + caught is caught + 1 + try: + local q is dd[IMAX - k] + catch e: + caught is caught + 1 + try: + local q is ys[P47 + k] + catch e: + caught is caught + 1 + try: + ys[IMIN + k] is P47 + catch e: + caught is caught + 1 + try: + local q is ys[1:IMAX - k] + catch e: + caught is caught + 1 + try: + local q is sort_by of [[P47, IMIN, k], (v) => 0 - v] + catch e: + caught is caught + 1 + k is k + 1 + return caught +ec is errs of N +ck of [ec == 7 * N, f"errs caught {ec}"] + +print of f"boxed int containers: PASS checks={CK}" diff --git a/tests/test_buffer_nonfinite_read.eigs b/tests/test_buffer_nonfinite_read.eigs index 691f4b62f..6d764d777 100644 --- a/tests/test_buffer_nonfinite_read.eigs +++ b/tests/test_buffer_nonfinite_read.eigs @@ -11,14 +11,45 @@ infs is matmul of [matrix, matrix] # These operands exercise the shared runtime scalar-read accessor contract. # They do not establish direct indexed-operator or strict diagnostic/flag # parity with ouroboros's separate unboxed-number optimizer. -print of ((buf_get of [infs, 0]) > 1e308) -print of ((buf_get of [infs, 0]) / (buf_get of [infs, 1])) -print of ((buf_get of [infs, 0]) % 3) -print of ((buf_get of [infs, 0]) * 0.5 > 1e308) -print of ((buf_get of [infs, 0]) + 0 > 1e308) -print of (buf_get of [infs, 0]) -print of (get_at of [infs, 0]) -print of (gather of [infs, [0, 0]])[0] +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of ((buf_get of [infs, 0]) > 1e308) +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of ((buf_get of [infs, 0]) / (buf_get of [infs, 1])) +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of ((buf_get of [infs, 0]) % 3) +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of ((buf_get of [infs, 0]) * 0.5 > 1e308) +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of ((buf_get of [infs, 0]) + 0 > 1e308) +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of (buf_get of [infs, 0]) +catch e: + print of e.kind +# N13 / R1.6c: the final get_at reader refuses non-finite buffer cells. +try: + print of (get_at of [infs, 0]) +catch e: + print of e.kind +try: + print of (gather of [infs, [0, 0]])[0] +catch e: + print of e.kind left is buffer of [1, 2] left[0] is 1e200 @@ -27,19 +58,40 @@ right is buffer of [2, 1] right[0] is 1e200 right[1] is 0 - 1e200 nans is matmul of [left, right] -print of nans[0] -print of (buf_get of [nans, 0]) -print of (get_at of [nans, 0]) -print of (gather of [nans, [0]])[0] +try: + print of nans[0] +catch e: + print of e.kind +# N13 / R1.6d: final data reader refuses the raw nonfinite payload. +try: + print of (buf_get of [nans, 0]) +catch e: + print of e.kind +try: + print of (get_at of [nans, 0]) +catch e: + print of e.kind +try: + print of (gather of [nans, [0]])[0] +catch e: + print of e.kind +# R1.6d-D1 point 3 / N13: lenient scalar refusals leave INVALID clear. print of (math_flags of null).invalid -# Buffer indices are scalar reads as well. A NaN index must collapse to zero -# before it is converted to an integer; casting the raw value is undefined C. +# Buffer indices require finite exact data before any integer conversion. source is buffer of 1 source[0] is 7 source_matrix is reshape of [source, 1, 1] -print of (gather of [source_matrix, nans])[0] +# N5 / R1.6d: index-buffer elements must be finite exact integers. +try: + print of (gather of [source_matrix, nans])[0] +catch e: + print of e.kind target is buffer of 1 -scatter_add of [target, nans, [5]] +try: + scatter_add of [target, nans, [5]] +catch e: + print of e.kind print of target[0] +# R1.6d-D1 point 3 / N13: lenient scalar refusals leave INVALID clear. print of (math_flags of null).invalid diff --git a/tests/test_buffer_nonfinite_read.out b/tests/test_buffer_nonfinite_read.out index 28577cf28..fdcca1c24 100644 --- a/tests/test_buffer_nonfinite_read.out +++ b/tests/test_buffer_nonfinite_read.out @@ -1,16 +1,17 @@ +value +value +value +value +value +value +value +value +value +value +value +value false -1 -2 +value +value +0.0 false -false -1e+308 -1e+308 -1e+308 -0 -0 -0 -0 -true -7 -5 -true diff --git a/tests/test_builtin_contracts.eigs b/tests/test_builtin_contracts.eigs index 3d79025f2..e1386530b 100644 --- a/tests/test_builtin_contracts.eigs +++ b/tests/test_builtin_contracts.eigs @@ -11,7 +11,14 @@ assert_eq of [max of [1, 2, 3], 3, "max reduces past the first two elements"] assert_eq of [min of [4, 2, 9, 1, 5], 1, "min N-ary reduction"] assert_eq of [max of [-3, -1, -2], -1, "max of all-negative list"] assert_eq of [min of (7), 7, "min of a bare number is itself (mirrors sum)"] -assert_eq of [max of [], 0, "max of empty list keeps the 0 fallback"] +# R1.6b-D2 / #1895: no numeric winner exists, including in soft mode. +for thunk in [(x) => min of [], (x) => min of ([]), (x) => max of [], (x) => max of ([])]: + empty_kind is "" + try: + thunk of null + catch e: + empty_kind is e.kind + assert_eq of [empty_kind, "value", "R1.6b-D2 empty min/max raises"] assert_eq of [max of ["a", 5], 0, "non-number element keeps the 0 fallback"] # ---- #316: string predicates reject non-string operands ---- diff --git a/tests/test_builtin_indirect.eigs b/tests/test_builtin_indirect.eigs index d39a7164f..46b3b2906 100644 --- a/tests/test_builtin_indirect.eigs +++ b/tests/test_builtin_indirect.eigs @@ -42,6 +42,15 @@ indirect1 is dsp of [table, 1, 5] check of ["dispatch indirect agrees k=0", indirect0 == direct0] check of ["dispatch indirect agrees k=1", indirect1 == direct1] +# N5 / R1.6c: the C fallback must balance a returned numeric child. A SMI +# hides double-retention by the outer borrow scan; this int is a HEAP value. +boxed_arg is 1152921504606846977 +check of ["dispatch input is boxed int", (num_type of boxed_arg) == "int"] +identity_table is [num] +boxed_result is dsp of [identity_table, 0, boxed_arg] +check of ["dispatch boxed result kind", (num_type of boxed_result) == "int"] +check of ["dispatch boxed result exact", boxed_result == boxed_arg] + # Out-of-range key: both paths return null. check of ["dispatch direct OOR null", (dispatch of [table, 9, 5]) == null] check of ["dispatch indirect OOR null", (dsp of [table, 9, 5]) == null] diff --git a/tests/test_builtin_overflow.sh b/tests/test_builtin_overflow.sh index 95cdc45a1..a478a4267 100755 --- a/tests/test_builtin_overflow.sh +++ b/tests/test_builtin_overflow.sh @@ -78,7 +78,7 @@ print of caught' "1" # A normal matmul still produces the right answer (guards the cap refactor). run_case "matmul normal correctness" \ 'print of (matmul of [[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]])' \ -'[[19, 22], [43, 50]]' +'[[19.0, 22.0], [43.0, 50.0]]' # substr: a huge length overflowed `start + rlen` (int) past the clamp, then the # allocation aborted (SIGABRT). Now clamped via subtraction to the string tail. diff --git a/tests/test_channel_nb.eigs b/tests/test_channel_nb.eigs index 3cb0c9d88..719092fe5 100644 --- a/tests/test_channel_nb.eigs +++ b/tests/test_channel_nb.eigs @@ -118,40 +118,43 @@ if err != null: else: print of "MISMATCH 12a: expected error for non-number ms" -# 13. Issue #151 — huge ms above LONG_MAX must not invoke UB in the -# (long) cast. With a value already buffered, the call returns at -# once instead of computing a garbage deadline (which used to fire -# immediately as a spurious timeout, or wait essentially forever). +# 13. N5/Q7 (#1758 R1.6c): an unrepresentable duration raises before +# consuming a queued value; no cast or silent one-year cap. ch_big is channel of null send of [ch_big, 11] t0 is monotonic_ms of null -v is recv_timeout of [ch_big, 1e30] +err is null +try: + v is recv_timeout of [ch_big, 1e30] +catch e: + err is e.kind elapsed is monotonic_ms of null - t0 -check of ["13a", v, 11] -check of ["13b", elapsed < 100, true] +check of ["13a N5/Q7 magnitude", err, "value"] +check of ["13b refusal preserves queue", try_recv of ch_big, 11] -# 14. Same with an even larger value that saturates the runtime's double -# range — still must not overflow the long cast or the deadline. +# 14. The larger finite value also raises before the receive effect. ch_huge is channel of null send of [ch_huge, 22] t0 is monotonic_ms of null -v is recv_timeout of [ch_huge, 1e308] +err is null +try: + v is recv_timeout of [ch_huge, 1e308] +catch e: + err is e.kind elapsed is monotonic_ms of null - t0 -check of ["14a", v, 22] -check of ["14b", elapsed < 100, true] +check of ["14a N5/Q7 magnitude", err, "value"] +check of ["14b refusal preserves queue", try_recv of ch_huge, 22] -# 15. Empty channel + huge ms must use the clamped ceiling. We bound -# the elapsed time to prove the deadline didn't degenerate to "wait -# forever" — the wait runs in its own thread that we kill via close. +# 15. Empty channel + huge duration refuses without waiting (Q7). ch_giant is channel of null -define giant_closer() as: - usleep of 30000 - close_channel of ch_giant -spawn of giant_closer t0 is monotonic_ms of null -v is recv_timeout of [ch_giant, 1e30] +err is null +try: + v is recv_timeout of [ch_giant, 1e30] +catch e: + err is e.kind elapsed is monotonic_ms of null - t0 -check of ["15a", v, null] +check of ["15a N5/Q7 magnitude", err, "value"] check of ["15b", elapsed < 500, true] if passed == checks: diff --git a/tests/test_container_slots.c b/tests/test_container_slots.c index b6404630f..5d727c8c8 100644 --- a/tests/test_container_slots.c +++ b/tests/test_container_slots.c @@ -11,8 +11,8 @@ static int failed; #define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL:%d: %s\n", __LINE__, #x); failed = 1; } } while (0) static int num_cmp(const void *a, const void *b) { - double da = eigs_slot_num(*(const EigsSlot *)a, __func__); - double db = eigs_slot_num(*(const EigsSlot *)b, __func__); + double da = eigs_slot_float(*(const EigsSlot *)a, __func__); + double db = eigs_slot_float(*(const EigsSlot *)b, __func__); return (da > db) - (da < db); } @@ -24,14 +24,14 @@ int main(int argc, char **argv) { * must abort in the real builtin-return compensation path. */ static EigsView escaped; Value *arg = make_list(1); - list_append_num(arg, 1.0); - vm_borrow_compensate(arg, slot_view(slot_from_num(1665.0), &escaped), 1, NULL, NULL); + list_append_f64(arg, 1.0); + vm_borrow_compensate(arg, slot_view(slot_from_f64(1665.0), &escaped), 1, NULL, NULL); return 99; } Value *list = make_list(8); Value *nested = make_list(1); Value *str = make_str("slot"); - list_append_num(list, 3.0); + list_append_f64(list, 3.0); list_append_slot(list, eigs_container_slot(str)); list_append_slot(list, eigs_container_slot(nested)); list_append_slot(list, eigs_container_slot(make_null())); @@ -40,88 +40,88 @@ int main(int argc, char **argv) { /* Storage encodings: numbers immediate, null/bool singleton bits, refs heap. */ EigsSlot *raw = list_values_storage(list); - CHECK(slot_is_num(raw[0]) && SLOT_NUM_RAW(raw[0]) == 3.0); + CHECK(slot_is_f64(raw[0]) && SLOT_F64_RAW(raw[0]) == 3.0); CHECK(slot_is_heap(raw[1]) && slot_is_heap(raw[2])); CHECK(raw[3].u == SLOT_NULL_BITS && raw[4].u == SLOT_TRUE_BITS); CHECK(str->refcount == 1); /* one container ref; a list may also be GC-buffered */ - CHECK(slot_type(list_slot(list, 0)) == VAL_NUM); + CHECK(slot_type(list_slot(list, 0)) == VAL_FLOAT); CHECK(list_elem_type(list, 1) == VAL_STR); CHECK(list_get_ref(list, 0) == NULL && list_get_ref(list, 1) != NULL); - CHECK(eigs_slot_num(list_slot(list, 0), "test") == 3.0); - CHECK(eigs_slot_elem_num(list_slot(list, 0), "test") == 3.0); + CHECK(eigs_slot_float(list_slot(list, 0), "test") == 3.0); + CHECK(eigs_slot_elem_float(list_slot(list, 0), "test") == 3.0); double opt = 7.0; - CHECK(!eigs_slot_opt_num(list_slot(list, 3), &opt, "test") && opt == 7.0); - CHECK(eigs_slot_opt_num(list_slot(list, 0), &opt, "test") && opt == 3.0); + CHECK(!eigs_slot_opt_float(list_slot(list, 3), &opt, "test") && opt == 7.0); + CHECK(eigs_slot_opt_float(list_slot(list, 0), &opt, "test") && opt == 3.0); { EIGS_VIEW(a); EIGS_VIEW(b); Value *av = list_get_view(list, 0, &a); - list_append_num(list, 9.0); + list_append_f64(list, 9.0); Value *bv = list_get_view(list, 5, &b); - CHECK(av != bv && eigs_num_arg(av, "test") == 3.0 && eigs_num_arg(bv, "test") == 9.0); + CHECK(av != bv && eigs_float_arg(av, "test") == 3.0 && eigs_float_arg(bv, "test") == 9.0); CHECK(list_get_view(list, 1, &a) == list_get_ref(list, 1)); } Value *owned = list_get_owned(list, 0); Value *returned = list_get_return(list, 0); - CHECK(owned != returned && owned->type == VAL_NUM && VAL_NUM_RAW(owned) == 3.0); + CHECK(owned != returned && owned->type == VAL_FLOAT && VAL_F64_RAW(owned) == 3.0); val_decref(owned); val_decref(returned); - list_set_num(list, 0, 4.0); + list_set_f64(list, 0, 4.0); list_set_slot_owned(list, 1, slot_from_heap(make_str("owned"))); - CHECK(eigs_slot_num(list_slot(list, 0), "test") == 4.0); + CHECK(eigs_slot_float(list_slot(list, 0), "test") == 4.0); /* A boxed number or a singleton stored through a heap slot is re-encoded. */ - list_set_slot_owned(list, 2, slot_from_heap(make_num(5.0))); - CHECK(slot_is_num(list_values_storage(list)[2])); + list_set_slot_owned(list, 2, slot_from_heap(make_float(5.0))); + CHECK(slot_is_f64(list_values_storage(list)[2])); list_set_owned(list, 3, make_bool(0)); CHECK(list_values_storage(list)[3].u == SLOT_FALSE_BITS); /* The NaN guard: the default x86 NaN must never be stored as null bits. */ { int strict = g_strict; g_strict = 0; - list_set_num(list, 4, -__builtin_nan("")); + list_set_f64(list, 4, -__builtin_nan("")); g_strict = strict; - CHECK(slot_is_num(list_values_storage(list)[4])); + CHECK(slot_is_f64(list_values_storage(list)[4])); } EigsSlot taken = list_take_slot(list, 0); - CHECK(eigs_slot_num(taken, "test") == 4.0); + CHECK(eigs_slot_float(taken, "test") == 4.0); slot_decref(taken); list_move(list, 0, 0, 1); Value *sort = make_list(3); - list_append_num(sort, 2); list_append_num(sort, 1); list_append_num(sort, 3); + list_append_f64(sort, 2); list_append_f64(sort, 1); list_append_f64(sort, 3); list_sort(sort, num_cmp); - CHECK(eigs_slot_num(list_slot(sort, 0), "test") == 1.0); + CHECK(eigs_slot_float(list_slot(sort, 0), "test") == 1.0); /* Zero bits are 0.0: a calloc'd slot array reads as numbers. */ Value *zeros = make_list(4); - CHECK(slot_is_num(list_values_storage(zeros)[0]) && zeros->data.list.count == 0); + CHECK(slot_is_f64(list_values_storage(zeros)[0]) && zeros->data.list.count == 0); Value *dict = make_dict(4); - dict_set_owned(dict, "n", make_num(2)); + dict_set_owned(dict, "n", make_float(2)); dict_set_owned(dict, "s", make_str("dict")); dict_set_owned(dict, "l", make_list(1)); dict_set_owned(dict, "z", make_null()); dict_set_owned(dict, "b", make_bool(0)); int ni = env_hash_find_dict(dict, "n", env_hash_name("n")); int si = env_hash_find_dict(dict, "s", env_hash_name("s")); - CHECK(dict_value_type(dict, ni) == VAL_NUM && dict_value_get_ref(dict, ni) == NULL); - CHECK(slot_is_num(dict_values_storage(dict)[ni])); - CHECK(eigs_slot_num(dict_value_slot(dict, ni), "test") == 2.0); - { EIGS_VIEW(v); CHECK(eigs_num_arg(dict_value_get_view(dict, ni, &v), "test") == 2.0); } + CHECK(dict_value_type(dict, ni) == VAL_FLOAT && dict_value_get_ref(dict, ni) == NULL); + CHECK(slot_is_f64(dict_values_storage(dict)[ni])); + CHECK(eigs_slot_float(dict_value_slot(dict, ni), "test") == 2.0); + { EIGS_VIEW(v); CHECK(eigs_float_arg(dict_value_get_view(dict, ni, &v), "test") == 2.0); } { EIGS_VIEW(v); Value *zv = dict_get_view(dict, "z", &v); CHECK(zv && zv->type == VAL_NULL && dict_get_view(dict, "missing", &v) == NULL); } { int found = 0; EigsSlot zs = dict_get_slot(dict, "z", &found); CHECK(found && slot_is_null(zs)); (void)dict_get_slot(dict, "missing", &found); CHECK(!found); } owned = dict_value_get_owned(dict, ni); returned = dict_value_get_return(dict, ni); - CHECK(owned != returned && VAL_NUM_RAW(owned) == 2.0); + CHECK(owned != returned && VAL_F64_RAW(owned) == 2.0); val_decref(owned); val_decref(returned); - dict_value_set_num(dict, ni, 8.0); + dict_value_set_f64(dict, ni, 8.0); dict_value_set_slot_owned(dict, si, slot_from_heap(make_str("changed"))); - CHECK(eigs_slot_num(dict_value_slot(dict, ni), "test") == 8.0); + CHECK(eigs_slot_float(dict_value_slot(dict, ni), "test") == 8.0); taken = dict_value_take_slot(dict, ni); - CHECK(eigs_slot_num(taken, "test") == 8.0 && slot_is_null(dict_value_slot(dict, ni))); + CHECK(eigs_slot_float(taken, "test") == 8.0 && slot_is_null(dict_value_slot(dict, ni))); slot_decref(taken); - dict_value_set_owned(dict, ni, make_num(8.0)); + dict_value_set_owned(dict, ni, make_float(8.0)); val_decref(zeros); val_decref(dict); val_decref(sort); val_decref(list); eigs_close(state); diff --git a/tests/test_corpus_ints.sh b/tests/test_corpus_ints.sh index 9fff88741..3076ed690 100755 --- a/tests/test_corpus_ints.sh +++ b/tests/test_corpus_ints.sh @@ -96,10 +96,10 @@ else fi # genuine zeros run stays ONE repeated token -- real repetition is not destroyed. -if [ "$(g ZERO_COUNT)" = "5" ]; then +if [ "$(g ZERO_COUNT)" = "5.0" ]; then ok "IL02 a genuine zeros run stays one repeated token (real repetition kept)" else - fail "IL02 zeros run wrong" "zero_count=$(g ZERO_COUNT), want 5" + fail "IL02 zeros run wrong" "zero_count=$(g ZERO_COUNT), want 5.0" fi # 100 (>= int_count) and 3.5 (non-integer) must NOT occupy int tokens: exactly @@ -122,10 +122,10 @@ for i in range of nint: print of ("DISTINCT_TOTAL " + (str of tot)) RNG ROUT=$("$EIGS" "$WORK/range.eigs" "$WORK" 2>/dev/null | sed -n 's/^DISTINCT_TOTAL //p') -if [ "$ROUT" = "6" ]; then +if [ "$ROUT" = "6.0" ]; then ok "IL03 out-of-range (100) and non-integer (3.5) literals stay lossy, not int tokens" else - fail "IL03 wrong distinct int total" "distinct=$ROUT, want 6 (values 0..5 only)" + fail "IL03 wrong distinct int total" "distinct=$ROUT, want 6.0 (values 0..5 only)" fi # Backward compat: no 7th arg -> no int region, stream byte-identical to slot-only. diff --git a/tests/test_db.eigs b/tests/test_db.eigs index 1fa95d5ea..7457703f1 100644 --- a/tests/test_db.eigs +++ b/tests/test_db.eigs @@ -205,7 +205,7 @@ if live: assert of [(type of (db_query_value of "SELECT null::int")) == "none", "DB24 db_query_value returns null for SQL NULL"] assert of [(type of (db_query_value of "SELECT ''")) == "str", "DB24 db_query_value keeps '' a str"] assert of [(db_query_value of "SELECT 7::float8") == 7, "DB24 db_query_value types floats"] - # NaN would become 0 through make_num's num_guard — a silently wrong + # NaN would become 0 through make_float's num_guard — a silently wrong # number — so it comes back as text on both paths. assert of [(db_query_value of "SELECT 'NaN'::float8") == "NaN", "DB24 NaN stays text, not 0"] nan_row is (json_decode of (db_query_json of "SELECT 'NaN'::float8 AS x"))[0] @@ -218,20 +218,29 @@ if live: assert of [num_row.m == "1.10", "DB25 numeric keeps its scale"] assert of [(type of num_row.f) == "num", "DB25 ::float8 is the opt-in to a number"] - # An exact-integer column past 2^53 RAISES rather than silently rounding. - c26 is 0 - m26 is "" - try: - db_query_json of "SELECT 9007199254740993::bigint AS big" - catch e: - c26 is 1 - m26 is e.message - assert of [c26 == 1, "DB26 a bigint past 2^53 raises instead of rounding"] - assert of [(contains of [m26, "big"]), "DB26 message names the column"] - assert of [(contains of [m26, "::text"]), "DB26 message names the fix"] - # ...and the boundary itself is fine. + # N14 / Q-DB253: int64 SQL columns decode exactly, replacing the old + # 2^53 refusal now that the runtime represents those values as ints. + big53 is (json_decode of (db_query_json of "SELECT 9007199254740993::bigint AS big"))[0] + assert of [big53.big == 9007199254740993, "DB26 N14 bigint above 2^53 is exact"] + assert of [(num_type of big53.big) == "int", "DB26 N14 JSON integer column decodes as int"] + direct53 is db_query_value of "SELECT 9007199254740993::bigint" + assert of [direct53 == 9007199254740993, "DB26 N14 direct bigint above 2^53 is exact"] + assert of [(num_type of direct53) == "int", "DB26 N14 direct integer column is int"] ok53 is (json_decode of (db_query_json of "SELECT 9007199254740992::bigint AS big"))[0] - assert of [ok53.big == 9007199254740992, "DB26 2^53 itself is representable"] + assert of [ok53.big == 9007199254740992, "DB26 N14 2^53 itself remains exact"] + edges26 is (json_decode of (db_query_json of "SELECT '-9223372036854775808'::bigint AS lo, '9223372036854775807'::bigint AS hi"))[0] + assert of [edges26.lo == (-9223372036854775807 - 1), "DB26 N14 JSON INT64_MIN is exact"] + assert of [edges26.hi == 9223372036854775807, "DB26 N14 JSON INT64_MAX is exact"] + assert of [(num_type of edges26.lo) == "int" and (num_type of edges26.hi) == "int", "DB26 N14 JSON int64 edges retain kind"] + lo26 is db_query_value of "SELECT '-9223372036854775808'::bigint" + hi26 is db_query_value of "SELECT '9223372036854775807'::bigint" + assert of [lo26 == edges26.lo and hi26 == edges26.hi, "DB26 N14 direct int64 edges match JSON"] + assert of [(num_type of lo26) == "int" and (num_type of hi26) == "int", "DB26 N14 direct int64 edges retain kind"] + assert of [(num_type of (db_query_value of "SELECT 1::smallint")) == "int", "DB26 N14 smallint is int"] + assert of [(num_type of (db_query_value of "SELECT 1::integer")) == "int", "DB26 N14 integer is int"] + assert of [(db_query_value of "SELECT 4294967295::oid") == 4294967295, "DB26 N14 oid maximum is exact"] + assert of [(num_type of (db_query_value of "SELECT 1::oid")) == "int", "DB26 N14 oid is int"] + assert of [(num_type of (db_query_value of "SELECT 7::float8")) == "float", "DB26 N14 integral real column remains float"] # ---- #1637: a bool parameter binds as an SQL boolean, so a bool read # from a boolean column can be written back ---- diff --git a/tests/test_db_params.c b/tests/test_db_params.c index 301f7ebe8..f2b66f654 100644 --- a/tests/test_db_params.c +++ b/tests/test_db_params.c @@ -14,13 +14,14 @@ static void check(int ok, const char *name) { printf("%s: %s\n", ok ? "PASS" : "FAIL", name); } -int main(void) { - EigsState *st = eigs_open(); - if (!st) return 2; +/* #1808/#2098: intentional refusals need a handler on the active VM. + * Keep the existing fourteen assertions inside that real catch context. */ +static Value *db_params_probe(Value *probe_arg) { + (void)probe_arg; Value *arg = make_list(5); list_append_owned(arg, make_str("update t set a = $1, b = $2, c = $3, d = $4")); list_append(arg, make_bool(1)); - list_append_owned(arg, make_num(3)); + list_append_owned(arg, make_float(3)); list_append_owned(arg, make_str("s")); list_append(arg, make_bool(0)); const char *sql = NULL; const char *params[DB_MAX_PARAMS]; @@ -29,7 +30,7 @@ int main(void) { int ok = db_build_query(arg, &sql, &n, params, numbuf, types); check(ok && n == 4, "four parameters built"); check(ok && strcmp(params[0], "true") == 0 && types[0] == 16, "true binds as SQL boolean 'true' (OID 16)"); - check(ok && strcmp(params[1], "3") == 0 && types[1] == 0, "a number keeps the inferred type"); + check(ok && strcmp(params[1], "3.0") == 0 && types[1] == 0, "a number keeps the inferred type"); check(ok && strcmp(params[2], "s") == 0 && types[2] == 0, "a string keeps the inferred type"); check(ok && strcmp(params[3], "false") == 0 && types[3] == 16, "false binds as SQL boolean 'false' (OID 16)"); check(DB_PARAM_OID_BOOL == DB_OID_BOOL, "the parameter OID is the column classifier's boolean OID"); @@ -41,7 +42,39 @@ int main(void) { "a dict parameter still raises"); eigs_clear_error(); val_decref(bad); + /* R1.7 N6/N14: exact int digits and Python-repr float text. */ + Value *numbers = make_list(8); + list_append_owned(numbers, make_str("select $1, $2, $3, $4, $5, $6, $7")); + list_append_owned(numbers, make_int(1234567)); + list_append_owned(numbers, make_int(INT64_C(9007199254740993))); + list_append_owned(numbers, make_float(0.1 + 0.2)); + list_append_owned(numbers, make_float(3.0)); + list_append_owned(numbers, make_float(-0.0)); + list_append_owned(numbers, make_float(0.1)); + list_append_owned(numbers, make_int(INT64_MIN)); + const char *expected[] = {"1234567", "9007199254740993", + "0.30000000000000004", "3.0", "-0.0", "0.1", "-9223372036854775808"}; + ok = db_build_query(numbers, &sql, &n, params, numbuf, types); + for (int i = 0; i < 7; i++) { + char label[96]; + snprintf(label, sizeof(label), "N6/N14 parameter %d: %s", i + 1, expected[i]); + check(ok && n == 7 && strcmp(params[i], expected[i]) == 0 && types[i] == 0, label); + } + val_decref(numbers); + return make_bool(1); +} + +int main(void) { + EigsState *st = eigs_open(); + if (!st) return 2; + Value *probe = make_builtin(db_params_probe); + eigs_set_global("db_params_probe", probe); + val_decref(probe); + Value *result = eigs_eval_string("try:\n db_params_probe of null\ncatch e:\n false\n"); + int probe_ok = result && result->type == VAL_BOOL && result->data.boolean && !eigs_has_error(); + if (!probe_ok) fprintf(stderr, "db params: VM probe execution failed\n"); + val_decref(result); eigs_close(st); - printf("db params: %d passed, %d failed (7 declared)\n", passed, failed); - return failed || passed != 7; + printf("db params: %d passed, %d failed (14 declared)\n", passed, failed); + return !probe_ok || failed || passed != 14; } diff --git a/tests/test_doc_examples.py b/tests/test_doc_examples.py index 409e84910..2a7c15c6a 100644 --- a/tests/test_doc_examples.py +++ b/tests/test_doc_examples.py @@ -331,10 +331,10 @@ def is_asan_build(): # --------------------------------------------------------------------------- POPULATION = { # file fences paired fragment nocheck value-comments - "README.md": ( 7, 3, 4, 0, 0), - "docs/llms.txt": ( 9, 6, 2, 1, 0), - "docs/SPEC.md": ( 87, 83, 1, 3, 0), # #1425: +1 paired (multi-line binary operator error line) - "docs/COMPARISON.md": ( 19, 18, 1, 0, 0), + "README.md": (17, 13, 4, 0, 0), # R1.6g: +1 paired Rust-derived N9 example + "docs/llms.txt": (19, 16, 2, 1, 0), # R1.6g: +1 paired Rust-derived N9 example + "docs/SPEC.md": (98, 94, 1, 3, 0), # Phase 2 + main #1425 operator-line example + "docs/COMPARISON.md": (29, 28, 1, 0, 0), # R1.6g: +1 paired Rust-derived N9 example "docs/CONCURRENCY.md": ( 9, 9, 0, 0, 0), # #1879: +1 paired (thread handle crosses a channel) "docs/STDLIB.md": ( 15, 7, 8, 0, 0), "docs/SYNTAX.md": ( 35, 15, 20, 0, 1), @@ -342,7 +342,7 @@ def is_asan_build(): "docs/PREDICATES.md": ( 13, 2, 10, 1, 4), "docs/DIAGNOSTICS.md": ( 4, 1, 3, 0, 0), "docs/OBSERVER.md": ( 2, 0, 2, 0, 0), - "docs/BUILTINS.md": ( 2, 2, 0, 0, 0), # #1888: nearest-other contract + "docs/BUILTINS.md": (4, 4, 0, 0, 0), # Phase 2 + main #1888 nearest-other example "docs/LANGUAGE_CONTRACT.md": ( 1, 0, 1, 0, 0), } diff --git a/tests/test_embed_observer.c b/tests/test_embed_observer.c index bb2dd002e..8be5d2e8a 100644 --- a/tests/test_embed_observer.c +++ b/tests/test_embed_observer.c @@ -34,11 +34,11 @@ static void raw_host(void) { return; } Env *env = env_new(NULL); - env_set_local_owned(env, "raw_x", make_num(100)); + env_set_local_owned(env, "raw_x", make_float(100)); double x = 100; for (int i = 0; i < 12; i++) { x *= 0.5; - env_set_local_owned(env, "raw_x", make_num(x)); + env_set_local_owned(env, "raw_x", make_float(x)); observer_slot_update_num(env, 0, x); } int answer = observer_predicate_at(env, 0, 2 /* improving */, 1); @@ -53,12 +53,12 @@ static void direct(void) { EigsState *st = eigs_open(); if (!st) { check(0, "open direct state"); return; } Env *env = g_global_env; - env_set_local_owned(env, "native_x", make_num(100)); + env_set_local_owned(env, "native_x", make_float(100)); int idx = env->count - 1; double x = 100; for (int i = 0; i < 12; i++) { x *= 0.5; - env_set_local_owned(env, "native_x", make_num(x)); + env_set_local_owned(env, "native_x", make_float(x)); observer_slot_update_num(env, idx, x); } int answer = observer_predicate_at(env, idx, 2 /* improving opcode operand */, 1); @@ -70,7 +70,7 @@ static void direct(void) { x = 100; for (int i = 0; i < 12; i++) { x *= 0.5; - emit(c, OP_CONST, (uint16_t)constant(c, make_num(x))); + emit(c, OP_CONST, (uint16_t)constant(c, make_float(x))); emit(c, OP_SET_NAME, (uint16_t)name); emit(c, OP_OBSERVE_NAME_POST, (uint16_t)name); chunk_emit(c, OP_POP, 1); @@ -203,16 +203,16 @@ static void gap(const char *source, const char *name) { static EigsValue *host_reader(EigsValue *arg) { (void)arg; Env *env = env_new(NULL); - env_set_local_owned(env, "x", make_num(100)); + env_set_local_owned(env, "x", make_float(100)); double x = 100; for (int i = 0; i < 12; i++) { x *= 0.5; - env_set_local_owned(env, "x", make_num(x)); + env_set_local_owned(env, "x", make_float(x)); observer_slot_update_num(env, 0, x); } int answer = observer_predicate_at(env, 0, 2 /* improving */, 1); env_decref(env); - return make_num(answer); + return make_float(answer); } /* Execute the documented explicit-host-arm recipe. No compiled predicate * may rescue the read-free unit: interrogate its slot directly from C. */ diff --git a/tests/test_env_flags.sh b/tests/test_env_flags.sh index 5f342606a..c956de700 100755 --- a/tests/test_env_flags.sh +++ b/tests/test_env_flags.sh @@ -9,7 +9,7 @@ set -u TESTS_DIR="$(cd "$(dirname "$0")" && pwd)" EIGS="$TESTS_DIR/../src/eigenscript" -PROG="$TESTS_DIR/bench_idxset.eigs" +PROG="$TESTS_DIR/jit_env_flags_float.eigs" compiled() { env "$@" EIGS_JIT_STATS=1 "$EIGS" "$PROG" 2>&1 >/dev/null | grep -o 'compiled=[0-9]*' | head -1; } c_on=$(compiled -u EIGS_JIT_OFF) c_zero=$(compiled EIGS_JIT_OFF=0) diff --git a/tests/test_error_line_fallback.c b/tests/test_error_line_fallback.c index 727c74290..ab459423d 100644 --- a/tests/test_error_line_fallback.c +++ b/tests/test_error_line_fallback.c @@ -111,8 +111,8 @@ int main(void) { * 6001 calls make it hot for the JIT. The last element returns a string, * so sort_by raises after its final callback. */ EigsValue *defs = eigs_eval_string( - "define ekey(x) as:\n y is 0\n i is 0\n loop while i < 3:\n" - " y is y + x\n i is i + 1\n if x < 0:\n" + "define ekey(x) as:\n y is 0.0\n i is 0.0\n loop while i < 3.0:\n" + " y is y + x\n i is i + 1.0\n if x < 0.0:\n" " return \"s\"\n return y\n"); if (defs) eigs_value_release(defs); Value *ekey = eigs_get_global("ekey"); @@ -120,8 +120,8 @@ int main(void) { if (!ekey) return 1; Value *good = make_list(6001), *bad = make_list(6001); for (int i = 0; i < 6001; i++) { - list_append_owned(good, make_num(6001 - i)); - list_append_owned(bad, make_num(i < 6000 ? 6001 - i : -1)); + list_append_owned(good, make_float(6001 - i)); + list_append_owned(bad, make_float(i < 6000 ? 6001 - i : -1)); } Value *args_good = make_list(2), *args_bad = make_list(2); list_append(args_good, good); list_append(args_good, ekey); @@ -150,22 +150,22 @@ int main(void) { static const char *const NM = "errline_1434"; EigsSlot sl, out; g_has_error = 0; /* a pending error halts the callback before it runs */ - g_trace_current_line = 70; SLOT_NUM_RAW(sl) = 1.0; trace_assign(NM, sl); + g_trace_current_line = 70; SLOT_F64_RAW(sl) = 1.0; trace_assign(NM, sl); g_trace_current_line = 80; r = builtin_sort_by(args_good); check(!g_has_error && r && r->type == VAL_LIST && r->data.list.count == 6001, "#1434 the history row's sort_by ran its callbacks"); if (r) val_decref(r); - SLOT_NUM_RAW(sl) = 2.0; trace_assign(NM, sl); + SLOT_F64_RAW(sl) = 2.0; trace_assign(NM, sl); trace_line(95); /* a stop right after the store, for [0b]'s `--step` query */ - check(trace_query_at(0, NM, 75, &out) && SLOT_NUM_RAW(out) == 1.0 && - trace_query_at(0, NM, 85, &out) && SLOT_NUM_RAW(out) == 2.0, + check(trace_query_at(0, NM, 75, &out) && SLOT_F64_RAW(out) == 1.0 && + trace_query_at(0, NM, 85, &out) && SLOT_F64_RAW(out) == 2.0, "#1434 a store after the callbacks is filed under the call's stamp"); /* The line live history filed the second store under, for [0b] to compare * with the tape stepper's `t` row over the same run's EIGS_TRACE tape. */ int filed = 0; for (int ln = 1; ln <= 100 && !filed; ln++) - if (trace_query_at(0, NM, ln, &out) && SLOT_NUM_RAW(out) == 2.0) filed = ln; + if (trace_query_at(0, NM, ln, &out) && SLOT_F64_RAW(out) == 2.0) filed = ln; printf("LIVE: %s=2 line %d\n", NM, filed); g_has_error = 0; @@ -180,12 +180,12 @@ int main(void) { * NUM_ZERO 2, DIV 7, RETURN 40 (src/vm.h enum order). */ static const int HALT77[] = {68, 77, 0, 0, 0, 3, 2, 7, 40}; Value *code = make_list(9), *desc = make_list(3), *sb_args = make_list(2); - for (int i = 0; i < 9; i++) list_append_owned(code, make_num(HALT77[i])); - list_append_owned(desc, make_num(1)); + for (int i = 0; i < 9; i++) list_append_owned(code, make_float(HALT77[i])); + list_append_owned(desc, make_float(1)); list_append_owned(desc, code); list_append_owned(desc, make_list(0)); list_append_owned(sb_args, desc); - list_append_owned(sb_args, make_num(100)); + list_append_owned(sb_args, make_float(100)); g_has_error = 0; g_trace_current_line = 100; r = builtin_sandbox_run(sb_args); diff --git a/tests/test_float_index_range_1849.eigs b/tests/test_float_index_range_1849.eigs index b74e38dff..5061d5745 100644 --- a/tests/test_float_index_range_1849.eigs +++ b/tests/test_float_index_range_1849.eigs @@ -44,4 +44,44 @@ frac_args is [table, 2147483647.5, null] expect_error of ["fractional builtin dispatch key above int range", "value", (_) => dispatch of frac_args] expect_error of ["fractional slice bound", "value", (_) => xs[1.5:]] +# Sync #10: main's range diagnostic also applies to exact Phase2 integers. +# Keep the index-domain check in int64 rather than rounding an int to float. +huge_int is 1099511627776 +negative_int is -1099511627776 +buf is buffer of 2 +text is "ab" + +define write_list(index) as: + xs[index] is 99.0 + +define write_buffer(index) as: + buf[index] is 99.0 + +expect_error of ["positive int list index", "index_range", (_) => xs[huge_int]] +expect_error of ["negative int list index", "index_range", (_) => xs[negative_int]] +expect_error of ["positive int buffer index", "index_range", (_) => buf[huge_int]] +expect_error of ["negative int buffer index", "index_range", (_) => buf[negative_int]] +expect_error of ["positive int string index", "index_range", (_) => text[huge_int]] +expect_error of ["negative int string index", "index_range", (_) => text[negative_int]] +expect_error of ["positive int list store", "index_range", (_) => write_list of huge_int] +expect_error of ["negative int list store", "index_range", (_) => write_list of negative_int] +expect_error of ["positive int buffer store", "index_range", (_) => write_buffer of huge_int] +expect_error of ["negative int buffer store", "index_range", (_) => write_buffer of negative_int] + +# N5 refuses a float without a signed-int64 representation before the C-int +# range path. This Phase2 boundary precedes main's old double-only clamp. +outside_i64 is 9223372036854775808.0 +expect_error of ["outside int64 list index", "value", (_) => xs[outside_i64]] +expect_error of ["outside int64 buffer index", "value", (_) => buf[outside_i64]] +expect_error of ["outside int64 string index", "value", (_) => text[outside_i64]] +expect_error of ["outside int64 list store", "value", (_) => write_list of outside_i64] +expect_error of ["outside int64 buffer store", "value", (_) => write_buffer of outside_i64] + +assert_eq of [xs[-1], 3.5, "valid exact negative list index"] +assert_eq of [text[-1], "b", "valid exact negative string index"] +xs[0] is 7.5 +buf[0] is 8.5 +assert_eq of [xs[0], 7.5, "valid exact list store"] +assert_eq of [buf[0], 8.5, "valid exact buffer store"] + test_summary of null diff --git a/tests/test_floor_division.eigs b/tests/test_floor_division.eigs new file mode 100644 index 000000000..5b3f636e0 --- /dev/null +++ b/tests/test_floor_division.eigs @@ -0,0 +1,151 @@ +# #1758 N4/N4b/Q6: exact floor arithmetic and owned boxed operands. +load_file of "lib/test.eigs" + +assert_eq of [7 // 2, 3, "int floor division"] +assert_eq of [-7 // 2, -4, "negative dividend floors"] +assert_eq of [7 // -2, -4, "negative divisor floors"] +assert_eq of [-7 // -2, 3, "two negatives"] +assert_eq of [7 + 9 // 2 * 3, 19, "multiplicative precedence"] +assert_eq of [24 / 3 // 2, 4, "multiplicative left associativity"] +assert_eq of [num_type of (7 // 2), "int", "int quotient kind"] +assert_eq of [num_type of (7.0 // 2), "float", "mixed quotient kind"] +assert_eq of [num_type of (7 // 2.0), "float", "reverse mixed quotient kind"] +assert_eq of [-7.5 // 2, -4, "float quotient floors"] +assert_eq of [-7 % 3, 2, "N4 positive divisor remainder"] +assert_eq of [7 % -3, -2, "N4 negative divisor remainder"] +assert_eq of [-7.5 % 2, 0.5, "N4 float remainder"] +assert_eq of [7.5 % -2, -0.5, "N4 negative float divisor"] +assert_eq of [num_type of (7 % 3), "int", "int remainder kind"] +assert_eq of [num_type of (7.0 % 3), "float", "mixed remainder kind"] +assert_eq of [1e300 % 3.0, 0, "remainder without overflowing the quotient product"] + +# #1882: Python float_divmod rounds from fmod, including mixed operands. +assert_eq of [1.0 // 0.1, 9.0, "decimal quotient boundary"] +assert_eq of [1 // 0.1, 9.0, "mixed decimal quotient boundary"] +assert_eq of [-1.0 // -0.1, 9.0, "negative decimal pair"] +assert_eq of [7.5 // 0.1, 74.0, "fractional decimal quotient boundary"] +assert_eq of [3.0 // -0.1, -30.0, "quotient snaps after floor correction"] +assert_eq of [f64_to_bytes of (-0.0 // 2.0), [128, 0, 0, 0, 0, 0, 0, 0], "zero quotient takes negative ratio sign"] +assert_eq of [f64_to_bytes of (-0.0 // -2.0), [0, 0, 0, 0, 0, 0, 0, 0], "zero quotient takes positive ratio sign"] +assert_eq of [1e-300 // -1e300, -1.0, "negative quotient underflow floors"] +assert_eq of [-1e-300 // 1e300, -1.0, "negative dividend underflow floors"] +assert_eq of [num_type of (1 // 0.1), "float", "mixed boundary result kind"] +rounding_target is 1.0 +rounding_target //= 0.1 +assert_eq of [rounding_target, 9.0, "compound decimal quotient boundary"] +underflow_target is 1e-300 +underflow_target //= -1e300 +assert_eq of [underflow_target, -1.0, "compound quotient underflow"] + +# Independent small-number quotient oracle; identity and sign cover every +# sign combination and exact multiples without permitting C truncation. +a is -30 +loop while a <= 30: + b is -7 + loop while b <= 7: + if b != 0: + q is a // b + rem is a % b + assert_eq of [q, floor of (a / b), "small quotient oracle"] + assert_eq of [q * b + rem, a, "division/modulo identity"] + assert of [rem == 0 or ((rem > 0) == (b > 0)), "divisor sign"] + b is b + 1 + a is a + 1 + +# Exact boxed int cases: generated from Python integer // and %, so no +# float round-trip can agree accidentally above 2^53. +assert_eq of [9007199254740993 // 3, 3002399751580331, "boxed quotient"] +assert_eq of [9007199254740993 % 3, 0, "boxed remainder"] +assert_eq of [-9007199254740993 // 3, -3002399751580331, "boxed quotient"] +assert_eq of [-9007199254740993 % 3, 0, "boxed remainder"] +assert_eq of [9223372036854775807 // -3, -3074457345618258603, "boxed quotient"] +assert_eq of [9223372036854775807 % -3, -2, "boxed remainder"] +assert_eq of [(-9223372036854775807 - 1) // 3, -3074457345618258603, "boxed quotient"] +assert_eq of [(-9223372036854775807 - 1) % 3, 1, "boxed remainder"] +assert_eq of [(-9223372036854775807 - 1) % -1, 0, "boxed remainder"] +assert_eq of [1 // (-9223372036854775807 - 1), -1, "boxed quotient"] +assert_eq of [1 % (-9223372036854775807 - 1), -9223372036854775807, "boxed remainder"] +assert_eq of [-1 // 9223372036854775807, -1, "boxed quotient"] +assert_eq of [-1 % 9223372036854775807, 9223372036854775806, "boxed remainder"] +assert_eq of [(-9223372036854775807 - 1) // 9223372036854775807, -2, "boxed quotient"] +assert_eq of [(-9223372036854775807 - 1) % 9223372036854775807, 9223372036854775806, "boxed remainder"] + +# Q6: the existing compound-assignment target machinery must carry //. +x is -7 +x //= 2 +assert_eq of [x, -4, "name //="] +d is {"x": 7.5} +d.x //= -2 +assert_eq of [d.x, -4, "field //="] +xs is [7, -7.5] +xs[0] //= -2 +xs[1] //= 2 +assert_eq of [xs, [-4, -4], "index //="] +assert_eq of [num_type of xs[1], "float", "compound float kind"] + +# Fresh boxed operands in success and catch paths. Repeated calls make a +# missing decref measurable under LSan rather than hidden by live globals. +define boxed(n) as: + return 9223372036854775807 - n +# range still yields floats through R1.5. Use a VM int counter and pin +# the fresh operand kind so an immediate float cannot hide a missing decref. +k is 0 +loop while k < 80: + large is boxed of k + assert_eq of [num_type of large, "int", "ownership fixture is boxed int"] + assert of [large > 140737488355327, "ownership fixture exceeds the SMI range"] + assert_eq of [large % -1, 0, "boxed modulo special divisor"] + assert_eq of [large // 1, large, "boxed quotient retained"] + for op in ["//", "%"]: + caught is false + try: + if op == "//": + unused is (boxed of k) // 0 + else: + unused is (boxed of k) % 0 + catch e: + caught is e.kind == "value" + assert_eq of [caught, true, "boxed zero divisor raises"] + caught is false + try: + unused is (-9223372036854775807 - 1) // -1 + catch e: + caught is e.kind == "value" and (index_of of [e.message, "//"]) >= 0 + assert_eq of [caught, true, "N4b quotient overflow names operator"] + for operands in [[boxed of k, "bad"], ["bad", boxed of k]]: + caught is false + try: + unused is operands[0] // operands[1] + catch e: + caught is e.kind == "type_mismatch" + assert_eq of [caught, true, "boxed // type error owns both operands"] + caught is false + try: + unused is operands[0] % operands[1] + catch e: + caught is e.kind == "type_mismatch" + assert_eq of [caught, true, "boxed % type error owns both operands"] + k is k + 1 + +# #1758 comment 6294926822: all zero divisors raise in R1.4, including +# float/mixed // and //=; / and % retain their existing errors until N11. +for pair in [[1.0, 0], [0.0, 0.0], [7, 0.0], [1.0, -0.0]]: + for op in ["//", "/", "%", "//="]: + caught is false + try: + if op == "//": + unused is pair[0] // pair[1] + elif op == "/": + unused is pair[0] / pair[1] + elif op == "%": + unused is pair[0] % pair[1] + else: + local zz is pair[0] + zz //= pair[1] + catch e: + caught is e.kind == "value" and e.message == "division by zero" + if op == "%": + caught is e.kind == "value" and e.message == "modulo by zero" + assert_eq of [caught, true, "zero-divisor error compatibility"] + +test_summary of null diff --git a/tests/test_fmt.sh b/tests/test_fmt.sh index b485ec2f6..20045a9b5 100644 --- a/tests/test_fmt.sh +++ b/tests/test_fmt.sh @@ -154,6 +154,10 @@ EXPECTED=$(printf 'x is 1 + 2\n') check "--write mode" "$ACTUAL" "$EXPECTED" rm -f "$TMPFILE" +# #1758 N4/Q6: formatter inherits the lexer's single operator table. +ACTUAL=$(fmt_str "$(printf 'x is -7//2\nx//=2\n')") +check "// and //= spaced whole" "$ACTUAL" "$(printf 'x is -7 // 2\nx //= 2\n')" + # --- Multi-char operators must not be split (#729) --- # The third pass of fix_spacing is character-level; any operator missing from # its multi-char table gets split by the single-char branches, which silently diff --git a/tests/test_gc_traversal.c b/tests/test_gc_traversal.c index 77488ac5a..c380d5c4d 100644 --- a/tests/test_gc_traversal.c +++ b/tests/test_gc_traversal.c @@ -62,7 +62,7 @@ static void collect(int nodes,int garbage,int values,int envs,int chunks) { static void duplicate_and_leaf(void) { Value *leaf=make_list_heap(64), *root=make_list_heap(2); - for (int i=0;i<64;++i) list_append_owned(leaf,make_num(i)); + for (int i=0;i<64;++i) list_append_owned(leaf,make_float(i)); list_append(root,leaf); list_append(root,leaf); duplicate_child=leaf; val_decref(leaf); gc_note_possible_root(root); @@ -73,7 +73,7 @@ static void duplicate_and_leaf(void) { collect(4,2,4,0,0); CHECK(traversal_skips>before,"numeric-leaf mark traversal skipped"); CHECK(list_slot(root,0).u==list_slot(root,1).u,"live duplicate aliases retained"); - CHECK(eigs_slot_num(list_slot(slot_as_ptr(list_slot(root,0)),63),"gc_traversal")==63,"live leaf contents retained"); + CHECK(eigs_slot_float(list_slot(slot_as_ptr(list_slot(root,0)),63),"gc_traversal")==63,"live leaf contents retained"); duplicate_child=NULL; val_decref(root); gc_collect_cycles(); ++cases; diff --git a/tests/test_gfx_timeout.py b/tests/test_gfx_timeout.py index e8abb1cb6..8683974b8 100644 --- a/tests/test_gfx_timeout.py +++ b/tests/test_gfx_timeout.py @@ -53,7 +53,7 @@ def main(): "time.sleep(float(os.environ.get('EIGS_TIMEOUT_CONTROL_START_DELAY','0')))\n" "if mode in ('ignore','leak-ignore'): signal.signal(signal.SIGTERM,signal.SIG_IGN)\n" "open(" + repr(str(pidfile)) + ", 'w').write(str(os.getpid()))\n" - "if mode=='clean': print('sdl-present: 0'); raise SystemExit(0)\n" + "if mode=='clean': print('sdl-present: false'); raise SystemExit(0)\n" "if mode=='error': raise SystemExit(7)\n" "if mode.startswith('leak-'): print('==123==ERROR: LeakSanitizer: detected memory leaks',flush=True)\n" "if mode=='leak-clean': raise SystemExit(1)\n" diff --git a/tests/test_halting_descent.eigs b/tests/test_halting_descent.eigs index bf1953b01..0b554693e 100644 --- a/tests/test_halting_descent.eigs +++ b/tests/test_halting_descent.eigs @@ -8,12 +8,12 @@ print of "--- HD1: bare-predicate loop around a runaway exits via STALL ---" # honest exit reason. counter is 0 -x is 10 -x is 10 +x is 10.0 +x is 10.0 loop while not converged: counter is counter + 1 - x is x * 2 + x is x * 2.0 print of counter print of report of x diff --git a/tests/test_handles_mt.sh b/tests/test_handles_mt.sh index 5e45e124a..774ba6d6b 100755 --- a/tests/test_handles_mt.sh +++ b/tests/test_handles_mt.sh @@ -303,9 +303,11 @@ done # generation, which is the `generation-ignored` mutant. gen_two_arg=$(grep -rn 'handle_lookup([^,)]*, *HANDLE_' "$SRC_DIR"/*.c | grep -c . || true) # The handle VALUES carry it: one publication per dict-handle producer. -pub_thread=$(grep -c '"_handle_gen", make_num((double)hgen)' "$BI_C" || true) -pub_chan=$(grep -c '"_channel_gen", make_num((double)cgen)' "$BI_C" || true) -pub_store=$(grep -c '"_store_gen", make_num((double)sgen)' "$SRC_DIR/ext_store.c" || true) +# R1.6b/N3 makes spawn/channel metadata ints; R1.6f does the same for +# store metadata. Pin each producer's reviewed constructor separately. +pub_thread=$(grep -c '"_handle_gen", make_int(hgen)' "$BI_C" || true) +pub_chan=$(grep -c '"_channel_gen", make_int(cgen)' "$BI_C" || true) +pub_store=$(grep -c '"_store_gen", make_int(sgen)' "$SRC_DIR/ext_store.c" || true) # ... and the slot bumps it on every hand-out. reg_bump=$(printf '%s\n' "$(fn_body "$ES_C" "handle_register")" \ | grep -c 'st->handle_table\[idx\].gen = g;' || true) diff --git a/tests/test_hex_literals.eigs b/tests/test_hex_literals.eigs index 4e6bde76b..56f33aa3a 100644 --- a/tests/test_hex_literals.eigs +++ b/tests/test_hex_literals.eigs @@ -33,16 +33,15 @@ check of ["hex feeds hex-of round trip", hex of 0xBEEF, "BEEF"] xs is [0xA, 0xB] check of ["list of hex literals", xs[1], 11] -# num-of string conversion shares the lexer's contract (#378 through the -# string path): hex integers convert on every profile, conversion stops -# at the first non-hex character (so "0x1p4" is 1 — the p is just a -# stray char, exactly how the lexer splits the same source), and a -# prefix with no hex digit converts to 0 like any non-numeric string — -# strtod never sees a hex prefix here either. +# N10 (#1758 comment 6304880619): complete numeric-literal grammar. check of ["num of hex string", num of "0xFF", 255] check of ["num of negative hex string", num of "-0x10", -16] -check of ["num of stops at non-hex", num of "0xFFzz", 255] -check of ["num of hex-float string is 0", num of "0x.8", 0] -check of ["num of stops at p (no exponent semantics)", num of "0x1p4", 1] +for bad in ["0xFFzz", "0x.8", "0x1p4"]: + caught is false + try: + num of bad + catch e: + caught is e.kind == "value" + check of [f"N10 refuses {bad}", caught, true] print of "HEX_LITERALS_ALL_PASS" diff --git a/tests/test_http.eigs b/tests/test_http.eigs index 805f6ee5a..1af2d0bc4 100644 --- a/tests/test_http.eigs +++ b/tests/test_http.eigs @@ -87,4 +87,52 @@ catch e: header_shape is contains of [e.message, "requires"] assert_true of [header_shape, "HTTP17 response header shape raises"] +# N3 / (A): shared counters preserve integer arithmetic, including boxed +# ints. Follow every refused update with another operation to exercise the +# lock cleanup and prove that storage was not changed by the raise. +counter_big is 9007199254740992 +assert_true of [(num_type of counter_big) == "int", "HTTP18 N3 boxed counter fixture is int"] +shared_set of ["phase2-big", counter_big] +counter_next is shared_incr of ["phase2-big", 1] +assert_true of [counter_next == 9007199254740993, "HTTP18 N3 shared counter adds exactly above 2^53"] +assert_true of [(num_type of counter_next) == "int", "HTTP18 N3 shared result is int"] +counter_read is shared_get of "phase2-big" +assert_true of [counter_read == counter_next and (num_type of counter_read) == "int", "HTTP18 N14 stored boxed counter stays exact int"] + +shared_set of ["phase2-max", 9223372036854775807] +counter_overflow is "" +try: + shared_incr of ["phase2-max", 1] +catch e: + counter_overflow is e.kind +assert_true of [counter_overflow == "value", "HTTP19 A shared INT64_MAX overflow raises value"] +assert_true of [(shared_get of "phase2-max") == 9223372036854775807, "HTTP19 A overflow leaves counter unchanged and unlocks"] +assert_true of [(shared_incr of ["phase2-max", -1]) == 9223372036854775806, "HTTP19 A next increment succeeds after overflow"] + +counter_min is -9223372036854775807 - 1 +assert_true of [(num_type of counter_min) == "int", "HTTP20 N3 INT64_MIN fixture is int"] +shared_set of ["phase2-min", counter_min] +counter_underflow is "" +try: + shared_incr of ["phase2-min", -1] +catch e: + counter_underflow is e.kind +assert_true of [counter_underflow == "value", "HTTP20 A shared INT64_MIN underflow raises value"] +assert_true of [(shared_get of "phase2-min") == counter_min, "HTTP20 A underflow leaves counter unchanged and unlocks"] +assert_true of [(shared_incr of ["phase2-min", 1]) == -9223372036854775807, "HTTP20 A next increment succeeds after underflow"] + +shared_set of ["phase2-float", 1.5] +counter_float is shared_incr of ["phase2-float", 2] +assert_true of [counter_float == 3.5 and (num_type of counter_float) == "float", "HTTP21 N3 float counter keeps float arithmetic"] +shared_set of ["phase2-mixed", 1] +counter_mixed is shared_incr of ["phase2-mixed", 0.5] +assert_true of [counter_mixed == 1.5 and (num_type of counter_mixed) == "float", "HTTP21 N3 float delta produces float"] +counter_new is shared_incr of ["phase2-new", 7] +assert_true of [counter_new == 7 and (num_type of counter_new) == "int", "HTTP22 N3 absent counter starts at int zero"] +counter_new_float is shared_incr of ["phase2-new-float", 0.5] +assert_true of [counter_new_float == 0.5 and (num_type of counter_new_float) == "float", "HTTP22 N3 absent float counter stays float"] +shared_set of ["phase2-string", "heap counter mismatch"] +assert_true of [(shared_incr of ["phase2-string", 1]) == null, "HTTP23 nonnumeric shared counter still returns null"] +assert_true of [(shared_get of "phase2-string") == "heap counter mismatch", "HTTP23 nonnumeric counter remains unchanged and unlocks"] + test_summary of null diff --git a/tests/test_http_rss_growth.sh b/tests/test_http_rss_growth.sh index 7f844ba2f..5bbe02af8 100755 --- a/tests/test_http_rss_growth.sh +++ b/tests/test_http_rss_growth.sh @@ -38,7 +38,7 @@ # # RSS1 shared_incr: the counter path the builtin exists for. Leaked two # Values per call (a parsed JSON Value on both the success and the -# type-mismatch path, plus the make_num handed to eigs_json_encode). +# type-mismatch path, plus the make_float handed to eigs_json_encode). # RSS2 http_route_authed with a shared-store auth source: leaked the parsed # `require_auth` Value on every authenticated request. Same class, found # while auditing the other eigs_json_parse_value call sites for #731. diff --git a/tests/test_intern_owners.c b/tests/test_intern_owners.c index cd1301bca..61d892681 100644 --- a/tests/test_intern_owners.c +++ b/tests/test_intern_owners.c @@ -47,11 +47,11 @@ int main(void) { if (!a) { fputs("SETUP FAIL: state A\n", stderr); return 2; } EnvInternTable *old = eigs_current->intern_tbl; env_intern_table_ref(old); /* test owner, retained through normal close */ - env_set_local_owned(a->global_env, "owner_fixture_root", make_num_permanent(3)); + env_set_local_owned(a->global_env, "owner_fixture_root", make_float_permanent(3)); CHECK("state root retains originating table", owns(a->global_env->intern_refs, old) == 1); CHECK("builtin layer retains originating table", owns(a->builtin_env->intern_refs, old) == 1); int before = refs(old); - env_set_local_owned(a->global_env, "owner_fixture_second", make_num_permanent(4)); + env_set_local_owned(a->global_env, "owner_fixture_second", make_float_permanent(4)); CHECK("same Env deduplicates table owner", refs(old) == before); char *old_name = env_intern_name("owner_fixture_old_chunk"); @@ -61,15 +61,15 @@ int main(void) { Value *fn = make_fn("owner_fixture_fn", params, 1, a->global_env); CHECK("function params acquire separate owner", fn->data.fn.param_intern_tbl == old && refs(old) == before + 2); Value *ordinary = make_dict(1); - dict_set_owned(ordinary, "owner_fixture_dict", make_num_permanent(5)); + dict_set_owned(ordinary, "owner_fixture_dict", make_float_permanent(5)); CHECK("ordinary dict retains its exact table", owns(ordinary->data.dict.intern_refs, old) == 1 && refs(old) == before + 3); (void)env_intern_name("owner_fixture_scope_zero"); uint32_t previous = g_sandbox_intern_scope; uint32_t scope = env_intern_scope_begin(); Value *zero = make_dict(1), *private = make_dict(1); - dict_set_owned(zero, "owner_fixture_scope_zero", make_num_permanent(6)); - dict_set_owned(private, "owner_fixture_private_key", make_num_permanent(7)); + dict_set_owned(zero, "owner_fixture_scope_zero", make_float_permanent(6)); + dict_set_owned(private, "owner_fixture_private_key", make_float_permanent(7)); CHECK("scope-zero reuse has exact table owner", owns(zero->data.dict.intern_refs, old) == 1); CHECK("promoted private key has live Value owner", private_names(a, private) == 1); CHECK("private copy does not retain whole table", private->data.dict.intern_refs == NULL); @@ -129,7 +129,7 @@ int main(void) { if (!b) { fputs("SETUP FAIL: state B\n", stderr); return 2; } EnvInternTable *other = eigs_current->intern_tbl; env_intern_table_ref(other); - env_set_local_owned(b->global_env, "owner_fixture_other_state", make_num_permanent(8)); + env_set_local_owned(b->global_env, "owner_fixture_other_state", make_float_permanent(8)); CHECK("state B owns its own table", owns(b->global_env->intern_refs, other) == 1); CHECK("state B does not own state A table", owns(b->global_env->intern_refs, old) == 0); CHECK("state A does not own state B table", owns(a->global_env->intern_refs, other) == 0); diff --git a/tests/test_issue_1885_followup.sh b/tests/test_issue_1885_followup.sh index 51bb56575..aeda1e7ee 100755 --- a/tests/test_issue_1885_followup.sh +++ b/tests/test_issue_1885_followup.sh @@ -42,7 +42,7 @@ if [ "$rc" -eq 0 ] && [ "$(grep -c BODY_ONCE "$d/out")" -eq 1 ] && grep -q '^42$ ok "module cache precedes source read after path becomes a directory" else bad "module cache precedes source read (rc=$rc)"; fi -# tensor_save has no payload representation in v7, so replay refuses it before +# tensor_save has no payload representation in v8, so replay refuses it before # xfopen_write rather than fabricating the recorded boolean. d="$T/tensor"; mkdir -p "$d" printf 'print of (tensor_save of [[1, 2], "%s/out.tensor"])\n' "$d" > "$d/program.eigs" diff --git a/tests/test_jit_andor_leak.eigs b/tests/test_jit_andor_leak.eigs index 050612544..8839f5ab4 100644 --- a/tests/test_jit_andor_leak.eigs +++ b/tests/test_jit_andor_leak.eigs @@ -6,17 +6,17 @@ # Run well past the OSR threshold so the JIT thunk is exercised; the ASan leak # gate (run_all_tests.sh under detect_leaks=1) catches a regression. h is "a-heap-string" -i is 0 -acc is 0 -loop while i < 12000: +i is 0.0 +acc is 0.0 +loop while i < 12000.0: r is h and i acc is acc + r - i is i + 1 + i is i + 1.0 # also exercise `or` with a heap left operand that short-circuits (truthy -> returns h) -j is 0 -loop while j < 12000: +j is 0.0 +loop while j < 12000.0: s is h or j - j is j + 1 + j is j + 1.0 print of "jit-and-or-ok" diff --git a/tests/test_jit_cycle_root.eigs b/tests/test_jit_cycle_root.eigs index 031349180..3d2bbb13b 100644 --- a/tests/test_jit_cycle_root.eigs +++ b/tests/test_jit_cycle_root.eigs @@ -17,34 +17,34 @@ define mk() as: return l define churn() as: - local i is 0 - loop while i < 1200: + local i is 0.0 + loop while i < 1200.0: local t is mk of [] - t is 0 - i is i + 1 - return 0 + t is 0.0 + i is i + 1.0 + return 0.0 define spin(c) as: churn of [] - local i is 0 - local dropped is 0 - loop while i < 6000: - if i == 5990: - c is 0 - dropped is 1 - i is i + 1 + local i is 0.0 + local dropped is 0.0 + loop while i < 6000.0: + if i == 5990.0: + c is 0.0 + dropped is 1.0 + i is i + 1.0 return dropped -checks is 0 -passed is 0 +checks is 0.0 +passed is 0.0 define check(label, got, want) as: - checks is checks + 1 + checks is checks + 1.0 if got == want: - passed is passed + 1 + passed is passed + 1.0 else: print of ("MISMATCH " + label + ": got=" + str of got + " want=" + str of want) -check of ["native drop executed", spin of (mk of []), 1] +check of ["native drop executed", spin of (mk of []), 1.0] if passed == checks: print of "All tests passed" diff --git a/tests/test_jit_diff_env.sh b/tests/test_jit_diff_env.sh index 726ef773a..ae68eefc4 100755 --- a/tests/test_jit_diff_env.sh +++ b/tests/test_jit_diff_env.sh @@ -6,17 +6,23 @@ TESTS_DIR="$(cd "$(dirname "$0")" && pwd)" export EIGS_TEST_DIR="$TESTS_DIR" . "$TESTS_DIR/suite_program_env.sh" || exit 1 -expected='test_buffer_nonfinite_read.eigs +expected='test_audio.eigs +test_buffer_nonfinite_read.eigs test_builtin_contracts.eigs test_builtin_indirect.eigs +test_coalesce_legacy.eigs test_file_io.eigs test_file_rename.eigs +test_gfx_argtypes.eigs +test_gfx_text.eigs test_json_hard.eigs test_numeric_guard.eigs +test_phase2_gfx_q7.eigs test_stdlib_fixes.eigs test_stream_io.eigs -test_tensor_buffer_ops.eigs' -actual=$(awk '$0 !~ /^[[:space:]]*#/ && NF { print $1 }' "$TESTS_DIR/suite_program_env.txt" | sort) +test_tensor_buffer_ops.eigs +test_ui_containment_gfx.eigs' +actual=$(awk '$0 !~ /^[[:space:]]*#/ && NF { print $1 }' "$TESTS_DIR/suite_program_env.txt" | LC_ALL=C sort) [ "$actual" = "$expected" ] || { echo "FAIL: suite environment population changed:"; printf '%s\n' "$actual"; exit 1; } out=$(suite_program_run test_file_io.eigs "$TESTS_DIR/../src/eigenscript" "$TESTS_DIR/test_file_io.eigs" 2>&1); rc=$? @@ -33,4 +39,4 @@ grep -q 'suite-env completed=\$n_suite_env' "$TESTS_DIR/../tools/jit_diff.sh" || echo "FAIL: jit_diff does not report the suite-environment completion population" exit 1 } -echo "PASS: jit_diff and the suite share 10 per-program environments; fail-soft program reached its final marker" +echo "PASS: jit_diff and the suite share 16 per-program environments; fail-soft program reached its final marker" diff --git a/tests/test_jit_hot_osr.eigs b/tests/test_jit_hot_osr.eigs index 8ad9bbe4c..de4d6cb65 100644 --- a/tests/test_jit_hot_osr.eigs +++ b/tests/test_jit_hot_osr.eigs @@ -1,7 +1,7 @@ # One frame entry with enough loop back-edges to enter an OSR thunk. The # EIGS_JIT_HOT suite row derives the expected aggregate from this chunk's row. -i is 0 -total is 0 -loop while i < 20000: +i is 0.0 +total is 0.0 +loop while i < 20000.0: total is total + i - i is i + 1 + i is i + 1.0 diff --git a/tests/test_jit_paths.eigs b/tests/test_jit_paths.eigs index 54c50a5ce..472d4b41e 100644 --- a/tests/test_jit_paths.eigs +++ b/tests/test_jit_paths.eigs @@ -12,16 +12,16 @@ # The suite runs this file with EIGS_JIT_STATS=1 and (on x86-64) asserts # compiled > 0, so it cannot silently pass interpreted. -pass_count is 0 -fail_count is 0 +pass_count is 0.0 +fail_count is 0.0 define check(args) as: - label is args[0] - cond is args[1] + label is args[0.0] + cond is args[1.0] if cond: - pass_count is pass_count + 1 + pass_count is pass_count + 1.0 else: - fail_count is fail_count + 1 + fail_count is fail_count + 1.0 print of ("FAIL: " + label) # ---------------------------------------------------------------- @@ -31,109 +31,109 @@ define check(args) as: # the entry and cumulative-iteration thresholds. # ---------------------------------------------------------------- define fused_regs(seed) as: - regs is [{"a": seed, "b": 0}, {"a": 0, "b": 0}] - k is 0 - loop while k < 80: - regs[0].a is regs[0].a + 1 - regs[1].b is regs[1].b + regs[0].a - regs[1].a is regs[0].a & 255 - k is k + 1 - return regs[1].b + regs[1].a + regs is [{"a": seed, "b": 0.0}, {"a": 0.0, "b": 0.0}] + k is 0.0 + loop while k < 80.0: + regs[0.0].a is regs[0.0].a + 1.0 + regs[1.0].b is regs[1.0].b + regs[0.0].a + regs[1.0].a is regs[0.0].a & 255.0 + k is k + 1.0 + return regs[1.0].b + regs[1.0].a # 80 calls x 80 iterations: the chunk's from-zero thunk stops at the # dict/list literals, so the fused ops only run natively via the loop's # OSR thunk — 6400 cumulative back-edges crosses the 5000 OSR threshold. -fr_total is 0 -n is 0 -loop while n < 80: - fr_total is fr_total + (fused_regs of (n % 4)) - n is n + 1 +fr_total is 0.0 +n is 0.0 +loop while n < 80.0: + fr_total is fr_total + (fused_regs of (n % 4.0)) + n is n + 1.0 # per call with seed s: regs[1].b = 80s + 3240, regs[1].a = s + 80, # so 81s + 3320; seeds cycle 0..3, 20 calls each: 20*(81*6 + 4*3320) -check of ["fused local[idx].field checksum", fr_total == 275320] +check of ["fused local[idx].field checksum", fr_total == 275320.0] # ---------------------------------------------------------------- # 2) Dict-dot get/set through the 2-way inline cache (bench_dmg_shape # op-handler shape: mutate fields of a dict parameter). # ---------------------------------------------------------------- define op_inc(c) as: - c.a is c.a + 1 - c.cycles is c.cycles + 4 - return 4 + c.a is c.a + 1.0 + c.cycles is c.cycles + 4.0 + return 4.0 define op_mix(c) as: - c.b is (c.b ^ c.a) & 255 - c.cycles is c.cycles + 8 - return 8 + c.b is (c.b ^ c.a) & 255.0 + c.cycles is c.cycles + 8.0 + return 8.0 -ctx is {"a": 0, "b": 0, "cycles": 0} -n is 0 +ctx is {"a": 0.0, "b": 0.0, "cycles": 0.0} +n is 0.0 unobserved: - loop while n < 600: + loop while n < 600.0: r1 is op_inc of ctx r2 is op_mix of ctx - n is n + 1 -check of ["dict-dot IC a", ctx.a == 600] -check of ["dict-dot IC cycles", ctx.cycles == 7200] -check of ["dict-dot IC b", ctx.b == 88] + n is n + 1.0 +check of ["dict-dot IC a", ctx.a == 600.0] +check of ["dict-dot IC cycles", ctx.cycles == 7200.0] +check of ["dict-dot IC b", ctx.b == 88.0] # Polymorphic dict shapes through the same hot accessor: the 2-way # inline cache must miss, exercising the jit_helper_dot_get/dot_set # out-of-line fallbacks with identical semantics. define poly_bump(c) as: - c.v is c.v + 1 + c.v is c.v + 1.0 return c.v -pa is {"v": 0} -pb is {"pad1": 0, "v": 100} -pc_d is {"pad1": 0, "pad2": 0, "v": 200} -pd is {"pad1": 0, "pad2": 0, "pad3": 0, "v": 300} -n is 0 +pa is {"v": 0.0} +pb is {"pad1": 0.0, "v": 100.0} +pc_d is {"pad1": 0.0, "pad2": 0.0, "v": 200.0} +pd is {"pad1": 0.0, "pad2": 0.0, "pad3": 0.0, "v": 300.0} +n is 0.0 unobserved: - loop while n < 200: + loop while n < 200.0: r1 is poly_bump of pa r2 is poly_bump of pb r3 is poly_bump of pc_d r4 is poly_bump of pd - n is n + 1 -check of ["poly dot IC a", pa.v == 200] -check of ["poly dot IC b", pb.v == 300] -check of ["poly dot IC c", pc_d.v == 400] -check of ["poly dot IC d", pd.v == 500] + n is n + 1.0 +check of ["poly dot IC a", pa.v == 200.0] +check of ["poly dot IC b", pb.v == 300.0] +check of ["poly dot IC c", pc_d.v == 400.0] +check of ["poly dot IC d", pd.v == 500.0] # Dot access on a *global* dict inside a hot fn: the target isn't a # resolvable local slot, so this compiles to plain OP_DOT_GET/DOT_SET — # in a thunk those are the jit_helper_dot_get/dot_set out-of-line calls. -gdict is {"v": 0, "tag": ""} +gdict is {"v": 0.0, "tag": ""} define bump_global(amount) as: - k is 0 - loop while k < 50: + k is 0.0 + loop while k < 50.0: gdict.v is gdict.v + amount - k is k + 1 + k is k + 1.0 return gdict.v -n is 0 -loop while n < 60: - r is bump_global of 1 - n is n + 1 -check of ["global dict dot in thunk", gdict.v == 3000] +n is 0.0 +loop while n < 60.0: + r is bump_global of 1.0 + n is n + 1.0 +check of ["global dict dot in thunk", gdict.v == 3000.0] # Non-num stores through local dot: the inline LOCAL_DOT_SET fast path # only handles in-place num writes, so a string store takes the # jit_helper_local_dot_set slow path every iteration. define tag_local(t) as: d is {"name": ""} - k is 0 - loop while k < 80: + k is 0.0 + loop while k < 80.0: d.name is t + "!" - k is k + 1 + k is k + 1.0 return d.name -n is 0 +n is 0.0 last_tag is "" -loop while n < 80: +loop while n < 80.0: last_tag is tag_local of "z" - n is n + 1 + n is n + 1.0 check of ["local dot string store slow path", last_tag == "z!"] # Num store into a dict slot currently holding a string: the inline @@ -141,20 +141,20 @@ check of ["local dot string store slow path", last_tag == "z!"] # exclusive num, so the first store of each call is a guard miss into # jit_helper_local_dot_set; subsequent iterations re-take the fast path. define renum(d2) as: - k is 0 - loop while k < 60: + k is 0.0 + loop while k < 60.0: d2.x is k - k is k + 1 + k is k + 1.0 return d2.x rd is {"x": "start"} -n is 0 -rlast is 0 -loop while n < 80: +n is 0.0 +rlast is 0.0 +loop while n < 80.0: rd.x is "again" rlast is renum of rd - n is n + 1 -check of ["local dot guard-miss store", rlast == 59] + n is n + 1.0 +check of ["local dot guard-miss store", rlast == 59.0] # #1665 arm D B10: dict values are EigsSlot storage, so the inline # LOCAL_DOT_GET probe pushes the field's slot bits. A 0 field is all-zero @@ -162,156 +162,156 @@ check of ["local dot guard-miss store", rlast == 59] # numbers and must take the helper, and a number store over a number field # is the inline bit store. define dot_mix(z) as: - d is {"zero": 0, "s": "str", "n": null, "t": true, "neg": -0.5} - k is 0 - acc is 0 + d is {"zero": 0.0, "s": "str", "n": null, "t": true, "neg": -0.5} + k is 0.0 + acc is 0.0 seen is "" - loop while k < 80: + loop while k < 80.0: acc is acc + d.zero + d.neg if d.t: seen is d.s if d.n == null: - acc is acc + 1 + acc is acc + 1.0 d.zero is d.zero + z - k is k + 1 + k is k + 1.0 return [acc, seen, d.zero] -n is 0 +n is 0.0 dm is null -loop while n < 80: - dm is dot_mix of 1 - n is n + 1 +loop while n < 80.0: + dm is dot_mix of 1.0 + n is n + 1.0 # acc = sum_{k<80} (k - 0.5 + 1) = 3160 + 40 -check of ["local dot slot reads (0 field, ref fields)", dm[0] == 3200 and dm[1] == "str" and dm[2] == 80] +check of ["local dot slot reads (0 field, ref fields)", dm[0.0] == 3200.0 and dm[1.0] == "str" and dm[2.0] == 80.0] # ---------------------------------------------------------------- # 3) Buffer + list index writes in a fn-local loop (bench_idxset # shape: INDEX_SET inline fast path), plus the helper slow path # via non-num stores into list slots. # ---------------------------------------------------------------- -buf is buffer of 256 -lst is [0, 0, 0, 0] +buf is buffer of 256.0 +lst is [0.0, 0.0, 0.0, 0.0] define idx_fill(base) as: - k is 0 - loop while k < 100: - buf[(base + k) % 256] is k - lst[k % 4] is k - k is k + 1 - return lst[3] - -n is 0 -acc is 0 -loop while n < 60: + k is 0.0 + loop while k < 100.0: + buf[(base + k) % 256.0] is k + lst[k % 4.0] is k + k is k + 1.0 + return lst[3.0] + +n is 0.0 +acc is 0.0 +loop while n < 60.0: acc is acc + (idx_fill of n) - n is n + 1 -check of ["idx fill list tail", acc == 60 * 99] + n is n + 1.0 +check of ["idx fill list tail", acc == 60.0 * 99.0] # Fractional stores through the hot inline INDEX_SET path must keep # the full double (the JIT used to round-trip through cvttsd2si, # truncating to int — divergent from buf_set and the double[] storage). -fb is buffer of 8 +fb is buffer of 8.0 define frac_fill(base) as: - k is 0 - loop while k < 80: - fb[k % 8] is base + (k % 8) + 0.5 - k is k + 1 - return fb[3] - -n is 0 -fsum is 0 -loop while n < 60: - fsum is fsum + (frac_fill of 0) - n is n + 1 -check of ["fractional store in thunk", fsum == 60 * 3.5] + k is 0.0 + loop while k < 80.0: + fb[k % 8.0] is base + (k % 8.0) + 0.5 + k is k + 1.0 + return fb[3.0] + +n is 0.0 +fsum is 0.0 +loop while n < 60.0: + fsum is fsum + (frac_fill of 0.0) + n is n + 1.0 +check of ["fractional store in thunk", fsum == 60.0 * 3.5] # Last call ran with base=59, so its final write was buf[(59+99)%256]=99. -check of ["idx fill buffer", buf[158] == 99] +check of ["idx fill buffer", buf[158.0] == 99.0] # Slow path: storing strings (non-num) into list slots in a hot fn. strs is ["", "", "", ""] define str_fill(tag) as: - k is 0 - loop while k < 60: - strs[k % 4] is tag + "!" - k is k + 1 - return strs[1] + k is 0.0 + loop while k < 60.0: + strs[k % 4.0] is tag + "!" + k is k + 1.0 + return strs[1.0] -n is 0 +n is 0.0 last is "" -loop while n < 60: +loop while n < 60.0: last is str_fill of "t" - n is n + 1 + n is n + 1.0 check of ["index_set non-num slow path", last == "t!"] # ---------------------------------------------------------------- # 4) for-in iteration in a hot function (jit_helper_iter_next), # over both a list and a buffer. # ---------------------------------------------------------------- -xs is [1, 2, 3, 4, 5, 6, 7, 8] -bxs is buffer of 8 -k is 0 -loop while k < 8: - bxs[k] is k * 2 - k is k + 1 +xs is [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] +bxs is buffer of 8.0 +k is 0.0 +loop while k < 8.0: + bxs[k] is k * 2.0 + k is k + 1.0 define sum_list(dummy) as: - s is 0 + s is 0.0 for v in xs: s is s + v return s define sum_buf(dummy) as: - s is 0 + s is 0.0 for v in bxs: s is s + v return s -n is 0 -sl is 0 -sb is 0 -loop while n < 80: +n is 0.0 +sl is 0.0 +sb is 0.0 +loop while n < 80.0: sl is sl + (sum_list of null) sb is sb + (sum_buf of null) - n is n + 1 -check of ["for-in list in thunk", sl == 80 * 36] -check of ["for-in buffer in thunk", sb == 80 * 56] + n is n + 1.0 +check of ["for-in list in thunk", sl == 80.0 * 36.0] +check of ["for-in buffer in thunk", sb == 80.0 * 56.0] # ---------------------------------------------------------------- # 5) Native VAL_FN call inside a thunk (hot fn calling another fn). # ---------------------------------------------------------------- define leaf(x) as: - return x * 2 + 1 + return x * 2.0 + 1.0 define trunk(x) as: - s is 0 - k is 0 - loop while k < 40: + s is 0.0 + k is 0.0 + loop while k < 40.0: s is s + (leaf of k) - k is k + 1 + k is k + 1.0 return s + x -n is 0 -nc is 0 -loop while n < 60: +n is 0.0 +nc is 0.0 +loop while n < 60.0: nc is nc + (trunk of n) - n is n + 1 + n is n + 1.0 # sum_{k=0..39}(2k+1) = 1600; plus n each call: 60*1600 + 1770 -check of ["native call in thunk", nc == 97770] +check of ["native call in thunk", nc == 97770.0] # ---------------------------------------------------------------- # 6) OSR: a single long-running loop (>5000 back-edges) inside one # call gets a per-loop OSR thunk mid-flight. # ---------------------------------------------------------------- define osr_loop(dummy) as: - s is 0 - k is 0 - loop while k < 7000: - s is s + (k & 7) - k is k + 1 + s is 0.0 + k is 0.0 + loop while k < 7000.0: + s is s + (k & 7.0) + k is k + 1.0 return s osr_r is osr_loop of null # 7000/8 = 875 full cycles of 0..7 (sum 28) -check of ["OSR long loop checksum", osr_r == 875 * 28] +check of ["OSR long loop checksum", osr_r == 875.0 * 28.0] # ---------------------------------------------------------------- # 7) Error semantics from hot code: an out-of-range index raised @@ -319,20 +319,20 @@ check of ["OSR long loop checksum", osr_r == 875 * 28] # with the interpreter's message/behavior. # ---------------------------------------------------------------- define maybe_oob(i) as: - small is [10, 20] + small is [10.0, 20.0] return small[i] -n is 0 -loop while n < 60: - r is maybe_oob of (n % 2) - n is n + 1 +n is 0.0 +loop while n < 60.0: + r is maybe_oob of (n % 2.0) + n is n + 1.0 -caught is 0 +caught is 0.0 try: - r is maybe_oob of 5 + r is maybe_oob of 5.0 catch e: - caught is 1 -check of ["hot fn OOB still raises catchably", caught == 1] + caught is 1.0 +check of ["hot fn OOB still raises catchably", caught == 1.0] # ---------------------------------------------------------------- # 8) SET_FN_NAME_LOCAL in hot code (jit_helper_set_fn_name_local): @@ -342,66 +342,61 @@ check of ["hot fn OOB still raises catchably", caught == 1] # and falls back to the helper. # ---------------------------------------------------------------- define obs_sum(n) as: - total is 0 - k is 0 - loop while k < 100: - total is total + (k & 3) - k is k + 1 + total is 0.0 + k is 0.0 + loop while k < 100.0: + total is total + (k & 3.0) + k is k + 1.0 return what is total define cap_sum(n) as: - total is 0 + total is 0.0 define get as: return total - k is 0 - loop while k < 100: - total is total + (k & 3) - k is k + 1 + k is 0.0 + loop while k < 100.0: + total is total + (k & 3.0) + k is k + 1.0 return get of null -fns1 is 0 -fns2 is 0 -m is 0 -loop while m < 70: +fns1 is 0.0 +fns2 is 0.0 +m is 0.0 +loop while m < 70.0: fns1 is fns1 + (obs_sum of m) fns2 is fns2 + (cap_sum of m) - m is m + 1 + m is m + 1.0 # per call: 25 cycles of 0+1+2+3 = 150; 70 calls = 10500 -check of ["interrogated-name hot assign", fns1 == 10500] -check of ["captured-name hot assign", fns2 == 10500] +check of ["interrogated-name hot assign", fns1 == 10500.0] +check of ["captured-name hot assign", fns2 == 10500.0] # ---------------------------------------------------------------- -# Bitwise operators in a hot fn-local loop. The interpreter bitwise -# paths are covered by test_bitwise.eigs, but the JIT *emitters* for -# OR/XOR/shift-left/shift-right/NOT (emit_or_rax_rdx, emit_xor_rdx_rax, -# emit_shl_cl_rdx, emit_sar_cl_rdx, emit_not_rax) only fire on hot code -# and were unhit by the suite — only AND (`& mask`) appeared above. This -# loop is hot enough to JIT-compile and exercises all six operators with -# zero bailouts; the checksum is identical interpreted vs JIT-compiled -# (verified with EIGS_JIT_OFF=1), so a miscompiled bitwise emitter fails -# here loudly. +# Bitwise operators in a hot fn-local loop. R1.3 deliberately refuses +# bitwise opcodes in the JIT scanner until the int fast-path round. +# These checks retain interpreter/fallback coverage while the other +# float-shaped sections continue to exercise the admitted JIT paths. # ---------------------------------------------------------------- define bitwise_mix(seed) as: a is seed - acc is 0 - k is 0 - loop while k < 200: - a is (a | 1) - a is (a ^ 3) - a is (a << 2) - a is (a >> 1) + acc is 0.0 + k is 0.0 + loop while k < 200.0: + a is (a | 1.0) + a is (a ^ 3.0) + a is (a << 2.0) + a is (a >> 1.0) a is (~a) - a is (a & 1023) + a is (a & 1023.0) acc is acc + a - k is k + 1 + k is k + 1.0 return acc -bw_total is 0 -b is 0 -loop while b < 80: +bw_total is 0.0 +b is 0.0 +loop while b < 80.0: bw_total is bw_total + (bitwise_mix of b) - b is b + 1 -check of ["bitwise emitters hot loop", bw_total == 5621472] + b is b + 1.0 +check of ["bitwise hot loop fallback", bw_total == 5621472.0] # ---------------------------------------------------------------- # 9) #908: the emitted assign_counts bump, inside `unobserved:`. @@ -413,12 +408,13 @@ check of ["bitwise emitters hot loop", bw_total == 5621472] # takes over, silently stops counting. Restoring the old emitted # sequence makes this check report 5001 instead of 20001. # ---------------------------------------------------------------- -hot908 is 0 +hot908 is 0.0 unobserved: - q908 is 0 - loop while q908 < 20000: + q908 is 0.0 + loop while q908 < 20000.0: hot908 is q908 - q908 is q908 + 1 + q908 is q908 + 1.0 +# S9-K1 (#1758): assignment counts are ints. check of ["unobserved assigns counted in JIT thunk", (when is hot908) == 20001] # ---------------------------------------------------------------- @@ -426,53 +422,53 @@ check of ["unobserved assigns counted in JIT thunk", (when is hot908) == 20001] # immediately. Continuing in the thunk would run the counter increment # before the interpreter gets a chance to dispatch to the catch handler. # ---------------------------------------------------------------- -null_read_side_effects is {"count": 0} +null_read_side_effects is {"count": 0.0} define null_field_bail(x) as: value is x.foo - null_read_side_effects.count is null_read_side_effects.count + 1 + null_read_side_effects.count is null_read_side_effects.count + 1.0 return value define null_index_bail(x) as: - value is x[0] - null_read_side_effects.count is null_read_side_effects.count + 1 + value is x[0.0] + null_read_side_effects.count is null_read_side_effects.count + 1.0 return value define null_index_field_bail(x) as: - value is x[0].foo - null_read_side_effects.count is null_read_side_effects.count + 1 + value is x[0.0].foo + null_read_side_effects.count is null_read_side_effects.count + 1.0 return value # Keep this short warmup's back-edge count separate from the module's # earlier OSR loops; the cache diagnostic displays one OSR slot per chunk. define warm_null_reads(dummy) as: - local warm_i is 0 - loop while warm_i < 60: - null_field_bail of {"foo": 1} - null_index_bail of [[1]] - null_index_field_bail of [[{"foo": 1}]] - warm_i is warm_i + 1 + local warm_i is 0.0 + loop while warm_i < 60.0: + null_field_bail of {"foo": 1.0} + null_index_bail of [[1.0]] + null_index_field_bail of [[{"foo": 1.0}]] + warm_i is warm_i + 1.0 warm_null_reads of null before_null_errors is null_read_side_effects.count -null_errors_caught is 0 +null_errors_caught is 0.0 try: null_field_bail of null catch e: - null_errors_caught is null_errors_caught + 1 + null_errors_caught is null_errors_caught + 1.0 try: null_index_bail of null catch e: - null_errors_caught is null_errors_caught + 1 + null_errors_caught is null_errors_caught + 1.0 try: null_index_field_bail of null catch e: - null_errors_caught is null_errors_caught + 1 -check of ["hot null local reads remain catchable", null_errors_caught == 3] + null_errors_caught is null_errors_caught + 1.0 +check of ["hot null local reads remain catchable", null_errors_caught == 3.0] check of ["hot null local reads bail before side effects", null_read_side_effects.count == before_null_errors] # ---------------------------------------------------------------- print of f"jit paths: {pass_count} passed, {fail_count} failed" -if fail_count == 0: +if fail_count == 0.0: print of "All tests passed" diff --git a/tests/test_jit_temporal_osr.eigs b/tests/test_jit_temporal_osr.eigs index 4901c4ca9..fb40af47d 100644 --- a/tests/test_jit_temporal_osr.eigs +++ b/tests/test_jit_temporal_osr.eigs @@ -5,12 +5,12 @@ # written there, not the value frozen at the point the OSR thunk took over. # Pre-fix the JIT skipped the stamp and these queries froze (e.g. 4999). load_file of "lib/test.eigs" -x is 0 -k is 0 -loop while k < 20000: +x is 0.0 +k is 0.0 +loop while k < 20000.0: x is k - k is k + 1 -assert_eq of [what is x at 11, 19999, "JIT temporal: at-line correct under OSR"] -sa is state_at of 11 -assert_eq of [sa["x"], 19999, "JIT temporal: state_at correct under OSR"] + k is k + 1.0 +assert_eq of [what is x at 11.0, 19999.0, "JIT temporal: at-line correct under OSR"] +sa is state_at of 11.0 +assert_eq of [sa["x"], 19999.0, "JIT temporal: state_at correct under OSR"] test_summary of null diff --git a/tests/test_json_hard.eigs b/tests/test_json_hard.eigs index 834068f92..2b33ddd30 100644 --- a/tests/test_json_hard.eigs +++ b/tests/test_json_hard.eigs @@ -410,14 +410,13 @@ assert of [jb_tiny == "{\"v\": 1e-09}", "JH94 json_build keeps tiny magnitude"] rt_enc is json_encode of {"k": 0.000000001} assert of [rt_enc == "{\"k\":1e-09}", "JH95 json_encode writes tiny magnitude"] assert of [json_path of [rt_enc, "k"] == "1e-09", "JH96 json_path round-trips what json_encode wrote"] -# #875: 1.23e15 is an exact integer below 2^53, so it now renders the way -# `str of` renders it — bare, not in exponent form. The two used to disagree, -# which is the drift this fix removed; the compactness #725 was protecting -# (no 22-char fixed-point blob) still holds at 16 characters. +# #875/N6: JSON and str share the Python-repr writer. The float 1.23e15 +# uses fixed notation with .0; float magnitudes at 1e16 and above use +# scientific notation. jb_huge is json_build of ["v", 1.23e15] -assert of [jb_huge == "{\"v\": 1230000000000000}", "JH97 json_build renders huge values compactly"] +assert of [jb_huge == "{\"v\": 1230000000000000.0}", "JH97 json_build renders huge values compactly"] assert of [jb_huge == ("{\"v\": " + (str of 1.23e15) + "}"), "JH97 json_build agrees with str of"] -# A magnitude that is NOT an exact integer still uses exponent form. +# A float above the fixed-notation range still uses exponent form. jb_huge2 is json_build of ["v", 1.23e300] assert of [jb_huge2 == "{\"v\": 1.23e+300}", "JH97 json_build keeps exponent form past 2^53"] diff --git a/tests/test_lint.sh b/tests/test_lint.sh index 7488b949a..466bdb77b 100644 --- a/tests/test_lint.sh +++ b/tests/test_lint.sh @@ -2356,6 +2356,7 @@ define f as: f of [] EIGS RUN=$($EIGS "$TMPFILE" 2>&1 || true) +# S9-K1 (#1758): assignment counts retain their values as exact ints. check_contains "#1048 module-level for-body local is fresh per iteration (when=1)" "$RUN" "^module when=1$" check_contains "#1048 function for-body local persists across iterations (when=30)" "$RUN" "^function when=30$" rm -f "$TMPFILE" @@ -2647,6 +2648,40 @@ check_contains "#1251 W019 names the interrogative's own line" "$OUTPUT" ":6: wa check_contains "#1251 W019 names 'prev of's own line" "$OUTPUT" ":8: warning\[W019\]: interrogative 'prev of" rm -f "$TMPFILE" +# #1758 N4/Q6: lint uses the same parser; retain diagnostics for the new tokens. +TMPFILE=$(mktemp /tmp/eigs_floor_lint_XXXXXX.eigs) +printf 'x is -7 // 2\nx //= 2\nprint of x\n' > "$TMPFILE" +FLOOR_LINT_RC=0 +OUTPUT=$("$EIGS" --lint "$TMPFILE" 2>&1) || FLOOR_LINT_RC=$? +check_status "// and //= lint clean" "$FLOOR_LINT_RC" 0 +printf 'x is // 2\n' > "$TMPFILE" +OUTPUT=$("$EIGS" --lint "$TMPFILE" 2>&1) || true +check_contains "unexpected // diagnostic names the token" "$OUTPUT" "unexpected '//'" +rm -f "$TMPFILE" + +# #1758 R1.8: the lint walkers treat both numeric kinds as literal leaves. +# Preserve clean numeric code and the existing W017 answer for each kind; +# there is no numeric type-inference rule to add or a type system to invent. +TMPFILE=$(mktemp /tmp/eigs_numeric_lint_XXXXXX.eigs) +cat > "$TMPFILE" << 'EIGS' +whole is 9007199254740993 +fraction is 1.5 +integral is 3.0 +outside is 9223372036854775808 +wrapped is 0xffffffffffffffff +print of [whole, fraction, integral, outside, wrapped] +EIGS +NUMERIC_LINT_RC=0 +OUTPUT=$("$EIGS" --lint "$TMPFILE" 2>&1) || NUMERIC_LINT_RC=$? +check_status "int and float literal leaves lint clean" "$NUMERIC_LINT_RC" 0 +check_contains "int and float literal leaves report no issues" "$OUTPUT" "no issues found" +for LITERAL in 9007199254740993 3.0 1.5 9223372036854775808 0xffffffffffffffff; do + printf 'print of [%s]\n' "$LITERAL" > "$TMPFILE" + OUTPUT=$("$EIGS" --lint "$TMPFILE" 2>&1) || true + check_contains "W017 retains its answer for $LITERAL" "$OUTPUT" "W017.*passes one argument" +done +rm -f "$TMPFILE" + # --- #1121 ledger: no child of this file may have tripped a sanitizer --- # One check, covering every linter invocation above rather than a sample. In a # release build no file is ever written and this passes trivially, which is diff --git a/tests/test_loop_cap_772.eigs b/tests/test_loop_cap_772.eigs index 6e1d3a2c7..28c6a5959 100644 --- a/tests/test_loop_cap_772.eigs +++ b/tests/test_loop_cap_772.eigs @@ -4,12 +4,13 @@ # in-frame iterations — wrong results with exit 0. Found by polymethod's # brute-force differential oracle (4 corrupted outputs in 524,288 at n=19). # This loop crosses the old boundary; the armed sandbox cap keeps its own -# coverage in test_vm_run_bytecode.eigs. -i is 0 +# coverage in test_vm_run_bytecode.eigs. R1.3 spells this legacy float +# probe explicitly; integer native-loop recovery is measured by phase2_cost. +i is 0.0 unobserved: - loop while i < 105000000: - i is i + 1 -if i == 105000000: + loop while i < 105000000.0: + i is i + 1.0 +if i == 105000000.0: print of "cap772: loop crossed 1e8 uncapped PASS" else: print of f"cap772 FAIL: loop stopped at {i}" diff --git a/tests/test_loop_continue_iterations_2003.eigs b/tests/test_loop_continue_iterations_2003.eigs index 10a5767bf..78b5443d1 100644 --- a/tests/test_loop_continue_iterations_2003.eigs +++ b/tests/test_loop_continue_iterations_2003.eigs @@ -16,7 +16,7 @@ print of ["only-continue", __loop_iterations__] # The observer stall guard must also be on continue's path. This loop has no # body fallthrough at all; without the shared iteration-end edge it never # reaches the guard and runs forever. -x is 100 +x is 100.0 loop while not converged: x is x * 2 continue diff --git a/tests/test_loop_counter_sequential.c b/tests/test_loop_counter_sequential.c index 70a95d664..5dc5b7a2a 100644 --- a/tests/test_loop_counter_sequential.c +++ b/tests/test_loop_counter_sequential.c @@ -4,6 +4,7 @@ */ #include "eigs_embed.h" #include "eigenscript.h" +#include "vm.h" #include #include @@ -40,18 +41,20 @@ static void step(Env *env, const char *label, int stall, * so sequential reads do not defeat the ordinary warm-cache path. * This getter returns a borrowed slot: do not decrement its ownership. */ EigsSlot value = env_get_hashed_slot(env, counter, 0, &found); - int numeric = slot_is_num(value) || - (slot_is_ptr(value) && slot_as_ptr(value)->type == VAL_NUM); - double actual = slot_is_num(value) ? SLOT_NUM_RAW(value) : - (numeric ? VAL_NUM_RAW(slot_as_ptr(value)) : -1); + /* S9-K1 (#1758) replaces the old float-only expectation: a count is + * an int, including boxed counts beyond the SMI range and 2^53. */ + int integer = slot_is_smi(value) || + (slot_is_ptr(value) && slot_as_ptr(value)->type == VAL_INT); + int64_t actual = slot_is_smi(value) ? slot_smi_val(value) : + (integer ? VAL_I64_RAW(slot_as_ptr(value)) : -1); int assignments = env_get_assign_count(env, counter, 0); - int ok = rc == 0 && !g_has_error && found && numeric && - actual == (double)expected_iterations && + int ok = rc == 0 && !g_has_error && found && integer && + actual == expected_iterations && g_loop_iterations == expected_iterations && assignments == expected_assignments && (!require_setter || calls == 1); - printf("ROW %s step=%s mt=%d shared=%d value=%.0f expected=%lld assignments=%d expected_assignments=%d rc=%d setter_calls=%u setter_required=%d\n", + printf("ROW %s step=%s mt=%d shared=%d value=%lld expected=%lld assignments=%d expected_assignments=%d rc=%d setter_calls=%u setter_required=%d\n", label, stall ? "stall" : "cap", g_vm_multithreaded, - env->mt_shared, actual, expected_iterations, assignments, + env->mt_shared, (long long)actual, expected_iterations, assignments, expected_assignments, rc, calls, require_setter); check(ok, label); } @@ -65,7 +68,7 @@ static void grow_env(Env *env, const char *label) { for (int i = 0; i <= before; ++i) { char name[64]; snprintf(name, sizeof(name), "sequential_growth_%d", i); - Value *value = make_num(i); + Value *value = make_float(i); env_set_local(env, name, value); val_decref(value); } @@ -97,6 +100,43 @@ static int exercise(Env *env, const char *label, long long *iterations) { return assignments; } +static void scheduler_metadata(void) { + /* The trace reader has no script builtin; inspect its real returned + * dicts after running an ordinary task, including fractional sleep. */ + g_task_trace_on = 1; + Value *result = (Value *)eigs_eval_string( + "define count_metadata_worker() as:\n" + " task_sleep of 0.5\n" + " return 7\n" + "task_join of (task_spawn of count_metadata_worker)\n"); + check(result && !g_has_error, "ordinary task produces scheduler metadata"); + if (result) val_decref(result); + Value *trace = task_sched_trace_read(); + int metadata_ok = trace && trace->type == VAL_LIST && trace->data.list.count > 0; + int saw_task = 0, saw_fraction = 0; + if (metadata_ok) { + for (int i = 0; i < trace->data.list.count; ++i) { + Value *row = list_get_ref(trace, i); + if (!row || row->type != VAL_DICT) { metadata_ok = 0; break; } + EIGS_VIEW(seq_view); EIGS_VIEW(task_view); EIGS_VIEW(tick_view); + Value *seq = dict_get_view(row, "seq", &seq_view); + Value *task = dict_get_view(row, "task", &task_view); + Value *tick = dict_get_view(row, "tick", &tick_view); + if (!seq || seq->type != VAL_INT || VAL_I64_RAW(seq) != i || + !task || task->type != VAL_INT || VAL_I64_RAW(task) < 0 || + !tick || tick->type != VAL_FLOAT) { + metadata_ok = 0; break; + } + saw_task |= VAL_I64_RAW(task) > 0; + saw_fraction |= VAL_F64_RAW(tick) == 0.5; + } + } + check(metadata_ok && saw_task, "scheduler sequence and task ids publish exact ints"); + check(metadata_ok && saw_fraction, "scheduler virtual time preserves fractional float ticks"); + if (trace) val_decref(trace); + g_task_trace_on = 0; +} + int main(void) { EigsState *state = eigs_open(); if (!state) return 2; @@ -133,7 +173,17 @@ int main(void) { "reattach resets thread counter and preserves restored state mode"); g_unobserved_depth = 1; step(g_global_env, "reattached-root", 0, 1, root_assignments + 1); + /* Exercise cached SMI publication, the first boxed count and a count + * binary64 cannot represent; no loop budget or convergence is involved. */ + g_loop_iterations = SLOT_SMI_MAX - 1; + step(g_global_env, "last-smi-count", 0, SLOT_SMI_MAX, root_assignments + 2); + step(g_global_env, "first-boxed-count", 1, SLOT_SMI_MAX + 1, root_assignments + 3); + step(g_global_env, "next-boxed-count", 0, SLOT_SMI_MAX + 2, root_assignments + 4); + g_loop_iterations = INT64_C(9007199254740992); + step(g_global_env, "exact-count-above-2pow53", 0, + INT64_C(9007199254740993), root_assignments + 5); g_unobserved_depth = saved_unobserved; + scheduler_metadata(); /* Dynamic chunks share the root environment but must not reserve a hidden * binding for each loop. Exercise compilation above MAX_LOCALS and verify diff --git a/tests/test_loop_exit.eigs b/tests/test_loop_exit.eigs index e2e6ec220..4b97560ad 100644 --- a/tests/test_loop_exit.eigs +++ b/tests/test_loop_exit.eigs @@ -5,9 +5,9 @@ print of "=== LOOP EXIT TEST ===" # converged). The constant loop in LE2 is the one that exits normally now. print of "--- LE1: Runaway exits via stall ---" x is 1 -x is 100 +x is 100.0 loop while not converged: - x is x * 2 + x is x * 2.0 print of __loop_exit__ print of __loop_iterations__ diff --git a/tests/test_loop_iterations_per_loop.eigs b/tests/test_loop_iterations_per_loop.eigs index e07dade20..c01d0eea9 100644 --- a/tests/test_loop_iterations_per_loop.eigs +++ b/tests/test_loop_iterations_per_loop.eigs @@ -58,17 +58,17 @@ print of ["return", __loop_iterations__] exit_o is 0 loop while exit_o < 1: exit_o is exit_o + 1 - stall_x is 100 + stall_x is 100.0 loop while not converged: stall_x is stall_x * 2 stalled_reason is __loop_exit__ - limit_code is [97, 63,3,0, 30,6,0, 25,0,0, 40] + limit_code is [101, 63,3,0, 30,6,0, 25,0,0, 40] limit_r is sandbox_run of [[1, limit_code, ["__loop_iterations__"]], 5] print of ["stall-limit", __loop_iterations__, stalled_reason, limit_r.ok, limit_r.result] # Two three-iteration loops under a five-iteration sandbox budget. Neither # loop exceeds five alone; together they trip it on inner count 2 (total 5). -budget_code is [2,27,0,0,35,97,25,0,0,0,3,0,19,31,15,0,25,0,0,3,4,26,0,0,35,63,3,0,30,25,0,2,27,1,0,35,97,25,1,0,0,3,0,19,31,15,0,25,1,0,3,4,26,1,0,35,63,3,0,30,25,0,25,2,0,40] +budget_code is [2,27,0,0,35,101,25,0,0,0,3,0,19,31,15,0,25,0,0,3,4,26,0,0,35,63,3,0,30,25,0,2,27,1,0,35,101,25,1,0,0,3,0,19,31,15,0,25,1,0,3,4,26,1,0,35,63,3,0,30,25,0,25,2,0,40] budget_r is sandbox_run of [[1, budget_code, ["budget_a", "budget_b", "__loop_iterations__", 3]], 5] print of ["budget", budget_r.ok, budget_r.result, diff --git a/tests/test_lsp.py b/tests/test_lsp.py index 353bb3b5d..6e4eea8ce 100755 --- a/tests/test_lsp.py +++ b/tests/test_lsp.py @@ -259,6 +259,10 @@ def main(): r = converse([INIT, did_open("x is 5\nprint of x\n"), SHUTDOWN, EXIT]) check("clean document → empty diagnostics", diagnostics(r) == []) + # #1758 N4/Q6: shared tokenizer/parser/compiler accept both new spellings. + r = converse([INIT, did_open("x is -7 // 2\nx //= 2\nprint of x\n"), SHUTDOWN, EXIT]) + check("floor division and compound assignment have clean diagnostics", diagnostics(r) == []) + # #1366: these are capacity/stack invariants rather than observable LSP # semantics at today's 16-parameter parser cap. Pin the shared bound and # heap allocation so neither can silently regress while behavior stays green. @@ -566,6 +570,38 @@ def nest_try(ind, k): check("hover returns contents", isinstance(res, dict) and "contents" in res) + # #1758 R1.8: the lexer already records the literal kind exactly. Hover + # must report that kind, including integer-looking floats and decimal + # overflow, rather than guess from a double or the displayed digits. + numeric_literals = [ + ("0", "int"), + ("42", "int"), + ("9007199254740993", "int"), + ("9223372036854775807", "int"), + ("0xffffffffffffffff", "int"), + ("0x10000000000000000", "int"), + ("0.0", "float"), + ("3.0", "float"), + ("1.5", "float"), + ("1e3", "float"), + (".5", "float"), + ("9223372036854775808", "float"), + ] + numeric_doc = "".join("print of " + literal + "\n" + for literal, _ in numeric_literals) + numeric_requests = [ + {"jsonrpc": "2.0", "id": 8000 + i, "method": "textDocument/hover", + "params": {"textDocument": {"uri": URI}, + "position": {"line": i, "character": 9}}} + for i in range(len(numeric_literals)) + ] + r = converse([INIT, did_open(numeric_doc)] + numeric_requests + [SHUTDOWN, EXIT]) + for i, (literal, kind) in enumerate(numeric_literals): + result = (by_id(r, 8000 + i) or {}).get("result") or {} + check("numeric literal hover: " + literal + " is " + kind, + result.get("contents", {}).get("value") == + "```eigenscript\\n" + kind + "\\n```") + # --- definition of a used symbol returns a location --- define_doc = "define helper(a) as:\n return a\nr is helper of 5\n" defn = {"jsonrpc": "2.0", "id": 4, "method": "textDocument/definition", diff --git a/tests/test_n9_contract.eigs b/tests/test_n9_contract.eigs new file mode 100644 index 000000000..bf7fae9ab --- /dev/null +++ b/tests/test_n9_contract.eigs @@ -0,0 +1,17 @@ +# #1758 N9: supplementary contract witnesses, pre-registered from Rust. +load_file of "lib/test.eigs" +rounded is fround of 4611686293305294849 +assert_eq of [rounded, 4611686018427387904, "int converts to double before float32"] +assert_eq of [num_type of rounded, "float", "fround result kind"] +assert_eq of [fround of [0.1], fround of 0.1, "unary one-element unwrap"] +assert_eq of [u64_to_string of [-1], u64_to_string of -1, "unsigned unary unwrap"] +for operation in [u64_div, u64_rem]: + refused is false + try: + operation of [9007199254740993, 0] + catch e: + assert_eq of [e.kind, "value", "unsigned zero divisor kind"] + assert_eq of [e.message, "division by zero", "unsigned zero divisor exact message"] + refused is true + assert_eq of [refused, true, "zero divisor raises"] +test_summary of null diff --git a/tests/test_named_loop_verifier_1977.eigs b/tests/test_named_loop_verifier_1977.eigs index eabf24d11..c2be7d129 100644 --- a/tests/test_named_loop_verifier_1977.eigs +++ b/tests/test_named_loop_verifier_1977.eigs @@ -1,5 +1,5 @@ # #1977: a sandbox descriptor that abuses a private-loop-counter opcode must not -# corrupt the HOST frame. OP_LOOP_EXIT_NAMED (101) drains every operand-stack +# corrupt the HOST frame. OP_LOOP_EXIT_NAMED (105) drains every operand-stack # slot above its depth operand; the verifier modelled it as a fixed one-pop, so # a depth-0 EXIT_NAMED verified clean and then drained past the frame. Standalone # it double-freed the host's value (SIGABRT); nested inside a host `loop while` @@ -7,7 +7,7 @@ # "loop counter depth 0 out of range". The verifier now refuses the opcode in # assembled bytecode. This runs under all three execution tiers because the host # loop (whose counter is the target) is JIT/OSR-compilable. -bad is [1, [2, 2, 101, 0, 0, 35, 41], []] +bad is [1, [2, 2, 105, 0, 0, 35, 41], []] # Standalone: refused, ok=false, no crash. r is sandbox_run of [bad, 5] diff --git a/tests/test_native_fn.c b/tests/test_native_fn.c index fb81d8370..3f2fa81cc 100644 --- a/tests/test_native_fn.c +++ b/tests/test_native_fn.c @@ -15,10 +15,10 @@ static void check(int ok, const char *what) { if (!ok) fail = 1; } static Value *probe_add1(Value *a) { - double d = (a && a->type == VAL_NUM) ? VAL_NUM_RAW(a) : 0.0; - return make_num(d + 1); + double d = (a && val_is_number(a)) ? eigs_float_arg(a, __func__) : 0.0; + return make_float(d + 1); } -static Value *probe_plain(Value *a) { (void)a; return make_num(0); } +static Value *probe_plain(Value *a) { (void)a; return make_float(0); } static char *eval_str(const char *src) { Value *v = eigs_eval_string(src); @@ -39,7 +39,7 @@ int main(void) { s = eval_str("str of g"); check(strcmp(s, "") == 0, "str of a named native fn is "); free(s); s = eval_str("g of 41"); - check(strcmp(s, "42") == 0, "a named native fn still calls through (g of 41 = 42)"); free(s); + check(strcmp(s, "42.0") == 0, "a named native fn still calls through (g of 41 = 42.0)"); free(s); s = eval_str("type of b"); check(strcmp(s, "builtin") == 0, "a plain builtin still reports \"builtin\""); free(s); s = eval_str("str of b"); diff --git a/tests/test_num_guard_value.c b/tests/test_num_guard_value.c index 08931c363..961c889c9 100644 --- a/tests/test_num_guard_value.c +++ b/tests/test_num_guard_value.c @@ -37,7 +37,7 @@ int main(void) { g_strict = 0; check("default NaN", NAN); check("negative NaN", -NAN); - check("SLOT_NULL", SLOT_NUM_RAW(slot_null())); + check("SLOT_NULL", SLOT_F64_RAW(slot_null())); check("positive infinity", INFINITY); check("negative infinity", -INFINITY); check("positive maximum", EIGS_NUM_MAX); diff --git a/tests/test_numeric_guard.eigs b/tests/test_numeric_guard.eigs index 3fac6210c..a9058888b 100644 --- a/tests/test_numeric_guard.eigs +++ b/tests/test_numeric_guard.eigs @@ -3,6 +3,16 @@ CAP is 1e308 +# N13 / R1.6d: final data/byte/index readers refuse raw nonfinite cells. +define ng_final_refuses(operation, argument, label) as: + local refused is false + try: + operation of argument + catch e: + refused is e.kind == "value" + assert of [refused, label] + + # Sticky math_flags coverage runs in fresh processes via test_numeric_guard_flags.sh. # Values themselves are not sticky and belong in this shared program. @@ -58,11 +68,14 @@ assert of [tensor_tiny_div == CAP, "NG10 tensor near-zero division caps"] literal_inf is 1e309 assert of [literal_inf == CAP, "NG11 overflowing literal caps"] -string_nan is num of "nan" -assert of [string_nan == 0, "NG12 string nan collapses to 0"] - -string_inf is num of "inf" -assert of [string_inf == CAP, "NG13 string inf caps"] +# N10: these are invalid grammar in both strict modes, not float sources. +for invalid_text in ["nan", "inf"]: + refused is false + try: + num of invalid_text + catch e: + refused is e.kind == "value" + assert of [refused, "N10 non-literal numeric text raises"] neg_mul_inf is -1e308 * 2 assert of [neg_mul_inf == (0 - CAP), "NG14 negative overflow caps"] @@ -88,8 +101,8 @@ assert of [unobserved_inf == CAP, "NG19 unobserved reassignment literal caps"] # #1417: tensor kernels may hold an intermediate IEEE infinity in their -# unboxed result buffer. Every language-level read must expose the numeric -# ceiling, independent of which opcode consumes the scalar. +# unboxed result buffer. Every language-level read must use the finite +# data boundary, independent of which opcode consumes the scalar. raw_inf_input is buffer of 4 raw_inf_input[0] is 1e300 raw_inf_input[1] is 1e300 @@ -97,18 +110,29 @@ raw_inf_input[2] is 1e300 raw_inf_input[3] is 1e300 raw_inf_matrix is reshape of [raw_inf_input, 2, 2] raw_inf_product is matmul of [raw_inf_matrix, raw_inf_matrix] -assert of [not (raw_inf_product[0] > CAP), "NG31 comparison reads a clamped buffer infinity"] -assert of [(raw_inf_product[0] / raw_inf_product[1]) == 1, "NG31 division reads clamped buffer infinities"] -assert of [(raw_inf_product[0] % 3) == 2, "NG31 modulo reads a clamped buffer infinity"] -assert of [not (raw_inf_product[0] * 0.5 > CAP), "NG31 multiplication reads a clamped buffer infinity"] -assert of [not (raw_inf_product[0] + 0 > CAP), "NG31 addition reads a clamped buffer infinity"] -raw_inf_scalar is raw_inf_product[0] -assert of [raw_inf_scalar == CAP, "NG31 index read into a scalar is clamped"] -assert of [(buf_get of [raw_inf_product, 0]) == CAP, "NG31 buf_get is clamped"] - -# A NaN uses the same read boundary as infinity. In non-strict mode every -# accessor returns numeric 0 and sets invalid; none may leak the NaN-box null -# tag merely because it bypasses make_num. +define ng_read_cmp(b) as: + return b[0] > CAP +define ng_read_div(b) as: + return b[0] / b[1] +define ng_read_mod(b) as: + return b[0] % 3 +define ng_read_mul(b) as: + return b[0] * 0.5 > CAP +define ng_read_add(b) as: + return b[0] + 0 > CAP +define ng_read_zero(b) as: + return b[0] +ng_final_refuses of [ng_read_cmp, raw_inf_product, "NG31 N13 indexed comparison refuses infinity"] +ng_final_refuses of [ng_read_div, raw_inf_product, "NG31 N13 indexed division refuses infinity"] +ng_final_refuses of [ng_read_mod, raw_inf_product, "NG31 N13 indexed modulo refuses infinity"] +ng_final_refuses of [ng_read_mul, raw_inf_product, "NG31 N13 indexed multiply refuses infinity"] +ng_final_refuses of [ng_read_add, raw_inf_product, "NG31 N13 indexed addition refuses infinity"] +ng_final_refuses of [ng_read_zero, raw_inf_product, "NG31 N13 index refuses infinity"] +ng_final_refuses of [buf_get, [raw_inf_product, 0], "NG31 N13 buf_get refuses infinity"] + +# A NaN uses the same refusing data boundary as infinity. Every +# accessor refuses nonfinite data; none may leak the NaN-box null +# tag merely because it bypasses make_float. nan_left is buffer of [1, 2] nan_left[0] is 1e200 nan_left[1] is 1e200 @@ -116,13 +140,19 @@ nan_right is buffer of [2, 1] nan_right[0] is 1e200 nan_right[1] is 0 - 1e200 raw_nan_product is matmul of [nan_left, nan_right] -assert of [raw_nan_product[0] == 0, "NG32 indexed NaN read collapses to zero"] -assert of [(buf_get of [raw_nan_product, 0]) == 0, "NG33 buf_get NaN read collapses to zero"] -assert of [(get_at of [raw_nan_product, 0]) == 0, "NG34 get_at NaN read collapses to zero"] +ng_final_refuses of [ng_read_zero, raw_nan_product, "NG32 N13 indexed NaN refuses"] +ng_final_refuses of [buf_get, [raw_nan_product, 0], "NG33 N13 buf_get refuses NaN"] +# N13 / R1.6c: get_at now uses the final finite-only buffer reader. +ng_get_at_refused is false +try: + get_at of [raw_nan_product, 0] +catch e: + ng_get_at_refused is e.kind == "value" +assert of [ng_get_at_refused, "NG34 N13 get_at refuses non-finite payload"] nan_gathered is gather of [raw_nan_product, [0]] -assert of [nan_gathered[0] == 0, "NG35 gather NaN read collapses to zero"] +ng_final_refuses of [ng_read_zero, nan_gathered, "NG35 N13 gathered NaN refuses on read"] -# #1417: structural comparisons and scalar reductions read normalized inputs. +# R1.6z-D1: structural comparisons use stored IEEE doubles; reductions use their declared data readers. pair_left is buffer of [1, 1] pair_left[0] is 1e200 pair_right is buffer of [1, 2] @@ -132,18 +162,34 @@ raw_pair is matmul of [pair_left, pair_right] finite_pair is buffer of 2 finite_pair[0] is CAP finite_pair[1] is 0 - CAP -assert of [raw_pair == finite_pair, "NG36 structural equality normalizes infinities"] -assert of [(list_contains of [[raw_pair], finite_pair]), "NG36 list_contains shares normalized equality"] -assert of [(list_index_of of [[raw_pair], finite_pair]) == 0, "NG36 list_index_of shares normalized equality"] -assert of [(sum of raw_pair) == 0, "NG37 sum normalizes before cancellation"] -assert of [(mean of raw_pair) == 0, "NG37 mean normalizes before cancellation"] -assert of [raw_nan_product == raw_nan_product, "NG38 same-buffer equality normalizes NaN"] +assert of [raw_pair != finite_pair, "NG36 IEEE equality distinguishes infinity from finite"] +assert of [not (list_contains of [[raw_pair], finite_pair]), "NG36 list_contains shares IEEE equality"] +assert of [(list_index_of of [[raw_pair], finite_pair]) == -1, "NG36 list_index_of shares IEEE equality"] +# N13 / R1.6b: sum now crosses the final float-reader boundary, which +# refuses non-finite buffer payloads in both strict modes. Other reductions +# keep their earlier normalization policy until their own reader migration. +define ng_sum_refuses_nonfinite(tensor_input, label) as: + local refused is false + try: + sum of tensor_input + catch e: + refused is e.kind == "value" + assert of [refused, label] +ng_sum_refuses_nonfinite of [raw_pair, "NG37 N13 sum refuses non-finite elements before cancellation"] +ng_final_refuses of [mean, raw_pair, "NG37 N13 mean refuses non-finite input"] +assert of [raw_nan_product != raw_nan_product, "NG38 IEEE NaN buffer differs from itself"] zero_one is buffer of 1 -assert of [raw_nan_product == zero_one, "NG39 distinct-buffer equality normalizes NaN"] +assert of [raw_nan_product != zero_one, "NG39 IEEE NaN differs from zero"] zero_pair is buffer of 2 -assert of [(dot of [raw_pair, zero_pair]) == 0, "NG40 dot normalizes before multiplying by zero"] -assert of [(buf_dot of [raw_pair, zero_pair, 0, 0, 2]) == 0, "NG41 buf_dot normalizes each operand"] -assert of [(buf_peak of [raw_nan_product, 0, 1]) == 0, "NG42 buf_peak reads NaN as zero"] +# N13 / R1.6c: dot also refuses instead of substituting finite operands. +ng_dot_refused is false +try: + dot of [raw_pair, zero_pair] +catch e: + ng_dot_refused is e.kind == "value" +assert of [ng_dot_refused, "NG40 N13 dot refuses non-finite payload"] +ng_final_refuses of [buf_dot, [raw_pair, zero_pair, 0, 0, 2], "NG41 N13 buf_dot refuses infinity"] +ng_final_refuses of [buf_peak, [raw_nan_product, 0, 1], "NG42 N13 buf_peak refuses NaN"] # Ordinary finite/empty controls preserve the pre-existing reduction policies. finite is buffer of 2 @@ -164,6 +210,10 @@ empty is buffer of 0 assert of [(sum of empty) == 0, "NG45 empty sum"] assert of [(mean of empty) == 0, "NG45 empty mean"] assert of [(norm of empty) == 0, "NG45 empty norm"] +# N3/C2: empty sum becomes int, while a wrong-type soft stand-in keeps its +# original float kind. This fixture runs with EIGS_STRICT=0. +sum_soft_wrong_type is sum of "hello" +assert of [sum_soft_wrong_type == 0 and (num_type of sum_soft_wrong_type) == "float", "NG45 N3 sum preserves the legacy float-zero type fallback"] short is buffer of 1 short[0] is 2 assert of [(dot of [finite, short]) == 6, "NG46 dot retains shorter-length policy"] @@ -172,13 +222,13 @@ assert of [(buf_peak of [finite, 1, 1]) == 4, "NG47 peak keeps its window"] assert of [(buf_dot of [finite, finite_copy, 2, 2, 0]) == 0, "NG48 empty dot window"] assert of [(buf_peak of [finite, 2, 0]) == 0, "NG48 empty peak window"] -# A containing list must retain the scalar buffer-read normalization rule. -assert of [(sum of ([raw_pair])) == 0, "NG49 nested sum normalizes before cancellation"] -assert of [(mean of ([raw_pair])) == 0, "NG49 nested mean normalizes before cancellation"] -assert of [(norm of ([raw_pair])) == (norm of raw_pair), "NG49 nested norm uses normalized inputs"] -assert of [(sum of ([raw_nan_product])) == 0, "NG50 nested NaN sum is zero"] -assert of [(mean of ([raw_nan_product])) == 0, "NG50 nested NaN mean is zero"] -assert of [(norm of ([raw_nan_product])) == 0, "NG50 nested NaN norm is zero"] +# A containing list follows its reduction's reader policy (N13 for sum). +ng_sum_refuses_nonfinite of [([raw_pair]), "NG49 N13 nested sum refuses non-finite elements"] +ng_final_refuses of [mean, ([raw_pair]), "NG49 N13 nested mean refuses infinity"] +ng_final_refuses of [norm, ([raw_pair]), "NG49 N13 nested norm refuses infinity"] +ng_sum_refuses_nonfinite of [([raw_nan_product]), "NG50 N13 nested sum refuses NaN"] +ng_final_refuses of [mean, ([raw_nan_product]), "NG50 N13 nested mean refuses NaN"] +ng_final_refuses of [norm, ([raw_nan_product]), "NG50 N13 nested norm refuses NaN"] assert of [(sum of ([finite])) == 7, "NG51 nested finite sum"] assert of [(mean of ([finite])) == 3.5, "NG51 nested finite mean"] assert of [(norm of ([finite])) == 5, "NG51 nested finite norm"] @@ -186,29 +236,39 @@ assert of [(sum of ([empty])) == 0, "NG51 nested empty sum"] assert of [(mean of ([empty])) == 0, "NG51 nested empty mean"] assert of [(norm of ([empty])) == 0, "NG51 nested empty norm"] -# Non-strict NaN indices still select zero after normalization. +# N5 indices refuse nonfinite values in both strict modes. index_dst is buffer of 1 index_dst[0] is 10 -scatter_add of [index_dst, raw_nan_product, 5] -assert of [index_dst[0] == 15, "NG52 non-strict scatter NaN index selects zero"] +ng_final_refuses of [scatter_add, [index_dst, raw_nan_product, 5], "NG52 N5 nonfinite scatter index refuses"] +assert of [index_dst[0] == 10, "NG52 refusal leaves destination unchanged"] index_matrix is buffer of [1, 1] index_matrix[0] is 7 -index_selected is gather of [index_matrix, raw_nan_product] -assert of [index_selected[0] == 7, "NG53 non-strict gather NaN index selects zero"] +ng_final_refuses of [gather, [index_matrix, raw_nan_product], "NG53 N5 nonfinite gather index refuses"] -# Numeric byte decoders normalize before converting, preserving finite byte -# truncation/modulo behavior and the buffer's raw work-area representation. -assert of [(str_from_bytes of (buf_from_list of [65.75, 321, -191])) == "AAA", "NG54 finite byte truncation and wrapping"] +# Numeric byte decoders enforce N5/Q1 before converting; buffers retain +# their raw work-area representation until the decoder reads a cell. +ng_final_refuses of [str_from_bytes, buf_from_list of [65.75, 321, -191], "NG54 N5/Q1 refuses fractional byte"] assert of [(f64_from_bytes of (buf_from_list of (f64_to_bytes of 12.5))) == 12.5, "NG54 finite f64 byte round trip"] -assert of [(str_from_bytes of raw_nan_product) == "", "NG55 non-strict NaN byte is NUL"] -raw_nan_bytes is buffer of 8 -raw_nan_bytes[0] is raw_nan_product[0] -assert of [(f64_from_bytes of raw_nan_bytes) == 0, "NG56 non-strict NaN byte decodes zero"] -assert of [(str_from_bytes of raw_pair) == "", "NG57 normalized infinity wraps to byte zero"] -encoded_nan is buf_to_pcm16le of [raw_nan_product, 0, 1] -assert of [encoded_nan == (buf_from_list of [0, 0]), "NG58 non-strict NaN PCM sample is silence"] -encoded_infinity is buf_to_pcm16le of [raw_pair, 0, 2] -assert of [encoded_infinity == (buf_from_list of [255, 127, 1, 128]), "NG59 normalized PCM infinities clamp to samples"] +ng_final_refuses of [str_from_bytes, raw_nan_product, "NG55 N13 refuses nonfinite buffer input"] +# #1926: keep eight NaNs in buffer storage; indexing would normalize first. +ng56_left is buffer of [8, 2] +for i in range of 16: + ng56_left[i] is 1e200 +ng56_right is buffer of [2, 1] +ng56_right[0] is 1e200 +ng56_right[1] is 0 - 1e200 +ng56_raw is matmul of [ng56_left, ng56_right] +ng56_refused is false +ng56_length_error is false +try: + f64_from_bytes of ng56_raw +catch e: + ng56_refused is e.kind == "value" + ng56_length_error is (index_of of [e.message, "needs 8 bytes"]) >= 0 +assert of [ng56_refused and not ng56_length_error, "NG56 N13 refuses nonfinite buffer input, not length"] +ng_final_refuses of [str_from_bytes, raw_pair, "NG57 N13 refuses nonfinite buffer input"] +ng_final_refuses of [buf_to_pcm16le, [raw_nan_product, 0, 1], "NG58 N13 refuses nonfinite buffer input"] +ng_final_refuses of [buf_to_pcm16le, [raw_pair, 0, 2], "NG59 N13 refuses nonfinite buffer input"] # Mixed buffer/list results box each buffer element before arithmetic. # Shaped buffers keep their matrix rows when boxed into nested lists. diff --git a/tests/test_numeric_guard_flags.sh b/tests/test_numeric_guard_flags.sh index 18e2cf3bb..6ac5b912f 100755 --- a/tests/test_numeric_guard_flags.sh +++ b/tests/test_numeric_guard_flags.sh @@ -24,12 +24,15 @@ run "NG24 other association clean" 'x is 1e200 * (1e200 / 1e200) assert of [not (math_flags of null).overflow, "other order clean"]' run "NG26 capped literal stays valid" 'x is 0.0 * 1e400 assert of [x == 0 and not (math_flags of null).invalid, "capped literal is valid"]' -run "NG30 num nan" 'x is num of "nan" -assert of [x == 0 and (math_flags of null).invalid, "num nan invalid"]' -run "NG30 num inf" 'x is num of "inf" -assert of [(math_flags of null).overflow, "num inf overflow"]' -run "NG30 num abc" 'x is num of "abc" -assert of [x == 0 and not (math_flags of null).invalid, "junk clean"]' +# N10: grammar refusal is unconditional and does not touch arithmetic flags. +for invalid in nan inf abc; do +run "NG30 N10 num $invalid refused" "caught is false +try: + num of \"$invalid\" +catch e: + caught is e.kind == \"value\" +assert of [caught and not ((math_flags of null).invalid or (math_flags of null).overflow), \"N10 refusal leaves flags clean\"]" +done run "NG30 num 42" 'x is num of "42" assert of [x == 42 and not ((math_flags of null).invalid or (math_flags of null).overflow), "42 clean"]' run "NG27 log controls/zero" 'a is log of 1e-15 @@ -58,30 +61,72 @@ right is buffer of [2, 1] right[0] is 1e200 right[1] is 0 - 1e200 raw is matmul of [left, right]' +# R1.6d-D1 point 3 / N13: indexed data reads refuse lenient NaN. +# NG35's gather copies the raw cell; the following [0] is the scalar read. for spec in \ 'NG32 index|x is raw[0]' \ - 'NG33 buf_get|x is buf_get of [raw, 0]' \ - 'NG34 get_at|x is get_at of [raw, 0]' \ - 'NG35 gather|x is (gather of [raw, [0]])[0]' \ + 'NG35 gathered index|x is (gather of [raw, [0]])[0]'; do + name=${spec%%|*}; op=${spec#*|} + run "$name N13 refusal" "$nan_setup +before is math_flags of null +caught is false +try: + $op +catch e: + caught is e.kind == \"value\" +assert of [caught and ((math_flags of null) == before), \"R1.6d-D1 indexed NaN refuses without flag writes\"]" +done +# R1.6z-D1 point 1 / N11: IEEE equality never writes arithmetic flags. +for spec in \ 'NG38 equality|x is raw == raw' \ - 'NG39 distinct equality|zero is buffer of 1; x is raw == zero' \ + 'NG39 distinct equality|zero is buffer of 1; x is raw == zero'; do + name=${spec%%|*}; op=${spec#*|}; op=$(printf '%s' "$op" | sed 's/; */\n/g') + run "$name N11 IEEE flags unchanged" "$nan_setup +before is math_flags of null +$op +assert of [not x and ((math_flags of null) == before), \"R1.6z-D1 IEEE NaN equality is false without flag writes\"]" +done +# N13 / R1.6d: these raw NaN reads refuse in lenient mode. +for spec in \ + 'NG33 buf_get|x is buf_get of [raw, 0]' \ 'NG42 buf_peak|x is buf_peak of [raw, 0, 1]' \ - 'NG50 nested sum|x is sum of ([raw])' \ 'NG50 nested mean|x is mean of ([raw])' \ 'NG50 nested norm|x is norm of ([raw])' \ 'NG52 scatter index|dst is buffer of 1; scatter_add of [dst, raw, 5]' \ 'NG53 gather index|dst is buffer of [1, 1]; x is gather of [dst, raw]' \ 'NG55 str_from_bytes|x is str_from_bytes of raw' \ 'NG58 PCM encode|x is buf_to_pcm16le of [raw, 0, 1]'; do - name=${spec%%|*}; op=${spec#*|}; op=$(printf '%s' "$op" | sed 's/; */\n/g') - run "$name invalid" "$nan_setup + name=${spec%%|*}; op=${spec#*|}; op=$(printf '%s' "$op" | sed 's/; */\n /g; s/^/ /') + run "$name N13 refusal" "$nan_setup +caught is false +try: $op -assert of [(math_flags of null).invalid, \"NaN read sets invalid\"]" +catch e: + caught is e.kind == \"value\" +assert of [caught and not (math_flags of null).invalid, \"N13 refuses without normalizing NaN\"]" done + +# N13 / R1.6c: get_at's final reader refuses NaN in both strict modes. +run "NG34 N13 get_at refuses NaN" "$nan_setup +caught is false +try: + get_at of [raw, 0] +catch e: + caught is e.kind == \"value\" +assert of [caught, \"N13 get_at refuses non-finite payload\"]" +# N13 / R1.6b: sum's final buffer reader refuses NaN in every strict mode. +# Keep this in its own program, as for the neighboring flag witnesses. +run "NG50 N13 nested sum refuses NaN" "$nan_setup +caught is false +try: + sum of ([raw]) +catch e: + caught is e.kind == \"value\" +assert of [caught, \"N13 sum refuses non-finite payload instead of substituting zero\"]" # NG56 needs 8 stored bytes since #1926 refuses a short input. Keep the NaN -# in buffer storage so the decoder's own read sets the flag: an index read -# first (raw[0]) would set it before f64_from_bytes ever ran. -run "NG56 f64_from_bytes invalid" 'left is buffer of [8, 2] +# in buffer storage so the decoder's own read performs the refusal: an index read +# first (raw[0]) would refuse before f64_from_bytes ever ran (N13). +run "NG56 f64_from_bytes N13 refusal" 'left is buffer of [8, 2] for i in range of 16: left[i] is 1e200 right is buffer of [2, 1] @@ -89,8 +134,14 @@ right[0] is 1e200 right[1] is 0 - 1e200 raw is matmul of [left, right] assert of [not (math_flags of null).invalid, "NaN setup leaves invalid clear"] -x is f64_from_bytes of raw -assert of [x == 0 and (math_flags of null).invalid, "decoder NaN read sets invalid"]' +caught is false +length_error is false +try: + f64_from_bytes of raw +catch e: + caught is e.kind == "value" + length_error is (index_of of [e.message, "needs 8 bytes"]) >= 0 +assert of [caught and not length_error and not (math_flags of null).invalid, "N13 refuses without normalizing NaN, not length"]' run "NG61 mixed materialization invalid" "$nan_setup x is add of [raw, [2]] assert of [x == [[2]] and (math_flags of null).invalid, \"mixed materialization observes invalid\"]" @@ -103,19 +154,27 @@ b[1] is 0 - 1e200 b[2] is 1e200 b[3] is 0 - 1e200 raw is matmul of [a, b]' -run "NG40 normalized dot stays valid" "$inf_setup -zero is buffer of 2 -x is dot of [raw, zero] -assert of [x == 0 and not (math_flags of null).invalid, \"normalized dot stays valid\"]" -run "NG41 normalized buf_dot stays valid" "$inf_setup +run "NG40 N13 dot refuses infinity" "$inf_setup zero is buffer of 2 -x is buf_dot of [raw, zero, 0, 0, 2] -assert of [x == 0 and not (math_flags of null).invalid, \"normalized buf_dot stays valid\"]" -run "NG57 byte decoder overflow" "$inf_setup -x is str_from_bytes of raw -assert of [(math_flags of null).overflow, \"infinity byte read sets overflow\"]" -run "NG59 PCM decoder overflow" "$inf_setup -x is buf_to_pcm16le of [raw, 0, 2] -assert of [(math_flags of null).overflow, \"infinity PCM read sets overflow\"]" +caught is false +try: + dot of [raw, zero] +catch e: + caught is e.kind == \"value\" +assert of [caught, \"N13 dot refuses non-finite payload\"]" +# N13 / R1.6d: infinity refusals do not normalize or set arithmetic flags. +for spec in \ + 'NG41 buf_dot|zero is buffer of 2; x is buf_dot of [raw, zero, 0, 0, 2]' \ + 'NG57 byte decoder|x is str_from_bytes of raw' \ + 'NG59 PCM encoder|x is buf_to_pcm16le of [raw, 0, 2]'; do + name=${spec%%|*}; op=${spec#*|}; op=$(printf '%s' "$op" | sed 's/; */\n /g; s/^/ /') + run "$name N13 refusal" "$inf_setup +caught is false +try: +$op +catch e: + caught is e.kind == \"value\" +assert of [caught and not ((math_flags of null).invalid or (math_flags of null).overflow), \"N13 infinity refusal\"]" +done echo "NUMERIC_FLAGS: $pass passed, $fail failed" [ "$fail" -eq 0 ] diff --git a/tests/test_observer_snapshot_fix2.sh b/tests/test_observer_snapshot_fix2.sh index cf1fa4ff4..71e3a9133 100755 --- a/tests/test_observer_snapshot_fix2.sh +++ b/tests/test_observer_snapshot_fix2.sh @@ -94,8 +94,10 @@ printf '%s\n' "$STATS" | grep -Eq '^eigs-alloc-stats: cumulative=[0-9]+ live=0 p echo " PASS: observer fix2 closure ownership: no tracked bytes left" # A real matmul kernel leaves the null-slot NaN sentinel in unboxed buffer -# storage after inf-inf. Language equality and observer equality must apply -# the same finite-number VALUE mapping without mutating that work area. +# storage after inf-inf. S10-D1 / R1.6z-D1 require the same raw IEEE +# comparison for language and observer equality, with no mutation or flags. +# Bank stability stays true for this non-reflexive snapshot; convergence is +# false. Finite mutable snapshots above retain #2048's alias protection. cat >"$TMP/nonfinite.eigs" <<'EOF' left is buffer of [1, 2] left[0] is 1e200 @@ -116,13 +118,15 @@ x is x x is x x is x x is x +flags_before is math_flags of null print of (x == x) print of (stable of x) print of (converged of x) +assert of [(math_flags of null) == flags_before, "IEEE observer equality preserves math flags"] EOF -[ "$(EIGS_STRICT=0 "$EIGS" "$TMP/nonfinite.eigs")" = "true +[ "$(EIGS_STRICT=0 "$EIGS" "$TMP/nonfinite.eigs")" = "false true -true" ] +false" ] echo " PASS: observer fix2 nonfinite buffer equality" echo observer-snapshot-fix2-ok diff --git a/tests/test_opcode_abi.c b/tests/test_opcode_abi.c index 13e87c8f9..3543e11ff 100644 --- a/tests/test_opcode_abi.c +++ b/tests/test_opcode_abi.c @@ -35,6 +35,12 @@ ABI_ASSERT(OP_LINE, 68); ABI_ASSERT(OP_SET_LOCAL_INTERNAL, 94); ABI_ASSERT(OP_TRUE, 95); ABI_ASSERT(OP_FALSE, 96); +/* #1758 N1: int literals append; old float producer numbers do not move. */ +ABI_ASSERT(OP_INT_ZERO, 97); +ABI_ASSERT(OP_INT_ONE, 98); +ABI_ASSERT(OP_INT_CONST, 99); +/* #1758 N4: append-only floor division; no descriptor ABI bump. */ +ABI_ASSERT(OP_IDIV, 100); /* #704: the revision the ABI-stamped descriptors in the .eigs fixtures under * tests/ declare. Those @@ -54,5 +60,12 @@ int main(void) { } /* #1425: appended scope markers, also assembled by the parent-line test. */ -ABI_ASSERT(OP_BINARY_LINE, 102); -ABI_ASSERT(OP_BINARY_LINE_END, 103); +ABI_ASSERT(OP_BINARY_LINE, 106); +ABI_ASSERT(OP_BINARY_LINE_END, 107); + +/* Sync #10: main loop opcodes follow Phase2 append-only opcodes. */ +ABI_ASSERT(OP_LOOP_ENTER, 101); +ABI_ASSERT(OP_LOOP_STALL_NAMED, 102); +ABI_ASSERT(OP_LOOP_CAP_NAMED, 103); +ABI_ASSERT(OP_LOOP_ENTER_NAMED, 104); +ABI_ASSERT(OP_LOOP_EXIT_NAMED, 105); diff --git a/tests/test_osr_observe_assign.eigs b/tests/test_osr_observe_assign.eigs index 03b464c60..00e6f12ee 100644 --- a/tests/test_osr_observe_assign.eigs +++ b/tests/test_osr_observe_assign.eigs @@ -15,22 +15,22 @@ load_file of "lib/test.eigs" define run(rounds) as: items is [] - for i in range of 20: - v is (i + 1) * 1.0 + for i in range of 20.0: + v is (i + 1.0) * 1.0 append of [items, v] - total is 0 + total is 0.0 for k in range of rounds: - for i in range of 20: + for i in range of 20.0: a is items[i] - b is items[19] + b is items[19.0] pick is a - if i > 10: + if i > 10.0: pick is b total is total + pick # Rebuild items each round so the picked values are fresh heap # objects (the leak only bites when the rebound value is a pointer). new_items is [] - for i in range of 20: + for i in range of 20.0: w is items[i] + 0.0 append of [new_items, w] items is new_items @@ -40,10 +40,10 @@ define run(rounds) as: # threshold. Per round: i=0..10 -> pick=items[i]=i+1 (sum 1..11 = 66); # i=11..19 -> pick=items[19]=20 (9 * 20 = 180); round total 246. # 300 rounds -> 73800. -r1 is run of 300 -assert of [r1 == 73800, "OSR conditional observed-assign result correct"] +r1 is run of 300.0 +assert of [r1 == 73800.0, "OSR conditional observed-assign result correct"] -r2 is run of 300 +r2 is run of 300.0 assert of [r1 == r2, "OSR result is deterministic across repeated runs"] print of "osr observe-assign: all passed" diff --git a/tests/test_phase2_gfx_q7.eigs b/tests/test_phase2_gfx_q7.eigs new file mode 100644 index 000000000..e561fc36f --- /dev/null +++ b/tests/test_phase2_gfx_q7.eigs @@ -0,0 +1,39 @@ +load_file of "lib/test.eigs" +# #1758 N5/Q7: real pixels for clipping, enormous curved spans and source crop. +opened is gfx_open of [64, 48, "Q7 pixels"] +if (not opened): + print of "SKIP: gfx_open failed (no SDL2)" + exit of 0 +gfx_clear of [0, 0, 0] +gfx_line of [-1e300, 7, 1e300, 7, 0, 255, 0] +assert_eq of [gfx_read of [0, 7], [0, 255, 0], "huge line enters at the viewport edge"] +assert_eq of [gfx_read of [63, 7], [0, 255, 0], "huge line keeps its full visible interval"] +assert_eq of [gfx_read of [32, 8], [0, 0, 0], "clipping keeps the line slope"] +gfx_clear of [0, 0, 0] +gfx_line of [63.25, 10, 63.25, 20, 0, 255, 0] +assert_eq of [gfx_read of [63, 15], [0, 255, 0], "fractional last-pixel line survives float clipping"] +gfx_clear of [0, 0, 0] +gfx_circle of [0, 1e308, 1e308, 0, 255, 0] +assert_eq of [gfx_read of [0, 0], [0, 255, 0], "huge circle's tangent pixel"] +assert_eq of [gfx_read of [1, 0], [0, 0, 0], "tangent row has a point span"] +assert_eq of [gfx_read of [63, 1], [0, 255, 0], "huge circle's visible chord retains the row offset"] +gfx_clear of [0, 0, 0] +gfx_circle of [1e308, 1e308, 1e308, 0, 255, 0] +assert_eq of [gfx_read of [0, 0], [0, 0, 0], "far diagonal disk misses despite intersecting bounds"] +assert_eq of [gfx_read of [63, 47], [0, 0, 0], "far diagonal disk does not overflow into the viewport"] +fb is buf_from_list of [0, 3] +gfx_clear of [255, 0, 0] +gfx_fb of [fb, 2, 1, 0, 0, 1.5] +assert_eq of [gfx_read of [1, 0], [255, 255, 255], "fractional first source cell rounds at its edge"] +assert_eq of [gfx_read of [2, 0], [0, 0, 0], "fractional second source cell retains its index"] +assert_eq of [gfx_read of [3, 0], [255, 0, 0], "fractional destination stops at its rounded extent"] +gfx_clear of [255, 0, 0] +gfx_fb of [fb, 2, 1, -3e9, 0, 3e9] +assert_eq of [gfx_read of [0, 0], [0, 0, 0], "clipped framebuffer uses the second source pixel"] +assert_eq of [gfx_read of [63, 47], [0, 0, 0], "huge cropped framebuffer keeps source mapping"] +gfx_point of [3e9, 3e9, 255, 255, 255] +assert_eq of [gfx_read of [32, 24], [0, 0, 0], "outside finite point paints nothing"] +gfx_rrect of [0, 0, 0, 20, 1e300, 255, 255, 255] +assert_eq of [gfx_read of [32, 24], [0, 0, 0], "empty rounded rectangle stays empty"] +gfx_close of null +test_summary of null diff --git a/tests/test_pool_poison.c b/tests/test_pool_poison.c index 944f587e5..fbc89ae85 100644 --- a/tests/test_pool_poison.c +++ b/tests/test_pool_poison.c @@ -56,13 +56,13 @@ static int run_program(void) { } static int plant_num(void) { - Value *v = make_num(42.5); + Value *v = make_float(42.5); val_decref(v); /* refcount 0 -> parked on the freelist */ if (g_num_freelist != v) { puts("NOT-ARMED num: value not parked"); return 3; } printf("ARMED num: parked NUM %p, reading %p\n", (void *)v, - (void *)&VAL_NUM_RAW(v)); + (void *)&VAL_F64_RAW(v)); fflush(stdout); - volatile double stale = VAL_NUM_RAW(v); /* the planted stale read */ + volatile double stale = VAL_F64_RAW(v); /* the planted stale read */ printf("UNDETECTED num: read %g from a parked NUM\n", stale); return 0; } @@ -127,7 +127,7 @@ static int leaf_control(void) { r = eigs_eval_string("pool_poison_leaf_r is pool_poison_leaf of [7]\n"); if (r) eigs_value_release(r); Value *res = eigs_get_global("pool_poison_leaf_r"); - int result_ok = res && res->type == VAL_NUM && VAL_NUM_RAW(res) == 7.0; + int result_ok = res && res->type == VAL_INT && VAL_I64_RAW(res) == 7; if (res) eigs_value_release(res); no_env = no_env && c && !c->env_cache; if (classified && no_env && result_ok) { @@ -141,12 +141,12 @@ static int leaf_control(void) { static int control(void) { int bad = 0; - /* NUM pop: make_num hands the parked Value back, readable again. */ - Value *v = make_num(1.5); + /* NUM pop: make_float hands the parked Value back, readable again. */ + Value *v = make_float(1.5); val_decref(v); - Value *again = make_num(2.5); + Value *again = make_float(2.5); if (again != v) { puts("NOT-ARMED control: freelist did not reuse"); return 3; } - if (VAL_NUM_RAW(again) != 2.5 || again->refcount != 1) bad = 1; + if (VAL_F64_RAW(again) != 2.5 || again->refcount != 1) bad = 1; printf("control num reuse: %s\n", bad ? "WRONG" : "ok"); val_decref(again); @@ -177,7 +177,8 @@ static int control(void) { if (!r) { puts("control take: eval failed"); return 1; } eigs_value_release(r); Value *res = eigs_get_global("pool_poison_r"); - int ok = res && res->type == VAL_NUM && VAL_NUM_RAW(res) == 8.0; + /* #1758 N1/N3: this natural integer program returns the exact int 8. */ + int ok = res && res->type == VAL_INT && VAL_I64_RAW(res) == 8; if (res) eigs_value_release(res); int same = c->env_cache == first; printf("control env take: %s (re-parked same env: %s)\n", diff --git a/tests/test_probability.eigs b/tests/test_probability.eigs index 2a862905f..dd017b98a 100644 --- a/tests/test_probability.eigs +++ b/tests/test_probability.eigs @@ -63,6 +63,13 @@ assert_near of [probability.poisson_cdf of [200, 190.0], 0.7784236536636114, 7.7 # A large lambda underflows e^-lambda to 0 even when the probability is a # normal double, so the direct formula would return exactly 0. assert_near of [probability.poisson_pmf of [100, 800.0], 8.005888215986707e-216, 8.005888215986707e-228, "poisson_pmf(100, 800) e^-lambda underflow"] +# An INT k past 170 takes Stirling's series, whose products must be floats: +# with int literals, 1260 * k * k2 * k2 leaves int64 from k = 1490 and raises +# (#1758). The expected values are main 94657581's at the same arguments. +assert_eq of [num_type of (probability.poisson_pmf of [1490, 1000.0]), "float", "poisson_pmf(int 1490, 1000) is a float"] +assert_near of [probability.poisson_pmf of [1490, 1000.0], 5.902674575941492e-48, 5.902674575941492e-60, "poisson_pmf(int 1490, 1000) Stirling"] +assert_eq of [num_type of (probability.poisson_pmf of [3000, 3000.0]), "float", "poisson_pmf(int 3000, 3000) is a float"] +assert_near of [probability.poisson_pmf of [3000, 3000.0], 0.007283453882998077, 7.283453882998077e-15, "poisson_pmf(int 3000, 3000) Stirling"] # ---- Normal Distribution ---- print of "--- Normal ---" diff --git a/tests/test_r16b_builtin_ownership.eigs b/tests/test_r16b_builtin_ownership.eigs new file mode 100644 index 000000000..eb09b28aa --- /dev/null +++ b/tests/test_r16b_builtin_ownership.eigs @@ -0,0 +1,42 @@ +# #1758 R1.6b: real heap ints on successful and raising final-reader paths. +load_file of "lib/test.eigs" +big is 9007199254740993 +imax is 9223372036854775807 +imin is -9223372036854775807 - 1 +assert_eq of [num_type of big, "int", "heap operand kind"] +for iteration in range of 30: + copied is num_copy of big + assert_eq of [copied, big, "num_copy keeps heap int"] + assert_eq of [num_type of copied, "int", "copy kind"] + for op in [floor, ceil, round, abs]: + result is op of big + assert_eq of [result, big, "rounding/abs exact heap result"] + assert_eq of [num_type of result, "int", "rounding/abs kind"] + assert_eq of [min of [big, imax], big, "min exact"] + assert_eq of [max of [big, imax], imax, "max exact"] + assert_eq of [random_int of [big, big], big, "exact equal random bounds"] + assert_eq of [hex of big, "20000000000001", "hex exact"] + assert_eq of [num of (str of big), big, "N10 exact heap parse"] + decoded is json_decode of (json_encode of [big, imin, imax]) + assert_eq of [decoded, [big, imin, imax], "N14 exact heap decode"] + assert_eq of [num_type of decoded[0], "int", "decoded heap kind"] + assert_eq of [scan_ints of "9007199254740993 -9223372036854775808", [big, imin], "C3 exact scanners"] + caught is 0 + for thunk in [(x) => hex of (0 - x), (x) => range of x, (x) => abs of imin, (x) => scan_ints of "9007199254740993 9223372036854775808", (x) => scan_int_tokens of "9007199254740993 9223372036854775808", (x) => sign_extend of [-9223372036854775553, 8], (x) => min of [], (x) => max of ([])]: + try: + thunk of big + catch e: + assert_eq of [e.kind, "value", "exact-reader refusal kind"] + caught += 1 + assert_eq of [caught, 8, "all reader raises reached"] + ch is channel of null + send of [ch, big] + assert_eq of [recv of ch, big, "heap int through channel with int handle"] + close_channel of ch +# Return one heap argument through the migrated spawn result handle. +define identity(x) as: + return x +h is spawn of [identity, big] +assert_eq of [num_type of h._handle_id, "int", "thread id kind"] +assert_eq of [thread_join of h, big, "heap thread argument survives"] +test_summary of null diff --git a/tests/test_read_bytes_cap.sh b/tests/test_read_bytes_cap.sh index 3faaf6dec..db619e1d6 100755 --- a/tests/test_read_bytes_cap.sh +++ b/tests/test_read_bytes_cap.sh @@ -74,7 +74,7 @@ print of f\"kind={caught}\"" \ run_case "1-arg small read unchanged" \ "b is read_bytes_buf of \"$SMALL\" print of f\"{buf_len of b} {b[0]} {b[2]}\"" \ -"3 7 9" +"3 7.0 9.0" # 5. Guard-like max_bytes mistakes raise type_mismatch in strict mode; the # hard-ceiling policy remains a value error in every mode. diff --git a/tests/test_replay.sh b/tests/test_replay.sh index 932703db5..b3e2d9222 100755 --- a/tests/test_replay.sh +++ b/tests/test_replay.sh @@ -64,7 +64,7 @@ REC_B=$(EIGS_TRACE="$TAPE_B" "$EIGS" "$TMPDIR/p_buf.eigs" 2>&1) printf '\xff\xfe\xfd' > "$INPUT" REP_B=$(EIGS_REPLAY="$TAPE_B" "$EIGS" "$TMPDIR/p_buf.eigs" 2>&1) -EXPECTED_B=$'1\n2\n3' +EXPECTED_B=$'1.0\n2.0\n3.0' if [ "$REC_B" = "$EXPECTED_B" ] && [ "$REP_B" = "$EXPECTED_B" ]; then ok "buffer replay: read_bytes_buf restored as VAL_BUFFER (b[…] disambiguator)" else @@ -220,7 +220,7 @@ N 0 ls=[{"k": 42}, {"k": "ok"}, b[10, 20, 30]] EOF REP_N=$(EIGS_REPLAY="$TMPDIR/nested.tape" "$EIGS" "$TMPDIR/p_nested.eigs" 2>/dev/null) -EXPECTED_N=$'{"k": 42}\nok\n20' +EXPECTED_N=$'{"k": 42}\nok\n20.0' if [ "$REP_N" = "$EXPECTED_N" ]; then ok "nested replay: list[dict, dict, buffer] round-trip" @@ -634,8 +634,8 @@ print of x EOF CONTRACT_TAPE="$TMPDIR/contract.tape" EIGS_TRACE="$CONTRACT_TAPE" "$EIGS" "$TMPDIR/p_tape_contract.eigs" >/dev/null 2>&1 -if head -1 "$CONTRACT_TAPE" | grep -q '^V 8 '; then - ok "tapes written by this build stamp format v8" +if head -1 "$CONTRACT_TAPE" | grep -q '^V 9 '; then + ok "tapes written by this build stamp format v9" else fail "format stamp" "$(head -1 "$CONTRACT_TAPE")" fi @@ -660,7 +660,7 @@ else fail "pre-v3 replay refusal" "rc=$OLD_REPLAY_RC $(cat "$TMPDIR/old-replay.err")" fi -# O records remain part of v7 tapes and the stepper still folds them. Bound the +# O records remain part of v9 tapes and the stepper still folds them. Bound the # multiplicative configuration-record x binding work even though programs can # no longer create those records through observer setter builtins. WORK_TAPE="$TMPDIR/observer-work-limit.tape" diff --git a/tests/test_runtime_counts.eigs b/tests/test_runtime_counts.eigs new file mode 100644 index 000000000..8f0212574 --- /dev/null +++ b/tests/test_runtime_counts.eigs @@ -0,0 +1,57 @@ +# S9-K1 (#1758): counts, indices and metadata stay exact ints on publication. +load_file of "lib/test.eigs" + +define count_is(value, wanted, label) as: + assert_eq of [num_type of value, "int", label + " type"] + assert_eq of [value, wanted, label + " value"] + +counter_probe is 0 +loop while counter_probe < 2: + counter_probe is counter_probe + 1 +count_is of [__loop_iterations__, 2, "loop iteration count"] +count_is of [when is counter_probe, 3, "live assignment count"] +count_is of [when is counter_probe at 1000000, 3, "line assignment count"] +count_is of [when is counter_probe when 2, 2, "ordinal assignment count"] +count_is of [when is (4 + 5), 0, "value assignment count"] +count_is of [what is "abc", 3, "string byte length"] +count_is of [what is ([4, 5]), 2, "list length"] +count_is of [what is (buffer of 4), 4, "buffer length"] +count_is of [(trajectory of counter_probe).window, 10, "trajectory sample window"] + +error_line_probe is null +try: + get_at of [[1], 2] +catch error_probe: + error_line_probe is error_probe.line +assert_eq of [num_type of error_line_probe, "int", "runtime error source line type"] +assert_true of [error_line_probe > 0, "runtime error source line value"] + +# Integer indices returned from a range retain their kind through for and +# comprehension binders; the iterator's own hidden index/length are ints too. +index_kinds is [num_type of index_probe for index_probe in (range of 3)] +assert_eq of [index_kinds, ["int", "int", "int"], "comprehension index kinds"] +for index_probe in (range of 3): + assert_eq of [num_type of index_probe, "int", "for index kind"] + +# The non-strict form keeps the documented miss sentinel as an int too. +if (env_get of "EIGS_STRICT") == "0": + count_is of [index_of of null, -1, "index_of malformed argument miss"] + count_is of [index_of of [3, "x"], -1, "index_of wrong operand miss"] + count_is of [list_index_of of null, -1, "list_index_of malformed argument miss"] + count_is of [list_index_of of [3, 1], -1, "list_index_of wrong operand miss"] + +define count_victim() as: + task_recv of null + return 1 +victim_probe is task_spawn of count_victim +task_yield of null +task_kill of victim_probe +kill_line_seen is false +try: + task_join of victim_probe +catch killed_probe: + count_is of [killed_probe.line, 0, "killed-task source-line sentinel"] + kill_line_seen is true +assert_true of [kill_line_seen, "killed-task error was observed"] + +test_summary of null diff --git a/tests/test_sandbox_error_keys.eigs b/tests/test_sandbox_error_keys.eigs index 903f1fe55..f896de9b9 100644 --- a/tests/test_sandbox_error_keys.eigs +++ b/tests/test_sandbox_error_keys.eigs @@ -56,4 +56,11 @@ for i in range of 100: assert_eq of [budget_keys[i], expected_error_keys, "#1402: byte-budget abort inside try keeps exact host error keys"] +# N5 (#1758 R1.6c): descriptor fields are guest data. They are refused +# structurally, while fractional caller-supplied limits raise to the host. +for desc in [[1.5, [0, 0, 0, 40], [7]], [1, [0.5, 0, 0, 40], [7]], [1, [0, 0, 0, 40], [7], [], 0.5]]: + local bad_numeric is sandbox_run of [desc, 1000] + assert_eq of [bad_numeric["ok"], false, "N5 malformed numeric descriptor rejected"] + assert_eq of [bad_numeric["error"]["kind"], "value", "N5 descriptor remains a structured error"] + assert_eq of [num_type of bad_numeric["error"]["line"], "int", "N5 descriptor line is an int"] test_summary of null diff --git a/tests/test_sandbox_intern_lifetime.c b/tests/test_sandbox_intern_lifetime.c index 015228bb4..a3fc14760 100644 --- a/tests/test_sandbox_intern_lifetime.c +++ b/tests/test_sandbox_intern_lifetime.c @@ -14,7 +14,7 @@ extern Value *builtin_sandbox_run(Value *arg); extern size_t env_intern_debug_count(const char *prefix); static void append_num(Value *list, double n) { - list_append_owned(list, make_num(n)); + list_append_owned(list, make_float(n)); } static size_t intern_count(void) { @@ -105,7 +105,7 @@ int main(void) { char key[64]; for (int i = 0; i < 128; i++) { - snprintf(key, sizeof key, "sandbox-only-key-%d", i); + snprintf(key, sizeof key, "sandbox-only-key-%d.0", i); Value *out = sandbox_call(i); assert(out != NULL); EigsView ok_view, result_view; @@ -124,7 +124,7 @@ int main(void) { const size_t sandbox_retained = intern_count() - baseline; const size_t sandbox_prefix_retained = sandbox_only_intern_count() - sandbox_baseline; - snprintf(key, sizeof key, "sandbox-only-key-%d", 0); + snprintf(key, sizeof key, "sandbox-only-key-%d.0", 0); /* Check the escaped-result control before the retention assertion: a * mutant that restores the leak must still demonstrate that the returned * key remains readable through the run boundary. */ @@ -135,8 +135,8 @@ int main(void) { fprintf(stderr, "escaped result=%s expected-key=%s\n", diagnostic, key); free(diagnostic); } - assert(escaped_value && escaped_value->type == VAL_NUM && - VAL_NUM_RAW(escaped_value) == 1.0); + assert(escaped_value && escaped_value->type == VAL_FLOAT && + VAL_F64_RAW(escaped_value) == 1.0); fprintf(stderr, "sandbox intern growth: baseline=%zu retained=%zu sandbox_only=%zu\n", baseline, sandbox_retained, sandbox_prefix_retained); diff --git a/tests/test_sandbox_work_budget.c b/tests/test_sandbox_work_budget.c index a0502ee33..2e919e06b 100644 --- a/tests/test_sandbox_work_budget.c +++ b/tests/test_sandbox_work_budget.c @@ -21,14 +21,14 @@ static Value *ordinary_descriptor(void) { if (chunk && !g_has_error && !g_parse_errors && chunk->code_len > 0 && chunk->code_len <= 64 && !chunk->fn_count && !chunk->local_count && !chunk->param_count && chunk->const_count == 1 && - chunk->constants[0]->type == VAL_NUM && VAL_NUM_RAW(chunk->constants[0]) == 42) { + chunk->constants[0]->type == VAL_INT && VAL_I64_RAW(chunk->constants[0]) == 42) { Value *code = make_list(chunk->code_len); Value *constants = make_list(1); for (int i = 0; i < chunk->code_len; i++) - list_append_owned(code, make_num(chunk->code[i])); + list_append_owned(code, make_float(chunk->code[i])); list_append(constants, chunk->constants[0]); descriptor = make_list(3); - list_append_owned(descriptor, make_num(EIGS_BYTECODE_ABI)); + list_append_owned(descriptor, make_float(EIGS_BYTECODE_ABI)); list_append_owned(descriptor, code); list_append_owned(descriptor, constants); } @@ -55,22 +55,22 @@ int main(void) { for (int run = 0; run < 2; run++) { Value *args = make_list(4); list_append(args, descriptor); - list_append_owned(args, make_num(100)); - list_append_owned(args, make_num(1048576)); - list_append_owned(args, make_num(10000)); + list_append_owned(args, make_float(100)); + list_append_owned(args, make_float(1048576)); + list_append_owned(args, make_float(10000)); Value *out = builtin_sandbox_run(args); EigsView ok_view, result_view; Value *ok = out ? dict_get_view(out, "ok", &ok_view) : NULL; Value *result = out ? dict_get_view(out, "result", &result_view) : NULL; REQUIRE(ok && ok->type == VAL_BOOL && ok->data.boolean == 1 && - result && result->type == VAL_NUM && VAL_NUM_RAW(result) == 42, + result && result->type == VAL_INT && VAL_I64_RAW(result) == 42, "ordinary sandbox result"); REQUIRE(!g_sandbox_active && g_sandbox_work_used == 13 && g_sandbox_work_max == 77, "sandbox restores caller accounting"); val_decref(out); val_decref(args); Value *host = eigs_eval_string("return 7\n"); - REQUIRE(host && host->type == VAL_NUM && VAL_NUM_RAW(host) == 7, + REQUIRE(host && host->type == VAL_INT && VAL_I64_RAW(host) == 7, "host work between sandbox calls"); val_decref(host); REQUIRE(g_sandbox_work_used == 13 && g_sandbox_work_max == 77, @@ -85,13 +85,13 @@ int main(void) { g_sandbox_work_max = 10000; g_sandbox_work_used = 0; Value *ordinary = eigs_eval_string("return 42\n"); - REQUIRE(ordinary && ordinary->type == VAL_NUM && VAL_NUM_RAW(ordinary) == 42, + REQUIRE(ordinary && ordinary->type == VAL_INT && VAL_I64_RAW(ordinary) == 42, "ordinary arithmetic result"); val_decref(ordinary); REQUIRE(g_sandbox_work_used > 0, "ordinary dispatch charges shared account"); uint64_t previous_work = g_sandbox_work_used; ordinary = eigs_eval_string("define work_identity(x) as:\n return x\nreturn work_identity of 42\n"); - REQUIRE(ordinary && ordinary->type == VAL_NUM && VAL_NUM_RAW(ordinary) == 42, + REQUIRE(ordinary && ordinary->type == VAL_INT && VAL_I64_RAW(ordinary) == 42, "ordinary function result"); val_decref(ordinary); REQUIRE(g_sandbox_work_used > previous_work, "re-entry preserves accounting"); diff --git a/tests/test_seed_random_env.sh b/tests/test_seed_random_env.sh new file mode 100755 index 000000000..b58def4e3 --- /dev/null +++ b/tests/test_seed_random_env.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +echo "PLAN: 5" +set -eu +EIGS=${EIGS:-$(cd "$(dirname "$0")/.." && pwd)/src/eigenscript} +T=$(mktemp -d); trap 'rm -rf "$T"' EXIT +cat > "$T/draw.eigs" <<'EIGS' +print of random of null +print of random_int of [1, 1000000] +EIGS +pass() { echo "PASS: $*"; } +fail() { echo "FAIL: $*"; exit 1; } +EIGS_SEED_RANDOM=2120 "$EIGS" "$T/draw.eigs" > "$T/a" +EIGS_SEED_RANDOM=2120 "$EIGS" "$T/draw.eigs" > "$T/b" +cmp "$T/a" "$T/b" >/dev/null || fail "same seed differed between processes" +pass "same seed produces identical process-wide draws" +printf '0.2717224071539057\n413189\n' > "$T/want2120" +cmp "$T/want2120" "$T/a" >/dev/null || fail "seed 2120 first draws changed" +pass "seed 2120 first draws are pinned" +EIGS_SEED_RANDOM=2121 "$EIGS" "$T/draw.eigs" > "$T/actual2121" +printf '0.14252471581949422\n26042\n' > "$T/want2121" +cmp "$T/want2121" "$T/actual2121" >/dev/null || fail "seed 2121 first draws changed" +pass "seed 2121 first draws are pinned" +rc=0; EIGS_SEED_RANDOM=not-an-integer "$EIGS" "$T/draw.eigs" > "$T/bad.out" 2> "$T/bad.err" || rc=$? +[ "$rc" -eq 2 ] || fail "malformed seed exited $rc, want 2 (a startup error)" +grep -F 'EIGS_SEED_RANDOM: invalid integer: not-an-integer' "$T/bad.err" >/dev/null || fail "malformed seed lacked diagnostic" +[ ! -s "$T/bad.out" ] || fail "malformed seed ran program code before failing" +pass "malformed seed is a startup error before any program output" +rc=0 +EIGS_SEED_RANDOM= "$EIGS" "$T/draw.eigs" > "$T/empty.out" 2> "$T/empty.err" || rc=$? +[ "$rc" -eq 0 ] || fail "empty seed exited $rc, want 0 (unset semantics)" +[ ! -s "$T/empty.err" ] || fail "empty seed wrote a startup diagnostic" +[ "$(wc -l < "$T/empty.out" | tr -d ' ')" -eq 2 ] || fail "empty seed did not run both unseeded draws" +pass "empty seed is treated as unset and permits unseeded draws" diff --git a/tests/test_sigusr1_dump.sh b/tests/test_sigusr1_dump.sh index c16356695..b1b5e2c92 100644 --- a/tests/test_sigusr1_dump.sh +++ b/tests/test_sigusr1_dump.sh @@ -9,6 +9,9 @@ # we poll for that line, then `kill -USR1`. The dump's own "# end dump" # trailer on stderr acknowledges the safepoint fired. Then we release the # child through a SENTINEL FILE and require the DONE marker + exit 0. +# If the first dump arms observation, a separate ARM file is created only +# after that dump's trailer. The child sees it, performs 5000 further loop +# assignments, and prints POST_ARM_READY before we send the second signal. # # The sentinel is load-bearing, and the reason is worth keeping. The child's # loop used to be bounded by a fixed iteration count (2,000,000) chosen so the @@ -47,6 +50,7 @@ FIX2=/tmp/eigs_sigusr1_b_$$.eigs # Release files: the child polls for these and winds down once the harness has # collected its dumps (see the header). SENT1=/tmp/eigs_sigusr1_a_$$.go +ARM1=/tmp/eigs_sigusr1_a_$$.arm SENT2=/tmp/eigs_sigusr1_b_$$.go OUT1=/tmp/eigs_sigusr1_a_$$.out ERR1=/tmp/eigs_sigusr1_a_$$.err @@ -61,7 +65,7 @@ cleanup() { for p in $PIDS; do sigusr1_stop "$p" || status=1 done - rm -f "$FIX1" "$FIX2" "$OUT1" "$ERR1" "$OUT2" "$ERR2" "$SENT1" "$SENT2" "$FIX1.bak" "$FIX2.bak" + rm -f "$FIX1" "$FIX2" "$OUT1" "$ERR1" "$OUT2" "$ERR2" "$SENT1" "$SENT2" "$ARM1" "$FIX1.bak" "$FIX2.bak" exit "$status" } trap cleanup EXIT @@ -90,10 +94,19 @@ epsilon is 1.0 define train(n) as: loss is 5.0 scale is 0.999 + post_arm_samples is -1 loop while step_count < n: step_count is step_count + 1 loss is loss * scale + 0.001 epsilon is epsilon * 0.99999 + if post_arm_samples < 0: + if file_exists of "@ARM1@": + post_arm_samples is 0 + else: + if post_arm_samples < 5000: + post_arm_samples is post_arm_samples + 1 + if post_arm_samples == 5000: + print of "POST_ARM_READY" if step_count == 5000: print of "READY" if step_count % 200000 == 0: @@ -104,9 +117,12 @@ r is train of 2000000000 print of "DONE" EOF sigusr1_replace_sentinel "$FIX1" "@SENT1@" "$SENT1" || exit 1 +sigusr1_replace_sentinel "$FIX1" "@ARM1@" "$ARM1" || exit 1 # ---- Subtest 1: single-thread dump shape ------------------------------- +# The ARM sentinel must be absent until the first gated dump acknowledges it. +rm -f "$ARM1" "$EIGS" "$FIX1" > "$OUT1" 2> "$ERR1" & PID=$! PIDS="$PIDS $PID" @@ -138,9 +154,16 @@ fi # capability is preserved across two signals instead of being dropped. if grep -q "observer gate CLOSED" "$ERR1"; then pass "sigusr1: gated first dump declares absence of data (not equilibrium)" - # Second signal: observation was armed by the first, so this one carries - # real trajectories. The row assertions below grep the whole file and so - # match this dump. + # A signal arriving immediately after arming may see too few observed + # assignments, especially under ASan. Wait for the child to acknowledge + # 5000 actual iterations after seeing this post-dump sentinel. + : > "$ARM1" + if ! poll_file "$OUT1" "^POST_ARM_READY$" 600; then + fail "sigusr1: child never completed 5000 assignments after the gate armed" + exit 1 + fi + # The second dump now carries real trajectories. The row assertions below + # grep the whole file and match this dump. DUMPS_BEFORE=$(grep -c '^# end dump$' "$ERR1") kill -USR1 "$PID" 2>/dev/null || true for _ in $(seq 1 600); do @@ -156,8 +179,9 @@ if grep -q "observer gate CLOSED" "$ERR1"; then fi # Row shape: name | value | when= | entropy= | dH= | word. -# when must have 4+ digits (READY is printed at assignment 5000 of -# step_count, the signal lands strictly after) — a settled long-lived +# when must have 4+ digits: READY follows 5000 step_count assignments; +# a gated second dump additionally waits for 5000 post-arm assignments. +# This is a settled long-lived # binding, visibly distinct from a fresh per-call one. if grep -qE '^step_count \| [0-9]+ \| when=[0-9]{4,} \| entropy=[^ ]+ \| dH=[^ ]+ \| [a-z]+$' "$ERR1"; then pass "sigusr1: module row shape (name|value|when|entropy|dH|trajectory) with settled when" diff --git a/tests/test_sort_stable.eigs b/tests/test_sort_stable.eigs index bf5b6778a..988cef4eb 100644 --- a/tests/test_sort_stable.eigs +++ b/tests/test_sort_stable.eigs @@ -1,3 +1,4 @@ +# #1758 sort ruling: tie order is observable in numeric kinds and signed zero. # #1982 sort ruling: sort_by tie order is observable through record identity. load_file of "lib/test.eigs" xs is [] @@ -16,6 +17,8 @@ result is sort of xs assert_eq of [str of result, str of want, "sort keeps mixed int/float ties in input order"] assert_eq of [str of xs, str of want, "sort mutates its input"] +zs is [0.0, -0.0, 0, -0.0, 0.0, 0] +assert_eq of [str of (sort of zs), "[0.0, -0.0, 0, -0.0, 0.0, 0]", "sort preserves signed-zero ties"] assert_eq of [str of (sort of ["b", "a", "a", "b"]), "[\"a\", \"a\", \"b\", \"b\"]", "sort strings"] assert_eq of [str of (sort of ([])), "[]", "sort empty"] assert_eq of [str of (sort of ([3])), "[3]", "sort singleton"] diff --git a/tests/test_spawn_jit.eigs b/tests/test_spawn_jit.eigs index d9adf2c2c..25d820194 100644 --- a/tests/test_spawn_jit.eigs +++ b/tests/test_spawn_jit.eigs @@ -9,27 +9,33 @@ # Each worker calls the shared `helper` enough times to cross the JIT threshold; # many workers run sequentially so compilation lands on a worker. No closure # cycle, so this is leak-clean (distinct from the still-open MT cycle-GC gap). +# +# The JIT-purposed code (helper, worker and the main-thread `check`, which is +# the chunk that compiles here) writes FLOAT literals: the JIT refuses int code +# until R5 (docs/design/phase2-numbers.md section 3), and with ints nothing in +# this file compiled. Its int twin returns at R5 (round plan, Round 5 +# obligations). define helper(x) as: - return (x * 3) + 1 + return (x * 3.0) + 1.0 define worker(k) as: - local s is 0 - local i is 0 - loop while i < 40: + local s is 0.0 + local i is 0.0 + loop while i < 40.0: s is s + (helper of i) - i is i + 1 + i is i + 1.0 return s + k # helper of i for i in 0..39: sum (3i+1) = 3*(0+..+39) + 40 = 3*780 + 40 = 2380 -expected_base is 2380 +expected_base is 2380.0 -checks is 0 -passed is 0 +checks is 0.0 +passed is 0.0 define check(label, got, want) as: - checks is checks + 1 + checks is checks + 1.0 if got == want: - passed is passed + 1 + passed is passed + 1.0 else: print of ("MISMATCH " + label + ": got=" + str of got + " want=" + str of want) diff --git a/tests/test_spawn_jit_warm.eigs b/tests/test_spawn_jit_warm.eigs index 91d0be5df..7da657998 100644 --- a/tests/test_spawn_jit_warm.eigs +++ b/tests/test_spawn_jit_warm.eigs @@ -14,26 +14,29 @@ # GET_NAME of `gmul` is what makes the race TSan-visible: emitted code is # uninstrumented, but the IC fill in jit_helper_get_name is C. Values are # pinned here; race-freedom is gated by tests/test_tsan.sh. +# helper, warm and worker compute in FLOATS so helper's thunk runs natively to +# its return: the JIT refuses int code until R5 (docs/design/phase2-numbers.md +# section 3). Its int twin returns at R5 (round plan, Round 5 obligations). -gmul is 3 +gmul is 3.0 define helper(x) as: - return (x * gmul) + 1 + return (x * gmul) + 1.0 define warm() as: - local s is 0 - local i is 0 - loop while i < 80: + local s is 0.0 + local i is 0.0 + loop while i < 80.0: s is s + (helper of i) - i is i + 1 + i is i + 1.0 return s # sum over i in 0..79 of (3i+1) = 3*3160 + 80 = 9560 define worker(k) as: - local s is 0 - local i is 0 - loop while i < 400: + local s is 0.0 + local i is 0.0 + loop while i < 400.0: s is s + (helper of i) - i is i + 1 + i is i + 1.0 return s + k # sum over i in 0..399 of (3i+1) = 3*79800 + 400 = 239800 checks is 0 diff --git a/tests/test_store.eigs b/tests/test_store.eigs index de1b6f679..25f34cb21 100644 --- a/tests/test_store.eigs +++ b/tests/test_store.eigs @@ -1,8 +1,9 @@ load_file of "lib/test.eigs" # Clean up any leftover test database -if file_exists of "/tmp/test_eigenstore.db": - remove_file of "/tmp/test_eigenstore.db" +for stale_path in ["/tmp/eigs1006_fp_a", "/tmp/eigs1006_fp_b", "/tmp/eigs1006_fp_c", "/tmp/eigs1006_update.db", "/tmp/eigs1758_r16b_boxed_store.db", "/tmp/eigs816_bounds.db", "/tmp/eigs_store_badmagic.db", "/tmp/eigs_store_badver.db", "/tmp/eigs_store_short.db", "/tmp/test_eigenstore.db"]: + if file_exists of stale_path: + remove_file of stale_path # Open creates file db is store_open of "/tmp/test_eigenstore.db" @@ -136,11 +137,18 @@ nb[2] is 1e300 nb[3] is 1e300 nm is reshape of [nb, 2, 2] np is matmul of [nm, nm] -assert_eq of [np[0], 1e308, "matmul overflow is clamped at the scalar read boundary"] +# R1.6d-D1/R1.6z: scalar indexing refuses raw infinity; the stored +# payload still round-trips, which IEEE buffer equality can observe directly. +nonfinite_index_refused is false +try: + np[0] +catch e: + nonfinite_index_refused is e.kind == "value" and e.message == "buffer_read_f64: expected a finite number" +assert_eq of [nonfinite_index_refused, true, "matmul overflow is refused at the scalar read boundary"] nk is store_put of [db2, "blobs", {"n": np}] ni is store_get of [db2, "blobs", nk] assert_eq of [type of ni.n, "buffer", "non-finite buffer still decodes as a buffer"] -assert_eq of [ni.n[0], np[0], "non-finite buffer element round-trips"] +assert_eq of [ni.n, np, "non-finite buffer elements round-trip by IEEE comparison"] # An empty buffer stays an empty buffer, not a null and not an empty list. eb is buffer of 0 @@ -379,4 +387,106 @@ if file_exists of "/tmp/eigs1006_update.db": if file_exists of "/tmp/test_eigenstore.db": remove_file of "/tmp/test_eigenstore.db" +# --- #1758 R1.6b: N5 keys, N14 boxed decode, and owned rejection paths --- +define r16b_store_expect_error(action, kind, label) as: + local caught is false + try: + action of null + catch e: + assert_true of [e.kind == kind, label + ": error kind"] + caught is true + assert_true of [caught, label + ": must raise"] + + +define r16b_store_boxed_checks(path) as: + if file_exists of path: + remove_file of path + local big is 9007199254740993 + local low is -9223372036854775807 - 1 + local high is 9223372036854775807 + local changed is big + 2 + assert_true of [(num_type of big) == "int", "boxed big operand kind"] + assert_true of [(num_type of low) == "int", "boxed minimum operand kind"] + assert_true of [(num_type of high) == "int", "boxed maximum operand kind"] + assert_true of [(num_type of changed) == "int", "boxed replacement operand kind"] + + local s is store_open of path + for key in [big, low, high, 0]: + local record is {"_id": str of key, "key": key, "n": big, "values": [low, big, high], "heap": {"message": "owned payload", "n": big}, "f": 2.5} + local written is store_put of [s, "c", record] + assert_true of [written == (str of key), "exact explicit key written"] + local loaded is store_get of [s, "c", key] + assert_true of [loaded != null, "exact int64 numeric key found"] + assert_true of [(num_type of loaded.key) == "int", "decoded key field kind"] + assert_true of [loaded.key == key, "decoded key field exact"] + assert_true of [(num_type of loaded.n) == "int", "decoded boxed field kind"] + assert_true of [loaded.n == big, "decoded boxed field exact"] + assert_true of [(num_type of loaded.values[0]) == "int", "nested minimum kind"] + assert_true of [(num_type of loaded.values[1]) == "int", "nested big kind"] + assert_true of [(num_type of loaded.values[2]) == "int", "nested maximum kind"] + assert_true of [loaded.values == [low, big, high], "nested boxed list exact"] + assert_true of [loaded.heap.message == "owned payload", "heap sibling preserved"] + assert_true of [(num_type of loaded.heap.n) == "int", "nested dict boxed kind"] + assert_true of [loaded.heap.n == big, "nested dict boxed value"] + assert_true of [(num_type of loaded.f) == "float", "fractional text stays float"] + + local replacement is {"n": changed, "values": [big, low, high], "heap": "replacement payload"} + assert_true of [store_update of [s, "c", big, replacement], "boxed-key update succeeds"] + local after_update is store_get of [s, "c", big] + assert_true of [(num_type of after_update.n) == "int", "updated boxed result kind"] + assert_true of [after_update.n == changed, "updated boxed result exact"] + assert_true of [after_update.heap == "replacement payload", "updated heap field"] + + # N5's post-raise payload is zero. Check that reader refusals leave a real + # zero-key record intact and do not modify the update argument. + local rejected is {"n": big, "values": [low, high], "heap": "must stay owned"} + r16b_store_expect_error of [() => store_get of [s, "c", 0.5], "value", "fractional get"] + r16b_store_expect_error of [() => store_delete of [s, "c", 0.5], "value", "fractional delete"] + r16b_store_expect_error of [() => store_update of [s, "c", 0.5, rejected], "value", "fractional update"] + assert_true of [(len of rejected) == 3, "fractional update does not add _id"] + assert_true of [(store_get of [s, "c", 0]).heap.message == "owned payload", "zero-key sentinel survives reader raises"] + assert_true of [(store_get of [s, "c", big]).n == changed, "boxed record survives reader raises"] + assert_true of [(store_count of [s, "c"]) == 4, "reader raises leave collection intact"] + + local outside is 9223372036854775808.0 + assert_true of [(num_type of outside) == "float", "out-of-int64 operand is a float"] + r16b_store_expect_error of [() => store_get of [s, "c", outside], "value", "out-of-range get"] + r16b_store_expect_error of [() => store_delete of [s, "c", outside], "value", "out-of-range delete"] + r16b_store_expect_error of [() => store_update of [s, "c", outside, rejected], "value", "out-of-range update"] + assert_true of [(len of rejected) == 3, "out-of-range update does not add _id"] + assert_true of [(store_get of [s, "c", 0]).n == big, "zero-key sentinel survives range raises"] + + # Rejection after store_update has entered the replacement path must release + # all heap arguments and restore the record containing boxed integer values. + r16b_store_expect_error of [() => store_update of [s, "c", big, ["wrong record type", big]], "type_mismatch", "boxed-key wrong record"] + local oversized is "" + for i in range of 420: + oversized is oversized + "xxxxxxxxxx" + r16b_store_expect_error of [() => store_update of [s, "c", big, {"n": big, "body": oversized}], "limit", "boxed-key oversized replacement"] + assert_true of [(store_get of [s, "c", big]).n == changed, "failed replacement restores boxed record"] + assert_true of [(store_count of [s, "c"]) == 4, "failed replacement loses no row"] + + # b-written catalog ints must survive f's unchanged open/close loader and + # feed b's exact root/next_id readers on later generated-id writes. + assert_true of [(store_put of [s, "auto", {"n": big}]) == "1", "first auto id"] + assert_true of [(store_put of [s, "auto", {"n": high}]) == "2", "second auto id"] + store_close of s + s is store_open of path + assert_true of [(store_put of [s, "auto", {"n": low}]) == "3", "auto id after catalog decode"] + assert_true of [(store_get of [s, "auto", 3.0]).n == low, "integral float key succeeds after reopen"] + local rows is store_query of [s, "c"] + assert_true of [(len of rows) == 4, "query returns all owned records"] + for row in rows: + assert_true of [(num_type of row.n) == "int", "query boxed result kind"] + assert_true of [(num_type of row.values[0]) == "int", "query nested boxed result kind"] + assert_true of [store_delete of [s, "c", big], "boxed-key delete succeeds"] + assert_true of [(store_get of [s, "c", big]) == null, "boxed-key record was deleted"] + assert_true of [not (store_delete of [s, "c", big]), "boxed-key delete miss"] + assert_true of [(store_get of [s, "c", low]).key == low, "minimum key survives neighbour deletion"] + assert_true of [(store_get of [s, "c", high]).key == high, "maximum key survives neighbour deletion"] + store_close of s + remove_file of path + +r16b_store_boxed_checks of "/tmp/eigs1758_r16b_boxed_store.db" + test_summary of null diff --git a/tests/test_strict_math.sh b/tests/test_strict_math.sh index 357b5bb5f..a9ee6ecf7 100755 --- a/tests/test_strict_math.sh +++ b/tests/test_strict_math.sh @@ -147,7 +147,7 @@ run "SM17 strict leaves valid args alone" 1 0 "1" 'prin # The exclusions matter as much as the conversions: these 0s are DOCUMENTED # RETURN VALUES, not fail-soft guards, so strict must NOT make them raise. -# A sed over `return make_num(0)` would have broken both. +# A sed over `return make_float(0)` would have broken both. run "SM18 strict: try_parse(bad) still answers false" 1 0 "false" 'print of (try_parse of "!!!")' run "SM19 strict: unknown task id still not alive" 1 0 "false" 'print of (task_alive of 99999)' @@ -174,7 +174,7 @@ run "SM27 strict write_bytes type raises" 1 1 "write_bytes: expected" 'print # str_replace COERCES rather than returning a stand-in — a non-string element # silently became "" and the search ran over an empty string. It has no -# `return make_num(0)` to convert, so it is invisible to the classifier and +# `return make_float(0)` to convert, so it is invisible to the classifier and # needed STRICT_REQUIRE (raise under strict, no-op otherwise). Found by the # differential, not by the classifier: SM29 is why both harnesses exist. run "SM28 opt-out str_replace(num,..) unchanged" 0 0 "[]" \ @@ -205,7 +205,7 @@ run "SM35 strict: JSON false decodes to false (#1637)" 1 0 "false" \ # error, repaired scalar, trailing garbage) as a catchable `value` error naming # the position. EIGS_STRICT=0: byte-identical (SM36/SM37 pin the lenient walk). JSON # `false`/`null`/absent-key are ANSWERS and stay quiet in both modes. -run "SM36 opt-out json_path(bad number) still walks partial" 0 0 "[0]" \ +run "SM36 opt-out json_path(bad number) still walks partial" 0 0 "[0.0]" \ 'print of f"[{json_path of ["{\"a\": 1e", "a"]}]"' run "SM37 opt-out json_path(truncated array) still partial" 0 0 "[[1,2]]" \ 'print of f"[{json_path of ["{\"a\": [1, 2", "a"]}]"' @@ -230,22 +230,21 @@ run "SM46 strict: valid nested path still resolves" 1 0 "x" 'print of (json # --- #971 NaN-collapse: the reachable NaN sources raise under strict ------------ # Enumerated on the tree (the VM's own + - * / % cannot reach NaN from finite # operands — 0/0 and x%0 raise first, and no operand can hold an inf): `pow` -# of a negative base with a fractional exponent, `num of "nan"` (strtod), +# of a negative base with a fractional exponent, # `f64_from_bytes` of a NaN bit pattern, `matmul`'s inf-inf accumulation (list # and buffer paths), `tensor_load` of a file carrying NaN bytes, and the # elementwise `divide` by zero (pre-collapsed to 0 where `/` raises). EIGS_STRICT=0 # collapses to 0 + math_flags.invalid exactly as before (SM47-SM49 pin it). -run "SM47 opt-out pow(-8, 0.5) still 0" 0 0 "0" 'print of (pow of [0 - 8, 0.5])' -run "SM48 opt-out num(\"nan\") still 0 + invalid" 0 0 "0 true" \ -'local v is num of "nan" -print of f"{v} {(math_flags of null).invalid}"' -run "SM49a opt-out matmul(inf-inf) LIST path still collapses to 0 + invalid" 0 0 "[0] true" \ +run "SM47 opt-out pow(-8, 0.5) still 0" 0 0 "0.0" 'print of (pow of [0 - 8, 0.5])' +# N10: invalid literal text raises even under EIGS_STRICT=0. +run "SM48 N10 opt-out num nan refuses grammar" 0 1 "num: expected complete numeric-literal text" 'num of "nan"' +run "SM49a opt-out matmul(inf-inf) LIST path still collapses to 0 + invalid" 0 0 "[0.0] true" \ 'local r is matmul of [[[1e200, 1e200]], [[1e200], [0 - 1e200]]] print of f"{r} {(math_flags of null).invalid}"' # #1417 applies the same scalar-read rule to buffer and list results. The -# kernel may retain a raw NaN internally, but exposing it collapses it to 0 and -# sets invalid just as make_num does on the boxed path. -run "SM49b opt-out matmul(inf-inf) BUFFER read collapses to 0 + invalid" 0 0 "0 true" \ +# kernel may retain a raw NaN internally; indexed data reads refuse it in +# every strict mode (R1.6d-D1/N13), without a lenient INVALID flag write. +run "SM49b opt-out matmul(inf-inf) BUFFER read refuses" 0 1 "buffer_read_f64: expected a finite number" \ 'local m1 is buffer of [1, 2] m1[0] is 1e200 m1[1] is 1e200 @@ -256,7 +255,7 @@ local r is matmul of [m1, m2] print of f"{r[0]} {(math_flags of null).invalid}"' # #1131: canonicalization must visit every NaN and preserve intervening # finite results. On ARM the kernel's invalid-operation NaNs are positive. -run "SM49c opt-out matmul buffer guards each NaN and preserves finite neighbor" 0 0 "0 2e+200 0 true" \ +run "SM49c opt-out matmul buffer refuses each NaN and preserves finite neighbor" 0 0 "value 2e+200 value false" \ 'local a is buffer of [1, 2] a[0] is 1e200 a[1] is 1e200 @@ -268,10 +267,20 @@ b[3] is 0 - 1e200 b[4] is 1 b[5] is 0 - 1e200 local r is matmul of [a, b] -print of f"{r[0]} {r[1]} {r[2]} {(math_flags of null).invalid}"' +local first is "accepted" +local last is "accepted" +try: + first is r[0] +catch e: + first is e.kind +try: + last is r[2] +catch e: + last is e.kind +print of f"{first} {r[1]} {last} {(math_flags of null).invalid}"' run "SM50 strict pow(-8, 0.5) raises, named" 1 1 "pow: result is not a number" 'print of (pow of [0 - 8, 0.5])' run "SM51 strict elementwise pow raises" 1 1 "pow: result is not a number" 'print of (pow of [[0 - 8, 4], 0.5])' -run "SM52 strict num(\"nan\") raises, named" 1 1 "num: result is not a number" 'print of (num of "nan")' +run "SM52 strict num(\"nan\") raises, named" 1 1 "num: expected complete numeric-literal text" 'print of (num of "nan")' run "SM53 strict f64_from_bytes(NaN bits) raises" 1 1 "f64_from_bytes: result is not a number" \ 'print of (f64_from_bytes of ([127, 248, 0, 0, 0, 0, 0, 0]))' run "SM54 strict matmul list inf-inf raises" 1 1 "matmul: result is not a number" \ @@ -305,15 +314,15 @@ catch e: # --- #1438: finite-difference losses must be scalar numbers ----------------- BAD_LOSS='define bad(_) as: return "bad"' -run "T1438a opt-out numerical_grad keeps zero stand-in" 0 0 "[0]" \ +run "T1438a opt-out numerical_grad keeps zero stand-in" 0 0 "[0.0]" \ "$BAD_LOSS p is [1.0] print of (numerical_grad of [bad, p, 0.001])" -run "T1438b opt-out numerical_grad_rows keeps zero stand-in" 0 0 "[[0]]" \ +run "T1438b opt-out numerical_grad_rows keeps zero stand-in" 0 0 "[[0.0]]" \ "$BAD_LOSS m is [[1.0]] print of (numerical_grad_rows of [bad, m, [0], 0.001])" -run "T1438c opt-out numerical_grad_cols keeps zero stand-in" 0 0 "[[0]]" \ +run "T1438c opt-out numerical_grad_cols keeps zero stand-in" 0 0 "[[0.0]]" \ "$BAD_LOSS m is [[1.0]] print of (numerical_grad_cols of [bad, m, [0], 0.001])" @@ -332,7 +341,7 @@ run "T1438f strict numerical_grad_cols names non-number loss" 1 1 \ "$BAD_LOSS m is [[1.0]] print of (numerical_grad_cols of [bad, m, [0], 0.001])" -run "SM58 strict: num(\"inf\") still saturates (overflow, not NaN)" 1 0 "1e+308" 'print of (num of "inf")' +run "SM58 N10 strict num inf refuses grammar" 1 1 "num: expected complete numeric-literal text" 'print of (num of "inf")' run "SM59 strict: pow with an integer exponent is defined" 1 0 "-8" 'print of (pow of [0 - 2, 3])' run "SM60 strict: tensor_load of NaN bytes raises, named" 1 1 "tensor_load: result is not a number" \ 'write_bytes of ["/tmp/eigs_strict_nan_$$.tensor", [1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 127, 0, 0, 0, 0, 0, 0, 4, 64]] @@ -430,5 +439,17 @@ local r is matmul_bt of [a, b] print of f"zero:{r[0] == 0}:end"' done +# R1.6b N5: final readers preserve existing nonnumeric soft guards. +run "sign_extend soft invalid value" 0 0 'float' 'print of (num_type of (sign_extend of ["x", 8]))' +run "sign_extend soft invalid width" 0 0 'float' 'print of (num_type of (sign_extend of [255, "x"]))' +run "sign_extend bool remains a type error" 0 1 'bool' 'print of (sign_extend of [true, 8])' +# R1.6b-D2 / #1895 is unconditional in both call forms, unlike type guards. +for mode in 0 1; do + run "R16b-D2 min empty args mode=$mode" "$mode" 1 "min: expected at least one number" 'min of []' + run "R16b-D2 min empty list mode=$mode" "$mode" 1 "min: expected at least one number" 'min of ([])' + run "R16b-D2 max empty args mode=$mode" "$mode" 1 "max: expected at least one number" 'max of []' + run "R16b-D2 max empty list mode=$mode" "$mode" 1 "max: expected at least one number" 'max of ([])' +done + echo "STRICT: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ] diff --git a/tests/test_system.eigs b/tests/test_system.eigs index b44e0d899..8eaf09d0e 100644 --- a/tests/test_system.eigs +++ b/tests/test_system.eigs @@ -10,6 +10,25 @@ ri is random_int of [5, 10] assert of [ri >= 5, "RI1 random_int >= lo"] assert of [ri <= 10, "RI2 random_int <= hi"] +# One draw sees an off-by-one in the range one time in six (#1758 R1.1 critic, +# plant D: random_int answering lo+1..hi+1 passed). 300 draws stay inside +# [5, 10] and reach both ends; missing an end by chance is about 2^-78. +ri_out is 0 +ri_lo is 0 +ri_hi is 0 +ri_k is 0 +loop while ri_k < 300: + ri_v is random_int of [5, 10] + if ri_v < 5 or ri_v > 10: + ri_out is ri_out + 1 + if ri_v == 5: + ri_lo is 1 + if ri_v == 10: + ri_hi is 1 + ri_k is ri_k + 1 +assert of [ri_out == 0, "RI5 300 draws stay in [5, 10]"] +assert of [ri_lo == 1 and ri_hi == 1, "RI6 300 draws reach both 5 and 10"] + # #725: span exactly 2^31 is the widest range lrand48 can serve — must NOT raise ri_max is random_int of [0, 2147483647] assert of [ri_max >= 0, "RI3 random_int boundary span >= lo"] diff --git a/tests/test_temporal_param.eigs b/tests/test_temporal_param.eigs index c77bf35ad..3eb311bb0 100644 --- a/tests/test_temporal_param.eigs +++ b/tests/test_temporal_param.eigs @@ -116,32 +116,32 @@ assert_true of [(plain of 1) == 1, "no interrogation: same restore (#1064)"] # the tape. Before the fix the inline SET_LOCAL recorded nothing, so history # froze at the OSR threshold: prev of t answered 4999, not 199999. define hot(t) as: - for i in range of 200000: - t is t + 1 + for i in range of 200000.0: + t is t + 1.0 return prev of t define hot_local(q) as: local t is q - for i in range of 200000: - t is t + 1 + for i in range of 200000.0: + t is t + 1.0 return prev of t -assert_true of [(hot of 0) == 199999, "OSR-compiled slot writes reach the tape (param)"] -assert_true of [(hot_local of 0) == 199999, "local twin of (6)"] +assert_true of [(hot of 0.0) == 199999.0, "OSR-compiled slot writes reach the tape (param)"] +assert_true of [(hot_local of 0.0) == 199999.0, "local twin of (6)"] define hot_what(t) as: - for i in range of 20000: - t is t + 1 - return what is t at 131 -assert_true of [(hot_what of 0) == 20000, "what is at reads the LAST write, past OSR"] + for i in range of 20000.0: + t is t + 1.0 + return what is t at 131.0 +assert_true of [(hot_what of 0.0) == 20000.0, "what is at reads the LAST write, past OSR"] define hot_bind(p) as: - for p in range of 20000: - p is p + 0 + for p in range of 20000.0: + p is p + 0.0 return prev of p define hot_bind_local(q) as: local p is q - for p in range of 20000: - p is p + 0 + for p in range of 20000.0: + p is p + 0.0 return prev of p -assert_true of [(hot_bind of 1) == (hot_bind_local of 1), "hot binder over a param == its local twin"] -assert_true of [(hot_bind of 1) == 19999, "...and the value itself (main froze the tape at OSR: 19998)"] +assert_true of [(hot_bind of 1.0) == (hot_bind_local of 1.0), "hot binder over a param == its local twin"] +assert_true of [(hot_bind of 1.0) == 19999.0, "...and the value itself (main froze the tape at OSR: 19998)"] # ---- (6b) the history table is keyed by NAME across all frames. A callee's # same-named SLOT local must not rewrite the caller's `prev` -- only a chunk diff --git a/tests/test_tensor_buffer_inputs.eigs b/tests/test_tensor_buffer_inputs.eigs index b60aabc4f..781012d8e 100644 --- a/tests/test_tensor_buffer_inputs.eigs +++ b/tests/test_tensor_buffer_inputs.eigs @@ -267,5 +267,90 @@ assert_eq of [(type of zv), "buffer", "200k map: zeros of n is a buffer"] assert_eq of [(sum of zv), (sum of bv), "200k map: zeros of n == buffer of n"] assert_eq of [(norm of zv), (norm of bv), "200k map: norm agrees"] +# ============ #1758 R1.6b: N3/N4b exact arithmetic, C2/C4 and (A) ============ +define tensor_expect_value_error(thunk, label) as: + local kind is "" + try: + local result is thunk of null + catch e: + kind is e.kind + assert_eq of [kind, "value", label] + +tensor_big is 9007199254740993 +tensor_hi is 9223372036854775807 +tensor_lo is -9223372036854775807 - 1 +assert_eq of [(num_type of tensor_big), "int", "N3 tensor boxed fixture is int"] +assert_eq of [(num_type of tensor_hi), "int", "A tensor high fixture is int"] +assert_eq of [(num_type of tensor_lo), "int", "N4b tensor low fixture is int"] + +tensor_exact_sum is sum of [tensor_big, 1] +assert_eq of [tensor_exact_sum, 9007199254740994, "N3 all-int sum preserves digits above 2^53"] +assert_eq of [(num_type of tensor_exact_sum), "int", "N3 all-int sum returns int"] +assert_eq of [(num_type of (sum of [])), "int", "C2 empty argument-list sum is int zero"] +assert_eq of [(sum of []), 0, "C2 empty argument-list sum is zero"] +assert_eq of [(num_type of (sum of ([]))), "int", "C2 empty list sum is int zero"] +assert_eq of [(sum of ([])), 0, "C2 empty list sum is zero"] +assert_eq of [(sum of [[tensor_big], [1]]), 9007199254740994, "N3 nested all-int sum is exact"] +tensor_expect_value_error of [(nul) => sum of [tensor_hi, 1], "A sum raises on int64 overflow"] +tensor_expect_value_error of [(nul) => sum of [tensor_lo, -1], "A sum raises on int64 underflow"] + +# A late float decides the WHOLE reduction kind before a prefix adds. +tensor_late_float is sum of [tensor_hi, 1, 0.5] +assert_eq of [(num_type of tensor_late_float), "float", "N3 late float selects float sum before integer prefix"] +assert_eq of [tensor_late_float, 9223372036854775808.0, "N3 mixed sum uses float conversion from first element"] +tensor_nested_float is sum of [[tensor_hi, 1], [[0.5]]] +assert_eq of [(num_type of tensor_nested_float), "float", "N3 deeply nested float selects float sum"] +assert_eq of [tensor_nested_float, 9223372036854775808.0, "N3 nested mixed sum avoids int-prefix overflow"] +tensor_buf is buf_from_list of [0.5, 1.5] +tensor_late_buffer is sum of [tensor_hi, 1, tensor_buf] +assert_eq of [(num_type of tensor_late_buffer), "float", "N3 nested buffer selects float sum before prefix"] +assert_eq of [(num_type of (sum of tensor_buf)), "float", "Q2 buffer sum remains float"] +assert_eq of [(num_type of (sum of (buffer of 0))), "float", "Q2 empty buffer sum remains float"] + +tensor_add_list is add of [[tensor_big, 2], [1, 3]] +assert_eq of [tensor_add_list, [9007199254740994, 5], "C4 list addition is exact"] +assert_eq of [(num_type of tensor_add_list[0]), "int", "C4 boxed list-add result keeps int kind"] +tensor_sub_list is subtract of [[tensor_big, 5], [1, 2]] +assert_eq of [tensor_sub_list, [9007199254740992, 3], "C4 list subtraction is exact"] +tensor_mul_list is multiply of [[3037000499, 3], [3037000499, 2]] +assert_eq of [tensor_mul_list, [9223372030926249001, 6], "C4 list multiplication is exact"] +tensor_broadcast is add of [[[tensor_big, 1], [tensor_big, 2]], [1, 2]] +assert_eq of [tensor_broadcast, [[9007199254740994, 3], [9007199254740994, 4]], "C4 row-vector broadcasting preserves exact ints"] +assert_eq of [(num_type of tensor_broadcast[0][0]), "int", "C4 broadcast result includes boxed int"] + +# Each failure follows an already-created boxed result in the same output. +# ASan/LSan then exercise partial-container ownership in every changed op. +tensor_expect_value_error of [(nul) => add of [[tensor_big, tensor_hi], 1], "A add drops partial boxed list on overflow"] +tensor_expect_value_error of [(nul) => subtract of [[tensor_big, tensor_lo], 1], "A subtract drops partial boxed list on overflow"] +tensor_expect_value_error of [(nul) => multiply of [[tensor_big, tensor_hi], 2], "A multiply drops partial boxed list on overflow"] +tensor_expect_value_error of [(nul) => negative of [[tensor_big], [tensor_lo]], "N4b negative drops partial nested boxed list on INT64_MIN"] +assert_eq of [(negative of tensor_big), -9007199254740993, "N4b negative scalar keeps all integer digits"] +assert_eq of [(num_type of (negative of tensor_big)), "int", "N4b negative scalar keeps int kind"] + +tensor_mixed_add is add of [[tensor_big, 1.5], [1, 2]] +assert_eq of [(num_type of tensor_mixed_add[0]), "int", "C4 elementwise kind is decided per element"] +assert_eq of [(num_type of tensor_mixed_add[1]), "float", "C4 mixed list element returns float"] +tensor_buffer_add is add of [tensor_buf, 1] +assert_eq of [(type of tensor_buffer_add), "buffer", "Q2 buffer arithmetic keeps buffer container"] +assert_eq of [(num_type of tensor_buffer_add[0]), "float", "Q2 buffer arithmetic keeps float elements"] +tensor_mixed_buffer is add of [tensor_buf, [1, 2]] +assert_eq of [(type of tensor_mixed_buffer), "list", "Q2 mixed buffer/list arithmetic keeps list container"] +assert_eq of [(num_type of tensor_mixed_buffer[0]), "float", "Q2 materialized buffer element stays float"] +assert_eq of [(num_type of (divide of [6, 3])), "float", "non-b divide result remains float"] +assert_eq of [(num_type of (pow of [2, 3])), "float", "non-b pow result remains float"] +assert_eq of [(num_type of (sqrt of 4)), "float", "non-b sqrt result remains float"] +assert_eq of [(num_type of (exp of 0)), "float", "non-b exp result remains float"] +assert_eq of [(num_type of (log of 1)), "float", "non-b log result remains float"] +assert_eq of [(num_type of (mean of [1, 2])), "float", "non-b mean result remains float"] +assert_eq of [(num_type of (norm of [3, 4])), "float", "non-b norm result remains float"] + +# tensor_total used to validate bools over the whole tree before reducing. +tensor_bool_error is "" +try: + sum of [tensor_hi, 1, true] +catch e: + tensor_bool_error is e.kind +assert_eq of [tensor_bool_error, "type_mismatch", "non-numeric bool validation precedes int-prefix accumulation"] + test_summary of null print of "TENSOR_BUFFER_INPUTS_ALL_PASS" diff --git a/tests/test_tensor_buffer_ops.eigs b/tests/test_tensor_buffer_ops.eigs index d2bc428d9..81992fe5a 100644 --- a/tests/test_tensor_buffer_ops.eigs +++ b/tests/test_tensor_buffer_ops.eigs @@ -149,9 +149,12 @@ guard is buffer of [3, 4] raises of ["scatter_add column out of range", (nul) => scatter_add of [guard, [1, 4, 1], [1.0, 2.0, 3.0]], "index_range"] assert_eq of [sum of guard, 0.0, "scatter_add: a raise leaves dst untouched (no partial row 0 write)"] raises of ["scatter_add flat out of range", (nul) => scatter_add of [(buffer of 3), [3], 1.0], "index_range"] -raises of ["scatter_add huge list index", (nul) => scatter_add of [(buffer of 3), [1e300], 1.0], "index_range"] -raises of ["scatter_add fractional buffer index", (nul) => scatter_add of [(buffer of 3), (vec_buf of [[1.5]]), 1.0], "index_range"] -raises of ["scatter_add fractional index", (nul) => scatter_add of [(buffer of 3), [1.5], 1.0], "index_range"] +# N5 / R1.6d: exact index conversion precedes bounds checks. +raises of ["scatter_add huge list index", (nul) => scatter_add of [(buffer of 3), [1e300], 1.0], "value"] +# N5 / R1.6d: exact index conversion precedes bounds checks. +raises of ["scatter_add fractional buffer index", (nul) => scatter_add of [(buffer of 3), (vec_buf of [[1.5]]), 1.0], "value"] +# N5 / R1.6d: exact index conversion precedes bounds checks. +raises of ["scatter_add fractional index", (nul) => scatter_add of [(buffer of 3), [1.5], 1.0], "value"] raises of ["scatter_add non-buffer dst", (nul) => scatter_add of [[0.0, 0.0], [0], 1.0], "type_mismatch"] # length mismatches are LOUD, not truncated — a dropped gradient entry is a # silent wrong number, the same reason an out-of-range index raises diff --git a/tests/test_tensor_io_limit.sh b/tests/test_tensor_io_limit.sh index 715596db1..5bead5da7 100755 --- a/tests/test_tensor_io_limit.sh +++ b/tests/test_tensor_io_limit.sh @@ -60,7 +60,7 @@ else exit "$rc" fi -# Format v7 records only a tensor header verdict, not payload bytes. Replay +# Format v9 records only a tensor header verdict, not payload bytes. Replay # consumes that verdict for tape alignment and then refuses before fopen; the # following env_get proves the next observation remains aligned. cat >"$tmp/replay.eigs" <type == VAL_INT && VAL_I64_RAW(v) == exact[i], + "v9 int replay retains exact kind and int64 payload"); + eigs_value_release(v); + } + for (int i = 0; i < 2; i++) { + EigsValue *v = NULL; + int got = eigs_replay_take("sample", &v); + check(got && v && v->type == VAL_FLOAT && + VAL_F64_RAW(v) == (i ? -0.0 : 3.0) && + (!!signbit(VAL_F64_RAW(v)) == i), + "v9 float replay retains kind and signed zero"); + eigs_value_release(v); + } + check(eigs_set_replay_tape(NULL, 0, 0), "v9 numeric-kind replay releases its source"); if (!eigs_thread_switch(peer)) return 2; eigs_thread_detach(); eigs_state_destroy(peer); if (!eigs_thread_switch(root)) return 2; eigs_close(root); - printf("trace context: %d passed, %d failed (44 declared)\n", passed, failed); - return failed || total != 44; + printf("trace context: %d passed, %d failed (53 declared)\n", passed, failed); + return failed || total != 53; } diff --git a/tests/test_trace_context.sh b/tests/test_trace_context.sh index b32a734b3..bc95dbfaf 100644 --- a/tests/test_trace_context.sh +++ b/tests/test_trace_context.sh @@ -37,10 +37,10 @@ if [[ "$rc" -ne 0 || "$classification" -ne 2 ]]; then echo "FAIL: trace context child rc=$rc sanitizer=$classification" exit 1 fi -summaries=$(grep -Fxc 'trace context: 44 passed, 0 failed (44 declared)' "$out" || true) +summaries=$(grep -Fxc 'trace context: 53 passed, 0 failed (53 declared)' "$out" || true) [[ "$summaries" -eq 1 ]] || exit 1 passes=$(grep -c '^PASS:' "$out" || true) -[[ "$passes" -eq 44 ]] || exit 1 +[[ "$passes" -eq 53 ]] || exit 1 if grep -q '^FAIL:' "$out"; then exit 1 fi diff --git a/tests/test_trace_correspondence.c b/tests/test_trace_correspondence.c index 74c0a5f92..2006758f5 100644 --- a/tests/test_trace_correspondence.c +++ b/tests/test_trace_correspondence.c @@ -50,7 +50,7 @@ static Value *sensor(Value *arg) { pthread_mutex_unlock(&mu); Value *value = NULL; if (!trace_replay_take("ordinary_sensor", &value)) { - value = make_num(id + live_bias); + value = make_float(id + live_bias); trace_nondet_value("ordinary_sensor", value); /* deliberately before LINE */ pthread_mutex_lock(&mu); live_calls++; @@ -73,7 +73,7 @@ typedef struct { static Value *spawn_sensor(int id) { Value *args = make_list(2); Value *fn = make_builtin(sensor); - Value *number = make_num(id); + Value *number = make_float(id); list_append(args, fn); list_append(args, number); val_decref(fn); diff --git a/tests/test_trace_history_boundary.c b/tests/test_trace_history_boundary.c index 62a154fe9..0f86004f6 100644 --- a/tests/test_trace_history_boundary.c +++ b/tests/test_trace_history_boundary.c @@ -30,7 +30,7 @@ static void check(int ok, const char *name) { if (ok) passed++; else failed++; } static int number(EigsSlot value, double want) { - return slot_is_num(value) && SLOT_NUM_RAW(value) == want; + return slot_is_f64(value) && SLOT_F64_RAW(value) == want; } static int host_values(const PrevEntry *e) { return e->has_current && number(e->current, 22) && @@ -86,10 +86,10 @@ int main(void) { /* No history table exists yet. All four public assignment routes must * return before metadata or retention calls when the branch is active. */ g_sandbox_active = 1; - trace_assign(fresh, slot_from_num(1)); - trace_assign_filtered(fresh, slot_from_num(2)); - trace_assign_at_line(fresh, slot_from_num(3), 10); - trace_assign_filtered_at_line(fresh, slot_from_num(4), 20); + trace_assign(fresh, slot_from_f64(1)); + trace_assign_filtered(fresh, slot_from_f64(2)); + trace_assign_at_line(fresh, slot_from_f64(3), 10); + trace_assign_filtered_at_line(fresh, slot_from_f64(4), 20); trace_record_obs(fresh, .5, .25, .125); g_sandbox_active = 0; check(!g_prev_tab && g_prev_count == 0 && g_prev_cap == 0, @@ -99,13 +99,13 @@ int main(void) { /* Trusted unarmed producers still record; the ordinary filtered twin * still narrows. End this small control table before the host scenario. */ - trace_assign_at_line(fresh, slot_from_num(11), 10); + trace_assign_at_line(fresh, slot_from_f64(11), 10); EigsSlot direct = slot_null(); int direct_found = trace_query_at(0, fresh, 10, &direct); check(direct_found && number(direct, 11), "trusted unarmed producer records outside boundary"); if (direct_found) slot_decref(direct); - trace_assign_filtered_at_line(unarmed, slot_from_num(22), 20); + trace_assign_filtered_at_line(unarmed, slot_from_f64(22), 20); EigsSlot filtered = slot_null(); int filtered_found = trace_query_at(0, unarmed, 20, &filtered); check(!filtered_found, "ordinary filtered producer still omits unarmed values"); @@ -113,9 +113,9 @@ int main(void) { trace_thread_release(); trace_arm_occurrences_name(host); - trace_assign_at_line(host, slot_from_num(11), 10); + trace_assign_at_line(host, slot_from_f64(11), 10); trace_record_obs(host, .25, .125, .0625); - trace_assign_filtered_at_line(host, slot_from_num(22), 20); + trace_assign_filtered_at_line(host, slot_from_f64(22), 20); trace_record_obs(host, .5, .25, .125); check(g_prev_tab && g_prev_count == 1, "host producer creates one entry"); if (!g_prev_tab) goto done; @@ -135,8 +135,8 @@ int main(void) { PrevEntry *table = g_prev_tab; unsigned retained = retain_calls, incremented = incref_calls; g_sandbox_active = 1; - trace_assign(host, slot_from_num(33)); - trace_assign_filtered_at_line(host, slot_from_num(44), 30); + trace_assign(host, slot_from_f64(33)); + trace_assign_filtered_at_line(host, slot_from_f64(44), 30); trace_record_obs(host, 9, 8, 7); g_sandbox_active = 0; check(g_prev_tab == table && g_prev_count == count && g_prev_cap == capacity, @@ -154,17 +154,17 @@ int main(void) { * not override the history boundary. The same fixed name remains live. */ trace_set_sink(capture, &tape); g_sandbox_active = 1; - trace_assign(fresh, slot_from_num(5)); - trace_assign_filtered(fresh, slot_from_num(6)); - trace_assign_at_line(fresh, slot_from_num(7), 30); - trace_assign_filtered_at_line(fresh, slot_from_num(8), 40); + trace_assign(fresh, slot_from_f64(5)); + trace_assign_filtered(fresh, slot_from_f64(6)); + trace_assign_at_line(fresh, slot_from_f64(7), 30); + trace_assign_filtered_at_line(fresh, slot_from_f64(8), 40); trace_record_obs(host, 9, 8, 7); g_sandbox_active = 0; check(!tape.overflow && tape.assignments == 4 && - strstr(tape.bytes, "A 0 boundary_fresh=5\n") && - strstr(tape.bytes, "A 0 boundary_fresh=6\n") && - strstr(tape.bytes, "A 0 boundary_fresh=7\n") && - strstr(tape.bytes, "A 0 boundary_fresh=8\n"), + strstr(tape.bytes, "A 0 boundary_fresh=5.0\n") && + strstr(tape.bytes, "A 0 boundary_fresh=6.0\n") && + strstr(tape.bytes, "A 0 boundary_fresh=7.0\n") && + strstr(tape.bytes, "A 0 boundary_fresh=8.0\n"), "all assignment routes retain exact ordinary tape records"); check(g_arm_all && g_prev_tab == table && g_prev_count == count && g_prev_cap == capacity, @@ -174,7 +174,7 @@ int main(void) { check(host_observers(e) && host_occ_observers(e), "wildcard recording preserves both host observer snapshots"); - trace_assign_at_line(host, slot_from_num(33), 30); + trace_assign_at_line(host, slot_from_f64(33), 30); trace_record_obs(host, .75, .5, .25); check(number(e->current, 33) && number(e->prev, 22), "host producer resumes after boundary ends"); @@ -188,8 +188,8 @@ int main(void) { check(e->occ_total == 3 && e->occ_count == 3 && e->occ[2].obs_valid && e->occ[2].entropy == .75 && e->occ[2].dH == .5, "host occurrence observer recording resumes"); - trace_assign_tape_only(fresh, slot_from_num(9)); - check(tape.assignments == 6 && strstr(tape.bytes, "A 0 boundary_fresh=9\n") && + trace_assign_tape_only(fresh, slot_from_f64(9)); + check(tape.assignments == 6 && strstr(tape.bytes, "A 0 boundary_fresh=9.0\n") && g_prev_count == count, "explicit tape-only producer remains tape-only outside boundary"); diff --git a/tests/test_trace_mt.sh b/tests/test_trace_mt.sh index a7d7aad71..022657550 100755 --- a/tests/test_trace_mt.sh +++ b/tests/test_trace_mt.sh @@ -310,7 +310,7 @@ EIGS_REPLAY="$TMPDIR/large-workers-interleaved.tape" "$EIGS" \ "$TESTS_DIR/trace_mt_large_workers.eigs" >/dev/null 2>"$TMPDIR/large-workers-interleaved.err" interleaved_rc=$? if [ "$interleaved_rc" -eq 3 ] \ - && grep -q 'malformed v8 .*N value' "$TMPDIR/large-workers-interleaved.err" \ + && grep -q 'malformed v9 .*N value' "$TMPDIR/large-workers-interleaved.err" \ && grep -q 'refusing to replay' "$TMPDIR/large-workers-interleaved.err"; then ok "large-answer reader rejects a record interleaved between N and C" else @@ -371,7 +371,7 @@ else fi # Missing, signed, overflowed, and concatenated stream tags are all red. The -# header is copied from this v8 binary and each short N value is valid v8 +# header is copied from this v9 binary and each short N value is valid v9 # syntax, so these fixtures reach stream-tag validation rather than framing or # format-version rejection. printf 'print of (random of null)\n' > "$TMPDIR/take.eigs" diff --git a/tests/test_trace_read_fix2.sh b/tests/test_trace_read_fix2.sh index 2265ce8c8..2e145e95f 100755 --- a/tests/test_trace_read_fix2.sh +++ b/tests/test_trace_read_fix2.sh @@ -9,7 +9,7 @@ trap 'rm -rf "$TMP"' EXIT fail() { echo "FAIL: $*"; exit 1; } ok() { echo "PASS: $*"; } -# A hand-edited v7 metadata count must be rejected before allocation. +# A hand-edited v8 metadata count must be rejected before allocation. printf 'print of "small"\n' > "$TMP/unit.eigs" printf 'load_file of "%s/unit.eigs"\n' "$TMP" > "$TMP/main.eigs" EIGS_TRACE="$TMP/source.tape" "$EIGS" "$TMP/main.eigs" >/dev/null @@ -49,7 +49,7 @@ else fail "source suffix after NUL was silently discarded" fi -# tensor_load recorded only a verdict in v7, never its payload. Replay must +# tensor_load recorded only a verdict in v8, never its payload. Replay must # refuse rather than opening whatever file now occupies the path. cat > "$TMP/tensor-setup.eigs" < 3000: - return 0 + if Q == 1.0: + return 0.0 + n.d is n.d + 1.0 + if n.d > 3000.0: + return 0.0 local args is [A] return g of (args) g of ([1]) -n.d is 0 +n.d is 0.0 g of ([1]) print of "ok" diff --git a/tests/test_underarity_1661_trycatch.eigs b/tests/test_underarity_1661_trycatch.eigs index 5e005b9d3..f5ee94521 100644 --- a/tests/test_underarity_1661_trycatch.eigs +++ b/tests/test_underarity_1661_trycatch.eigs @@ -1,19 +1,22 @@ # #1661: the try/catch variant — the same under-arity recursion inside a # try, then a raise and catch. The stale slot read was freed again on the # way out (release: "free(): double free detected in tcache 2"). -n is {"d": 0} +# g writes FLOAT literals so the JIT compiles it: the JIT refuses int code +# until R5 (docs/design/phase2-numbers.md section 3). Its int twin returns +# at R5 (round plan, Round 5 obligations). +n is {"d": 0.0} define g(A, Q) as: - if Q == 1: - return 0 - n.d is n.d + 1 - if n.d > 3000: + if Q == 1.0: + return 0.0 + n.d is n.d + 1.0 + if n.d > 3000.0: throw of "deep" local args is [A] return g of (args) caught is 0 for round in [1, 2, 3]: - n.d is 0 + n.d is 0.0 try: g of ([1]) catch err: diff --git a/tests/test_validate.eigs b/tests/test_validate.eigs index 16b1e16a7..052a01306 100644 --- a/tests/test_validate.eigs +++ b/tests/test_validate.eigs @@ -1,6 +1,7 @@ # validate.is_number / is_integer — a decimal string needs a digit (#1235). # '.' and '-.' used to pass the character scan, and is_integer then treated -# num-of-them as zero. All-pass prints VALIDATE_ALL_PASS. +# num-of-them as zero. N10 now refuses that conversion. All-pass prints +# VALIDATE_ALL_PASS. import validate fails is 0 @@ -15,8 +16,9 @@ print of "=== VALIDATE (lib/validate.eigs) ===" # The pre-#1235 rule: optional leading '-', at most one '.', digits or a # dot, and no requirement that a digit appear. '.' and '-.' return 1, and -# the integer path accepts them because num of them is 0. The library must -# disagree with this scan on those two strings. +# the historical integer path accepted them because num of them was 0. +# The library must disagree with the scan; N10 now also rejects the old +# integer path's invalid numeric text (#1758 comment 6304880619). define _pre1235_is_number(value) as: if (type of value) != "str": return 0 @@ -46,8 +48,15 @@ define _pre1235_is_integer(value) as: check of ["pre-fix scan accepts '.'", _pre1235_is_number of ".", 1] check of ["pre-fix scan accepts '-.'", _pre1235_is_number of "-.", 1] -check of ["pre-fix is_integer accepts '.' as zero", _pre1235_is_integer of ".", true] -check of ["pre-fix is_integer accepts '-.' as zero", _pre1235_is_integer of "-.", true] +# N10: keep the historical character-scan witness, then prove the runtime +# refuses its digit-free false positives instead of silently producing zero. +for digit_free in [".", "-."]: + refused_kind is "" + try: + _pre1235_is_integer of digit_free + catch e: + refused_kind is e.kind + check of [f"N10 old integer path refuses '{digit_free}'", refused_kind, "value"] check of ["is_number rejects '.'", validate.is_number of ".", false] check of ["is_number rejects '-.'", validate.is_number of "-.", false] @@ -90,6 +99,16 @@ check of ["is_integer rejects two dots", validate.is_integer of "1.2.3", false] check of ["validate_all reports a failed check", validate.validate_all of [[["", validate.is_nonempty, "name"], ["x", validate.is_nonempty, "ok"]]], ["name"]] check of ["validate_all with every check passing", validate.validate_all of [[["a@b.co", validate.is_email, "email"]]], []] +# N10: testing letter membership must not parse each character as a number. +check of ["is_alpha accepts lowercase letters", validate.is_alpha of "abc", true] +check of ["is_alpha accepts uppercase letters", validate.is_alpha of "AZ", true] +check of ["is_alpha accepts mixed-case letters", validate.is_alpha of "Hello", true] +check of ["is_alpha rejects a digit", validate.is_alpha of "1", false] +check of ["is_alpha rejects a trailing digit", validate.is_alpha of "abc1", false] +check of ["is_alpha rejects whitespace", validate.is_alpha of "a b", false] +check of ["is_alpha rejects empty", validate.is_alpha of "", false] +check of ["is_alpha rejects a non-string", validate.is_alpha of 12, false] + if fails == 0: print of "VALIDATE_ALL_PASS" if fails > 0: diff --git a/tests/test_vm_run_bytecode.eigs b/tests/test_vm_run_bytecode.eigs index 67444fed9..74eddeb88 100644 --- a/tests/test_vm_run_bytecode.eigs +++ b/tests/test_vm_run_bytecode.eigs @@ -50,6 +50,20 @@ define assert_bad_descriptor(desc, reason, label) as: # 2 + 3 -> 5 assert_eq of [vm_run_bytecode of [ABI, [CONST,0,0, CONST,1,0, ADD, RETURN], [2, 3]], 5, "arith add"] +# #1758 R1.3: new int opcodes append to the ABI; INT_CONST validates its +# pool entry before execution, while the old numeric zero/one remain floats. +INT_ZERO is 97 +INT_ONE is 98 +INT_CONST is 99 +assert_eq of [num_type of (vm_run_bytecode of [ABI, [2, RETURN], []]), "float", "#1758: legacy NUM_ZERO remains float"] +assert_eq of [num_type of (vm_run_bytecode of [ABI, [3, RETURN], []]), "float", "#1758: legacy NUM_ONE remains float"] +assert_eq of [num_type of (vm_run_bytecode of [ABI, [CONST,0,0, RETURN], [3.0]]), "float", "#1758: legacy CONST preserves a float constant"] +assert_eq of [num_type of (vm_run_bytecode of [ABI, [INT_ZERO, RETURN], []]), "int", "#1758: appended INT_ZERO produces int"] +assert_eq of [num_type of (vm_run_bytecode of [ABI, [INT_ONE, RETURN], []]), "int", "#1758: appended INT_ONE produces int"] +assert_eq of [vm_run_bytecode of [ABI, [INT_CONST,0,0, RETURN], [9007199254740993]], 9007199254740993, "#1758: INT_CONST preserves exact large int"] +assert_bad_descriptor of [[ABI, [INT_CONST,0,0, RETURN], [3.0]], "not an int", "#1758: INT_CONST rejects a float pool entry"] +assert_bad_descriptor of [[ABI, [INT_CONST,0,0, RETURN], ["3"]], "not an int", "#1758: INT_CONST rejects a string pool entry"] + # string concat "a" + "b" -> "ab" assert_eq of [vm_run_bytecode of [ABI, [CONST,0,0, CONST,1,0, ADD, RETURN], ["a", "b"]], "ab", "string concat via ADD"] @@ -215,11 +229,11 @@ JUMP_BACK is 30 RETURN_NULL is 41 LOOP_STALL_CHECK is 62 LOOP_CAP_CHECK is 63 -LOOP_ENTER is 97 -LOOP_STALL_NAMED is 98 -LOOP_CAP_NAMED is 99 -LOOP_ENTER_NAMED is 100 -LOOP_EXIT_NAMED is 101 +LOOP_ENTER is 101 +LOOP_STALL_NAMED is 102 +LOOP_CAP_NAMED is 103 +LOOP_ENTER_NAMED is 104 +LOOP_EXIT_NAMED is 105 # Each of the four *_NAMED opcodes is refused with a reason that names it. assert_bad_descriptor of [[ABI, [LOOP_ENTER_NAMED,0,0, RETURN_NULL], []], "compiler-only", "verify: LOOP_ENTER_NAMED is compiler-only"] @@ -233,11 +247,11 @@ assert_bad_descriptor of [[ABI, [NUM_ZERO, NUM_ZERO, LOOP_EXIT_NAMED,0,0, POP, R sr_exit_named is sandbox_run of [[ABI, [NUM_ZERO, NUM_ZERO, LOOP_EXIT_NAMED,0,0, POP, RETURN_NULL], []], 5] assert_eq of [sr_exit_named["ok"], false, "sandbox: LOOP_EXIT_NAMED descriptor refused (ok=0, no crash)"] -# Legacy no-slot loop opcodes (62/63/97) are NOT compiler-only and still run. -# OP_LOOP_ENTER (97) resets the shared counter and publishes zero; a bounded +# Legacy no-slot loop opcodes (62/63/101) are NOT compiler-only and still run. +# OP_LOOP_ENTER (101) resets the shared counter and publishes zero; a bounded # loop guarded by OP_LOOP_CAP_CHECK (63) / OP_LOOP_STALL_CHECK (62) still runs. le_ok is vm_run_bytecode of [ABI, [LOOP_ENTER, NUM_ONE, RETURN], []] -assert_eq of [le_ok, 1, "legacy LOOP_ENTER (97) still runs in assembled bytecode"] +assert_eq of [le_ok, 1, "legacy LOOP_ENTER (101) still runs in assembled bytecode"] # NUM_ONE; JUMP_IF_FALSE +6; LOOP_CAP_CHECK +3; JUMP_BACK 10; RETURN_NULL — # a runaway bounded by the sandbox cap: still accepted (ok reported capped). cap_legacy is sandbox_run of [[ABI, [NUM_ONE, JUMP_IF_FALSE,6,0, LOOP_CAP_CHECK,3,0, JUMP_BACK,10,0, RETURN_NULL], []], 50] @@ -284,7 +298,7 @@ assert_bad_descriptor of [[ABI, [LINE,7,0,0], []], "truncated LINE operand", "ve # #1425 fix2: the shared-temporal sandbox walker must use the verifier's # operand width. Before the fix it decoded BINARY_LINE's line bytes as opcodes # and dereferenced constant index 65535, crashing the whole process. -binary_line_walk is sandbox_run of [[ABI, [0,0,0, 0,1,0, 102,25,255,255,0, 4,103,40], [2,3]], 1000] +binary_line_walk is sandbox_run of [[ABI, [0,0,0, 0,1,0, 106,25,255,255,0, 4,107,40], [2,3]], 1000] assert_eq of [binary_line_walk["ok"], false, "sandbox refuses malformed BINARY_LINE descriptor without crashing (#1425 fix2)"] # --- #704: the ABI revision stamp itself --- diff --git a/tests/tsan_mt_clear_respawn.eigs b/tests/tsan_mt_clear_respawn.eigs index c4d17e7dd..cfe1940f3 100644 --- a/tests/tsan_mt_clear_respawn.eigs +++ b/tests/tsan_mt_clear_respawn.eigs @@ -2,6 +2,9 @@ # (collector, JIT and plain refcounts back on), then re-enters it on the next # spawn. TSan must see no race across either transition, including a worker # that spawns and joins its own worker. +# `hot` and the module loops (OSR, after each join) count in FLOATS so they +# compile: the JIT refuses int code until R5 (docs/design/phase2-numbers.md +# section 3). Its int twin returns at R5 (round plan, Round 5 obligations). define sq(n) as: return n * n define spawner(n) as: @@ -13,27 +16,27 @@ define make_counter(start) as: return count return value define hot(n) as: - acc is 0 - k is 0 + acc is 0.0 + k is 0.0 loop while k < n: acc is acc + k - k is k + 1 + k is k + 1.0 return acc total is 0 -round is 0 -loop while round < 3: +round is 0.0 +loop while round < 3.0: hs is [] for k in range of 4: append of [hs, spawn of [sq, k + round]] append of [hs, spawn of [spawner, round]] for h in hs: total is total + (thread_join of h) - j is 0 - loop while j < 5000: + j is 0.0 + loop while j < 5000.0: local c is make_counter of j - j is j + 1 - total is total + (hot of 2000) - (hot of 2000) - round is round + 1 + j is j + 1.0 + total is total + (hot of 2000.0) - (hot of 2000.0) + round is round + 1.0 if total == 103: print of "All tests passed" else: diff --git a/tests/tsan_spawn_emitted_jit.eigs b/tests/tsan_spawn_emitted_jit.eigs index 42ebdf2d3..d13c9b52f 100644 --- a/tests/tsan_spawn_emitted_jit.eigs +++ b/tests/tsan_spawn_emitted_jit.eigs @@ -3,16 +3,19 @@ # native RET diagnostic after 80 warm calls. Spawn occurs at module level, # followed by hot calls while MT mode is on; this does not cover spawn from # inside an active native caller. +# Every literal and argument is a FLOAT: the JIT refuses int code until R5 +# (design phase2-numbers.md section 3), and this fixture exists to race a +# spawn against EMITTED code. Its int twin returns at R5 (round plan, R5). define hot(v) as: - local doubled is v * 2 - return doubled + 1 -i is 0 -loop while i < 80: + local doubled is v * 2.0 + return doubled + 1.0 +i is 0.0 +loop while i < 80.0: hot of i - i is i + 1 + i is i + 1.0 define worker() as: - return hot of 20 + return hot of 20.0 h is spawn of worker -main_value is hot of 10 -if (thread_join of h) != 41 or main_value != 21: +main_value is hot of 10.0 +if (thread_join of h) != 41.0 or main_value != 21.0: throw of "emitted JIT call returned wrong value" diff --git a/tests/valgrind_smoke.sh b/tests/valgrind_smoke.sh index 3b96fe6ec..b9d6af65e 100755 --- a/tests/valgrind_smoke.sh +++ b/tests/valgrind_smoke.sh @@ -60,10 +60,16 @@ if command -v timeout >/dev/null 2>&1; then VG_TMO="timeout 600"; PRE_TMO="timeo elif command -v gtimeout >/dev/null 2>&1; then VG_TMO="gtimeout 600"; PRE_TMO="gtimeout 60"; fi if [ "$FULL" -eq 1 ]; then + ROOT=$(cd "$(dirname "$0")/.." && pwd) + OWNED_CORPUS=$(mktemp) || exit 1 + trap 'rm -f "$OWNED_CORPUS"' EXIT + (cd "$ROOT" && find tests -type f -name '*.eigs' | LC_ALL=C sort) > "$OWNED_CORPUS" + python3 "$ROOT/tools/phase2_corpus_exclude.py" "$ROOT" "$OWNED_CORPUS" || exit 1 SMOKE=("${PROGS[@]}") CANDIDATES=() for f in test_*.eigs; do [ -f "$f" ] || continue + grep -qxF "tests/$f" "$OWNED_CORPUS" || continue CANDIDATES+=("${f%.eigs}") done if [ "${#CANDIDATES[@]}" -lt "${#SMOKE[@]}" ]; then diff --git a/tools/codspeed_targets_check.sh b/tools/codspeed_targets_check.sh index c282b62fd..4a87203cb 100755 --- a/tools/codspeed_targets_check.sh +++ b/tools/codspeed_targets_check.sh @@ -27,7 +27,7 @@ CFG="${CODSPEED_CFG:-$ROOT/codspeed.yml}" WF="${CODSPEED_WF:-$ROOT/.github/workflows/codspeed.yml}" GUARD="${CODSPEED_GUARD:-$ROOT/tools/codspeed_unit_guard.py}" -TARGET_COUNT=13 # the set is the claim +TARGET_COUNT=19 # the set is the claim CONSUMER_IDS="dmg-cpu-instrs dmg-cpu-instrs-vm" # the rows a proxy cannot replace CONSUMER_DIR=".codspeed-consumers/DMG" # what the fetch step populates diff --git a/tools/enrolment_check.sh b/tools/enrolment_check.sh index ecd779572..d9462e44d 100644 --- a/tools/enrolment_check.sh +++ b/tools/enrolment_check.sh @@ -16,6 +16,21 @@ # program operand of an "$EIGS"/eigenscript run; a glob in a differential is # not invocation enrolment. # +# The same scan enrols tools/ (mechanical-gates section 151, #1758 R1.3: Phase +# 2's two gate tools had run only on builders' machines). Every top-level +# tools/*.sh and tools/*.py is an entry point, and so is each tools/

/ +# package holding a script, by the directory listing, never a hand-kept list. +# Each must be reached from a .github/workflows/*.yml step through invocations +# (the suite, a top-level tests/, tools/ or bench/ shell script, a script beside it +# called as "$(dirname "$0")/x"), Make recipe dependencies, and Python imports +# or execution calls, or be listed in the same exemptions file as local-only +# with its reason. A package counts as reached when CI runs any of its +# scripts. Static local uses: ./... steps also reach composite action manifests +# (action.yml or action.yaml), including nested local actions. Action manifests +# are graph sources, never roots: an unused action cannot enrol a tool. +# bench/*.sh also provide graph edges when reached; they are not new +# enrollment candidates (the candidate population remains tests/ and tools/). +# # Usage: tools/enrolment_check.sh [--selftest] exit 0 pass, 1 fail, 2 no instrument # ENROL_ROOT overrides the repository root (the selftest points it at a copy). set -u @@ -30,7 +45,8 @@ if [ "${1:-}" = "--selftest" ]; then mkdir -p "$ST/tests/sections" "$ST/tools" "$ST/.github" "$ST/bench" cp tests/*.sh tests/*.py "$EXEMPT" "$ST/tests/" && cp tests/sections/*.sh "$ST/tests/sections/" \ && cp tools/*.sh tools/*.py tools/selftests.txt "$ST/tools/" && cp -R .github/. "$ST/.github/" \ - && cp Makefile "$ST/" + && cp Makefile "$ST/" && cp -R bench/. "$ST/bench/" + for d in tools/*/; do cp -R "${d%/}" "$ST/tools/"; done cp tests/*.eigs "$ST/tests/" (cd "$ST" && git init -q && git add .) # #2060 is not present on every base branch; keep that known integration @@ -46,6 +62,10 @@ if [ "${1:-}" = "--selftest" ]; then else echo " SELFTEST FAIL: $1 (rc=$rc, want $2 + '$3')"; printf '%s\n' "$out" | sed 's/^/ /'; bad=1; fi } case_ "control: the unmodified tree passes" 0 "PASS: test-enrolment" + cp "$ST/tools/enrolment_edges.py" "$ST/tools/enrolment_edges.py.good" + printf 'raise SystemExit(7)\n' > "$ST/tools/enrolment_edges.py" + case_ "a failed edge extractor aborts instead of passing an empty graph" 2 "edge derivation failed" + mv "$ST/tools/enrolment_edges.py.good" "$ST/tools/enrolment_edges.py" # A linked checkout has a .git file, not a directory. It must be exactly # as acceptable to the population derivation as the primary checkout. (cd "$ST" && git -c user.name=selftest -c user.email=selftest@example.invalid commit -qm control) @@ -65,6 +85,33 @@ if [ "${1:-}" = "--selftest" ]; then else echo " SELFTEST FAIL: non-repository directory was not aborted (rc=$nonrepo_rc)"; bad=1 fi + mkdir -p "$ST/.github/actions/zz_enrol_leaf" "$ST/.github/actions/zz_enrol_parent" + printf 'raise SystemExit(0)\n' > "$ST/tools/zz_plant_gate.py" + # Keep fixture commands in printf arguments: literal run: lines in this + # shell source would themselves be scanned as invocations. + printf '%s\n' 'name: planted leaf' 'runs:' ' using: composite' ' steps:' \ + ' - shell: bash' ' run: python3 "$GITHUB_WORKSPACE/tools/zz_plant_gate.py"' \ + > "$ST/.github/actions/zz_enrol_leaf/action.yaml" + printf '%s\n' 'name: planted parent' 'runs:' ' using: composite' ' steps:' \ + ' - uses: ./.github/actions/zz_enrol_leaf' \ + > "$ST/.github/actions/zz_enrol_parent/action.yml" + printf '%s\n' 'name: planted workflow' 'on: push' 'jobs:' ' plant:' \ + ' runs-on: ubuntu-latest' ' steps:' \ + ' - uses: "./.github/actions/zz_enrol_parent"' \ + > "$ST/.github/workflows/zz_enrol.yaml" + case_ "control: workflow and nested local actions reach their helpers" 0 "PASS: test-enrolment" + # A bench script is an intermediate edge, never a root by itself. + printf 'raise SystemExit(0)\n' > "$ST/tools/zz_bench_gate.py" + printf '%s\n' '#!/bin/bash' 'python3 "$(dirname "$0")/../tools/zz_bench_gate.py"' \ + > "$ST/bench/zz_enrol.sh" + printf '%s\n' 'name: planted bench workflow' 'on: push' 'jobs:' ' bench:' \ + ' runs-on: ubuntu-latest' ' steps:' \ + ' - run: bash bench/zz_enrol.sh' \ + > "$ST/.github/workflows/zz_enrol_bench.yaml" + case_ "workflow reaches a helper through its bench script" 0 "PASS: test-enrolment" + rm "$ST/.github/workflows/zz_enrol_bench.yaml" + case_ "an orphan bench script cannot enrol its helper" 1 "tools/zz_bench_gate.py is invoked by nothing" + rm "$ST/bench/zz_enrol.sh" "$ST/tools/zz_bench_gate.py" printf '#!/bin/bash\necho "PASS: x"\n' > "$ST/tests/test_zz_plant.sh" case_ "an uninvoked tests/test_zz_plant.sh is red and named" 1 "tests/test_zz_plant.sh is invoked by nothing" rm "$ST/tests/test_zz_plant.sh" @@ -75,6 +122,31 @@ if [ "${1:-}" = "--selftest" ]; then rm "$ST/tests/test_clean.sh" echo 'tests/test_cli.sh | planted: test_cli.sh IS invoked' >> "$ST/$EXEMPT" case_ "an exemption for an invoked script is stale: red" 1 "exempts tests/test_cli.sh, which IS invoked" + sed -i.bak '$d' "$ST/$EXEMPT" && rm -f "$ST/$EXEMPT.bak" + printf '#!/bin/bash\nexit 1\n' > "$ST/tools/zz_plant_gate.sh" + mkdir -p "$ST/.github/actions/zz_enrol_orphan" + printf '%s\n' 'name: planted orphan' 'runs:' ' using: composite' ' steps:' \ + ' - shell: bash' ' run: bash tools/zz_plant_gate.sh' \ + > "$ST/.github/actions/zz_enrol_orphan/action.yml" + printf '%s\n' ' - run: true' ' env:' \ + ' uses: ./.github/actions/zz_enrol_orphan' \ + ' - run: |' ' cat <> "$ST/.github/workflows/zz_enrol.yaml" + # A scalar before any real steps makes scalar suppression independent of + # the active-steps filter: dropping either guard must expose this orphan. + printf '%s\n' 'name: |' ' steps:' \ + ' - uses: ./.github/actions/zz_enrol_orphan' 'on: push' 'jobs:' \ + ' noop:' ' runs-on: ubuntu-latest' ' steps:' ' - run: true' \ + > "$ST/.github/workflows/zz_enrol_scalar.yml" + case_ "env and scalar text do not invoke an orphan action" 1 "tools/zz_plant_gate.sh is invoked by nothing" + rm "$ST/tools/zz_plant_gate.sh" + sed -i.bak 's#uses: "./.github/actions/zz_enrol_parent"#run: true#' "$ST/.github/workflows/zz_enrol.yaml" + rm -f "$ST/.github/workflows/zz_enrol.yaml.bak" + case_ "removing workflow uses leaves tools/zz_plant_gate.py unenrolled" 1 "tools/zz_plant_gate.py is invoked by nothing" + rm "$ST/tools/zz_plant_gate.py"; mkdir "$ST/tools/zz_pkg"; printf 'raise SystemExit(1)\n' > "$ST/tools/zz_pkg/gate.py" + case_ "an uninvoked package tools/zz_pkg/ is red and named" 1 "tools/zz_pkg is invoked by nothing" + rm -rf "$ST/tools/zz_pkg" sed -i.bak '/tests\/test_cli.sh | planted/d' "$ST/$EXEMPT" && rm "$ST/$EXEMPT.bak" printf 'test_summary of null\n' > "$ST/tests/test_zz_asserting.eigs" (cd "$ST" && git add tests/test_zz_asserting.eigs) @@ -104,12 +176,15 @@ fi [ -f tests/run_all_tests.sh ] || { echo "test-enrolment: ABORTED: no tests/run_all_tests.sh"; exit 2; } EDGES=$(mktemp "${TMPDIR:-/tmp}/eigs_enrol.XXXXXX") || exit 2 -trap 'rm -f "$EDGES"' EXIT -# Derive the graph before taking its transitive closure. Keeping extraction -# separate makes every supported edge independently observable in self-tests. -python3 tools/enrolment_edges.py . | sort -u > "$EDGES" || { +trap 'rm -f "$EDGES" "$EDGES.raw"' EXIT +# Derive the graph before taking its transitive closure. Keep the extractor's +# exit status: a successful sort cannot stand in for a failed Python process. +python3 tools/enrolment_edges.py . > "$EDGES.raw" || { echo "test-enrolment: ABORTED: edge derivation failed"; exit 2; } +sort -u "$EDGES.raw" > "$EDGES" || { + echo "test-enrolment: ABORTED: edge sorting failed"; exit 2; +} # Commands in the self-test table are reachable through its driver. awk -F '[|]' '$0 !~ /^#/ { n = split($2, a, /[[:space:]]+/) @@ -118,7 +193,7 @@ awk -F '[|]' '$0 !~ /^#/ { REACHED=$(awk -F'\t' '{ adj[$1] = adj[$1] " " $2 } END { q[1] = "tests/run_all_tests.sh"; n = 1 for (f in adj) if (f ~ /^tests\/sections\/[^\/]+\.sh$/) q[++n] = f - for (f in adj) if (f ~ /^\.github\//) q[++n] = f + for (f in adj) if (f ~ /^\.github\/workflows\/[^\/]+\.ya?ml$/) q[++n] = f for (h = 1; h <= n; h++) { m = split(adj[q[h]], t, " ") for (j = 1; j <= m; j++) if (!(t[j] in seen)) { seen[t[j]] = 1; q[++n] = t[j]; print t[j] } } }' \ "$EDGES") || { echo "test-enrolment: ABORTED: reachability scan failed"; exit 2; } @@ -153,7 +228,12 @@ INSIDE_WORK_TREE=$(git -c safe.directory="$PWD" rev-parse --is-inside-work-tree [ "$INSIDE_WORK_TREE" = true ] || { echo "test-enrolment: ABORTED: git work tree required for asserting-test population (git rev-parse said: $INSIDE_WORK_TREE; cwd: $PWD)"; exit 2; } -ASSERTING=$(git -c safe.directory="$PWD" ls-files 'tests/*.eigs' | while IFS= read -r f; do +ASSERT_FILES=$(git -c safe.directory="$PWD" ls-files 'tests/*.eigs') || { + echo "test-enrolment: ABORTED: asserting-test path enumeration failed"; exit 2; +} +# During an unresolved merge git lists one pathname per index stage. Count +# test programs once, exactly as the same tree will be counted after staging. +ASSERTING=$(printf '%s\n' "$ASSERT_FILES" | sort -u | while IFS= read -r f; do grep -q 'test_summary of' "$f" && printf '%s\n' "$f" done) assert_total=0; assert_ok=0; assert_ex=0 diff --git a/tools/enrolment_edges.py b/tools/enrolment_edges.py index ac270b9b7..2c3d6faf7 100644 --- a/tools/enrolment_edges.py +++ b/tools/enrolment_edges.py @@ -12,12 +12,13 @@ import re import shlex import sys +from functools import lru_cache from pathlib import Path import yaml from _python_execution import EXECUTION_CALL_NAMES, name_of -from _derive_variants import shell_lines +from _derive_variants import _subst_bodies, shell_lines ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve() @@ -76,68 +77,66 @@ def eigs_program_path(word: str, source: Path) -> str | None: return None -def _substitutions(words): - """Commands run by a `$(...)` embedded in the words of one command. - - `x=$("$TESTS_DIR/../tools/t.sh" arg)` is one shlex word starting with an - assignment, so the caller would discard it as an env prefix; the command it - substitutes still executes and is an edge (#1679's resolver call).""" - for i, w in enumerate(words): - if "$(" not in w: - continue - sub = [w.split("$(")[-1]] + list(words[i + 1:]) - for j, x in enumerate(sub): - if x.endswith(")"): - sub = sub[:j + 1] - sub[-1] = sub[-1].rstrip(")") - break - sub = [x for x in sub if x] - if sub: - yield sub - - def shell_commands(text: str): - """Yield shell simple commands with wrappers removed.""" + """Yield fresh command lists; heredoc state belongs to this text scan.""" logical = re.sub(r"\\\n\s*", " ", text) for _, raw in shell_lines(logical): - try: - parts, start, quote, escaped = [], 0, None, False - i = 0 - while i < len(raw): - char = raw[i] - if escaped: - escaped = False - elif char == "\\" and quote != "'": - escaped = True - elif quote: - if char == quote: - quote = None - elif char in "'\"": - quote = char - elif char in ";|`" or raw[i:i + 2] == "&&": - parts.append(raw[start:i]); i += 1 if raw[i:i + 2] == "&&" else 0; start = i + 1 - i += 1 - parts.append(raw[start:]) - for part in parts: - words = shlex.split(part, comments=True) - yield from _substitutions(words) - while words and (re.match(r"^[A-Za-z_]\w*=", words[0]) or words[0] in {"if", "then", "elif", "else", "do", "while", "until", "time", "!", "{", "command", "exec", "nohup", "sudo", "xvfb-run"}): - words.pop(0) - if words and words[0] == "env": + for words in shell_line_commands(raw): + yield list(words) + + +@lru_cache(maxsize=8192) +def shell_line_commands(raw: str) -> tuple[tuple[str, ...], ...]: + """Cache only pure parsing, never filesystem lookups or mutable generators. + + Suite recovery windows overlap and repeat command lines. shell_lines still + decides which lines are executable in each window; the same resulting raw + line always parses identically. Callers receive fresh lists so removing + launcher options cannot mutate a later cache hit. + """ + commands = [] + # Extract before shlex removes single-quote provenance. Only balanced + # executable bodies are commands; suffix path text is an outer operand. + for body in _subst_bodies(raw): + commands.extend(tuple(words) for words in shell_commands(body)) + try: + parts, start, quote, escaped = [], 0, None, False + i = 0 + while i < len(raw): + char = raw[i] + if escaped: + escaped = False + elif char == "\\" and quote != "'": + escaped = True + elif quote: + if char == quote: + quote = None + elif char in "'\"": + quote = char + elif char in ";|`" or raw[i:i + 2] == "&&": + parts.append(raw[start:i]); i += 1 if raw[i:i + 2] == "&&" else 0; start = i + 1 + i += 1 + parts.append(raw[start:]) + for part in parts: + words = shlex.split(part, comments=True) + while words and (re.match(r"^[A-Za-z_]\w*=", words[0]) or words[0] in {"if", "then", "elif", "else", "do", "while", "until", "time", "!", "{", "command", "exec", "nohup", "sudo", "xvfb-run"}): + words.pop(0) + if words and words[0] == "env": + words.pop(0) + while words and (words[0].startswith("-") + or re.match(r"^[A-Za-z_]\w*=", words[0]) + or (re.match(r'^\$\{?[A-Za-z_]\w*(?:\[@\])?\}?$', words[0]) + and words[0] not in {"$EIGS", "${EIGS}"})): words.pop(0) - while words and (words[0].startswith("-") - or re.match(r"^[A-Za-z_]\w*=", words[0]) - or (re.match(r'^\$\{?[A-Za-z_]\w*(?:\[@\])?\}?$', words[0]) - and words[0] not in {"$EIGS", "${EIGS}"})): - words.pop(0) - if words and words[0] == "timeout": + if words and words[0] == "timeout": + words.pop(0) + while words and (words[0].startswith("-") or re.match(r"^\d+(?:\.\d+)?[smhd]?$", words[0])): words.pop(0) - while words and (words[0].startswith("-") or re.match(r"^\d+(?:\.\d+)?[smhd]?$", words[0])): - words.pop(0) - if words: - yield words - except ValueError: - continue + if words: + commands.append(tuple(words)) + except ValueError: + pass + return tuple(commands) def normalized_command_index(words: list[str]) -> int: @@ -156,7 +155,9 @@ def normalized_command_index(words: list[str]) -> int: "run_bounded": 1, "gfx_headless_run": 1, } - i = 1 + metadata.get(words[0], -1) + i = 0 + while i < len(words) and words[i] in metadata: + i += 1 + metadata[words[i]] while i < len(words) and re.match(r"^[A-Za-z_]\w*=", words[i]): i += 1 if i < len(words) and words[i] == "$EIGS_TMO": @@ -202,7 +203,9 @@ def shell_edges(source: Path, text: str, src_name: str | None = None) -> None: if words[0] in {"bash", "sh", "dash", "python", "python3", "source", "."}: words = words[1:] while words and words[0].startswith("-"): - words.pop(0) + option = words.pop(0) + if option in {"-W", "-X"} and words: + words.pop(0) if words: dst = executable_path(words[0], source) if dst: @@ -354,12 +357,32 @@ def make_edges(path: Path) -> None: # members is reached. The package directory itself is the candidate. emit(source.as_posix(), "/".join(source.parts[:2])) if source.parts and source.parts[0] == ".github": - for action in re.findall(r"uses:\s*\./([^\s#]+)", text): - target = Path(action) - if (ROOT / target).is_dir(): - target /= "action.yml" - if (ROOT / target).is_file(): - emit(source.as_posix(), target.as_posix()) + # Only real step uses entries create local-action edges. YAML scalar + # contents and env mappings are data, even if they spell `uses:`. + data = yaml.safe_load(text) + groups = [] + if isinstance(data, dict): + jobs = data.get("jobs", {}) + if isinstance(jobs, dict): + groups.extend(job.get("steps", []) for job in jobs.values() if isinstance(job, dict)) + runs = data.get("runs", {}) + if isinstance(runs, dict): + groups.append(runs.get("steps", [])) + for steps in groups: + if not isinstance(steps, list): + continue + for step in steps: + action = step.get("uses") if isinstance(step, dict) else None + if not isinstance(action, str) or not action.startswith("./"): + continue + target = Path(action) + if ".." in target.parts: + continue + targets = [target / "action.yml", target / "action.yaml"] if (ROOT / target).is_dir() else [target] + for manifest in targets: + if (ROOT / manifest).is_file(): + emit(source.as_posix(), manifest.as_posix()) + break makefile = ROOT / "Makefile" if makefile.is_file(): diff --git a/tools/freestanding_allowlist.txt b/tools/freestanding_allowlist.txt index b0bcd0f8c..0f60c0234 100644 --- a/tools/freestanding_allowlist.txt +++ b/tools/freestanding_allowlist.txt @@ -111,6 +111,10 @@ __ledf2 __gtdf2 __gedf2 __unorddf2 +# Phase 2 N9 fround (c57a2720): f64 -> f32 -> f64 soft-float casts. +# The existing stage-2 compiler-rt link supplies both; these are not HAL roots. +__extendsfdf2 +__truncdfsf2 # --- atomic HAL roots on targets which cannot implement the width --- __atomic_load_1 diff --git a/tools/gen_lsp_builtin_index.sh b/tools/gen_lsp_builtin_index.sh index d73773c12..167211c4f 100755 --- a/tools/gen_lsp_builtin_index.sh +++ b/tools/gen_lsp_builtin_index.sh @@ -45,7 +45,7 @@ export LC_ALL CORE_SRCS="src/builtins.c src/builtins_host.c src/hash.c src/ext_store.c" # TUs scanned for `name of ...` signature comments (core + every extension). -DOC_SRCS="src/builtins.c src/builtins_buf.c src/builtins_host.c src/builtins_tensor.c \ +DOC_SRCS="src/builtins.c src/builtins_int.c src/builtins_buf.c src/builtins_host.c src/builtins_tensor.c \ src/hash.c src/ext_store.c src/ext_gfx.c src/ext_http.c \ src/ext_db.c src/ext_net.c src/model_train.c" diff --git a/tools/jit_diff.sh b/tools/jit_diff.sh index 0b99777bc..07ff06b86 100755 --- a/tools/jit_diff.sh +++ b/tools/jit_diff.sh @@ -9,8 +9,12 @@ # The OBS arm is the old observer corpus: every tracked .eigs file, with the # gate forced open (EIGS_JIT_OFF=1 EIGS_OBS_FORCE=1), compared to ref. # A plain divergence is adjudicated. (1) Both sides run once more; if each -# reproduces itself, it is a row. (2) Otherwise ref records a tape and the -# other arm replays it; only a divergence that survives replay is a row. +# reproduces itself, it is a row. (2) Otherwise the arm is ledgered as unable +# to be adjudicated. Trace/replay opens the observer gate, so it cannot safely +# decide a divergence from any arm's ordinary gate configuration (#2122). +# Every arm receives the same process RNG seed, following V8's differential +# fuzzer --random-seed precedent (and Go's process-level randautoseed knob), so +# random output is comparable before trace/replay opens the observer gate. # A timeout on one side is a divergence, not a pass. # tests/jit_diff_expected.txt is the ledger. A '#' line or a blank line is a # reason, not a row: both are stripped before the comparison and the count. @@ -19,6 +23,38 @@ # Test only: JIT_DIFF_OBS_ONLY= runs just that OBS case, # skipping the JIT and OSR arms and the normal OBS corpus. set -u +# Phase 2 R1.3: every arm is headless, independent of the caller's display. +# A program requiring dummy drivers names them in the shared suite policy. +unset DISPLAY WAYLAND_DISPLAY XAUTHORITY +export SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy +if [ "${1:-}" = "--selftest" ] && [ "${JIT_DIFF_SELFTEST_SUITE:-0}" = "1" ]; then + ROOT="$(cd "$(dirname "$0")/.." && pwd)" + cd "$ROOT" || exit 1 + # The timeout-both fixture outlives both 25 s OBS deadlines by design, so it + # needs > 50 s; the row is keyed on the checker and fixtures (not on the + # product test it replays), which keeps it off the 60 s product-row cap (#1352). + timeout 90 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_timeout_both.eigs bash tools/jit_diff.sh --selftest || exit 1 + timeout 30 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_SELFTEST_PLANT=deterministic JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_taped_divergence.eigs bash tools/jit_diff.sh --selftest || exit 1 + timeout 30 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_SELFTEST_PLANT=gate_nondet JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_gate_nondeterministic.eigs bash tools/jit_diff.sh --selftest || exit 1 + timeout 60 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_OBS_ONLY=tests/test_chunk_verify_stack.eigs bash tools/jit_diff.sh --selftest + [ "$?" -eq 0 ] || exit 1 + selftest_ledger=$(mktemp); selftest_out=$(mktemp) + trap 'rm -f "$selftest_ledger" "$selftest_out"' EXIT + echo 'tests/fixtures/jit_diff_agreement.eigs OBS COULD_NOT_ADJUDICATE' > "$selftest_ledger" + timeout 60 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_SELFTEST_COMPARE=1 JIT_DIFF_BASE="$selftest_ledger" JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_agreement.eigs bash tools/jit_diff.sh --selftest > "$selftest_out" 2>&1 || { + cat "$selftest_out"; echo "jit_diff: FAIL: absent ledgered COULD_NOT_ADJUDICATE row was rejected"; exit 1; } + cat "$selftest_out" + grep -Fq 'jit_diff: NOTE: ledgered COULD_NOT_ADJUDICATE row absent (pair agreed): tests/fixtures/jit_diff_agreement.eigs OBS COULD_NOT_ADJUDICATE' "$selftest_out" || { + echo "jit_diff: FAIL: allowed absent row did not emit its NOTE"; exit 1; } + echo 'tests/fixtures/jit_diff_agreement.eigs OBS' > "$selftest_ledger" + if timeout 60 env -u JIT_DIFF_SELFTEST_SUITE JIT_DIFF_SELFTEST_COMPARE=1 JIT_DIFF_BASE="$selftest_ledger" JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_agreement.eigs bash tools/jit_diff.sh --selftest > "$selftest_out" 2>&1; then + cat "$selftest_out"; echo "jit_diff: FAIL: absent ordinary ledger row was accepted"; exit 1 + fi + grep -Fq '< tests/fixtures/jit_diff_agreement.eigs OBS' "$selftest_out" || { + cat "$selftest_out"; echo "jit_diff: FAIL: ordinary stale-row failure was not attributable"; exit 1; } + echo 'jit_diff: PASS: absent ordinary ledger row remains stale' + exit 0 +fi if [ "${1:-}" = "--selftest" ] && [ -z "${JIT_DIFF_OBS_ONLY:-}" ]; then echo "jit_diff: FAIL: --selftest requires JIT_DIFF_OBS_ONLY" >&2; exit 1 fi @@ -26,36 +62,60 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT/src" export EIGS_TEST_DIR="$ROOT/tests" . "$ROOT/tests/suite_program_env.sh" || exit 1 EIG="${EIGS_BIN:-./eigenscript}" -BASE="$ROOT/tests/jit_diff_expected.txt" +BASE="${JIT_DIFF_BASE:-$ROOT/tests/jit_diff_expected.txt}" T=$(mktemp -d); trap 'rm -rf "$T"' EXIT -got="$T/got"; : > "$got"; n=0; n_obs=0; n_deny=0; n_timeout=0; n_suite_env=0; adjudicated=0 +got="$T/got"; : > "$got"; n=0; n_obs=0; n_deny=0; n_timeout=0; n_suite_env=0; adjudicated=0; could_not_adjudicate=0; run_seq=0 # Only address-shaped hex (8+ digits) is noise; a short 0x1 vs 0x0 is data. norm() { sed -E 's/0x[0-9a-f]{8,}/0xADDR/g' "$1"; } # Flags are on when non-empty and not "0". GNU env takes -u BEFORE assignments. -REF=(-u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS EIGS_JIT_OFF=1) -JIT=(-u EIGS_JIT_OFF -u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS) -OSR=(-u EIGS_JIT_OFF -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS EIGS_JIT_OSR_THRESHOLD=1) -OBS=(-u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_GATE_STATS EIGS_JIT_OFF=1 EIGS_OBS_FORCE=1) +REF=(-u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS EIGS_SEED_RANDOM=2120 EIGS_JIT_OFF=1) +JIT=(-u EIGS_JIT_OFF -u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS EIGS_SEED_RANDOM=2120) +OSR=(-u EIGS_JIT_OFF -u EIGS_OBS_FORCE -u EIGS_OBS_GATE_STATS EIGS_SEED_RANDOM=2120 EIGS_JIT_OSR_THRESHOLD=1) +OBS=(-u EIGS_JIT_OSR_THRESHOLD -u EIGS_OBS_GATE_STATS EIGS_SEED_RANDOM=2120 EIGS_JIT_OFF=1 EIGS_OBS_FORCE=1) +if [ "${1:-}" = "--selftest" ] && { [ "${JIT_DIFF_SELFTEST_PLANT:-}" = "deterministic" ] || [ "${JIT_DIFF_SELFTEST_PLANT:-}" = "gate_nondet" ]; }; then + OBS+=(EIGS_JIT_DIFF_SELFTEST_ARM=arm) +fi run() { # $1 out-file, $2 seconds, rest = env args (options first) - local out="$1" sec="$2"; shift 2 - local setting - setting=$(suite_program_env "$prog") || { echo "jit_diff: FAIL: suite environment lookup failed for $prog" >&2; exit 1; } - case "$setting" in - "") env "$@" timeout "$sec" "$EIG" "$prog" "$out" 2>&1 ;; - EIGS_STRICT=0) env "$@" EIGS_STRICT=0 timeout "$sec" "$EIG" "$prog" "$out" 2>&1 ;; - *) echo "jit_diff: FAIL: unsupported suite environment for $prog: $setting" >&2; exit 1 ;; + local out="$1" sec="$2" rc; shift 2 + local plant_seed=() + run_seq=$((run_seq + 1)) + [ "${JIT_DIFF_SELFTEST_PLANT:-}" = "gate_nondet" ] && plant_seed=(EIGS_SEED_RANDOM=$((9000 + run_seq))) + # Phase 2 owns run conditions in one checked loader, including the gfx Q7 + # dummy-driver policy. Keep main's seed and self-test plants on the same run. + suite_program_check "$prog" > /dev/null || { echo "jit_diff: FAIL: suite environment lookup failed for $prog" >&2; exit 1; } + ( if grep -Eq '(gfx|ui|audio)_[a-z_]+' "$prog"; then ulimit -v 1500000; fi + suite_program_run "$prog" env "$@" "${plant_seed[@]}" timeout "$sec" "$EIG" "$prog" + ) "$out" 2>&1 + rc=$? + case " $* " in + *' EIGS_JIT_DIFF_SELFTEST_ARM=arm '*) + if [ "${JIT_DIFF_SELFTEST_PLANT:-}" = "gate_nondet" ]; then + # Trace and replay force the observer gate open. Model a defect that + # exists only on the ordinary, gate-closed path; the per-run plant seed supplies + # real output variation and a tape stabilizes the random draw. + case " $* " in *' EIGS_TRACE='*|*' EIGS_REPLAY='*) ;; *) echo "jit-diff gate-closed divergence plant" >> "$out" ;; esac + else + echo "jit-diff deterministic divergence plant" >> "$out" + fi ;; esac - echo "rc=$?" >> "$out" + echo "rc=$rc" >> "$out" } arm_env() { case "$1" in jit) AE=("${JIT[@]}") ;; osr) AE=("${OSR[@]}") ;; obs) AE=("${OBS[@]}") ;; esac; } # Top-level tests/foo.eigs keeps the historical basename; anything deeper keeps # its path (nested fixtures share basenames: peer.eigs, entry.eigs, ...). row_of() { case "$1" in tests/*/*) printf '%s' "$1" ;; tests/*) basename "$1" ;; *) printf '%s' "$1" ;; esac; } +# #772 deliberately crosses the old 1e8-iteration truncation boundary. On the +# reference 2-core box REF finishes in 19.8s and forced OBS in 25.6s with the +# same result. Give this finite boundary test the existing JIT/OSR 180s bound +# in OBS too; keep every other OBS case at 25s and retain one-sided refusals. +obs_deadline() { + case "$1" in tests/test_loop_cap_772.eigs) printf '180\n' ;; *) printf '25\n' ;; esac +} # '#' lines and blank lines are reasons. Data rows are what the diff sees. ledger_data() { awk 'NF && substr($0,1,1) != "#" { print }' "$1" | sort; } ledger_count() { ledger_data "$1" | wc -l | tr -d ' '; } -b=bench_idxset.eigs +b=jit_env_flags_float.eigs selfcheck() { # $1 arm name, $2.. env args local arm="$1"; shift local st; st=$(env "$@" EIGS_JIT_STATS=1 timeout 60 "$EIG" "../tests/$b" 2>&1 >/dev/null | grep -o 'compiled=[0-9]*') @@ -94,11 +154,12 @@ consider() { if diff -q <(norm "$T/ref") <(norm "$T/ref2") >/dev/null && diff -q <(norm "$T/arm") <(norm "$T/arm2") >/dev/null; then echo "$row $tag" >> "$got"; return fi - rm -f "$T/tape" - run "$T/rref" "$sec" "${REF[@]}" EIGS_TRACE="$T/tape" - run "$T/rarm" "$sec" "${AE[@]}" EIGS_REPLAY="$T/tape" - diff -q <(norm "$T/rref") <(norm "$T/rarm") >/dev/null && return - echo "$row $tag" >> "$got" + # A tape is not neutral here: recording/replay enables observer snapshots + # and opens eigs_obs_gate_open(). It therefore cannot distinguish the OBS + # arm, or a JIT/OSR defect confined to the gate-closed path. Fail closed and + # put the category in the ledger rather than silently accepting the pair. + could_not_adjudicate=$((could_not_adjudicate + 1)) + echo "$row $tag COULD_NOT_ADJUDICATE" >> "$got" } if [ -z "${JIT_DIFF_OBS_ONLY:-}" ]; then for f in "$ROOT"/tests/test_*.eigs; do @@ -122,8 +183,18 @@ done fi # Denied up front: a source that uses gfx_open opens a window and cannot finish # unattended in the headless gate; *seeded_race* and -# *userrace* (a user data race: undefined, can crash either side, #1171) are +# *userrace* (the deliberate #1489 positive-control race) are # nondeterministic on purpose; *state_at*|*statedump* prints hash-order output. +# Phase 2's sole ownership declaration is checked on the broad population, +# before selecting a singleton self-test. Include owned untracked bank files +# in an unresolved merge, while main's OBS execution stays tracked-only. +if ! owned_tracked=$(git -c safe.directory="$ROOT" -C "$ROOT" ls-files '*.eigs' 2>/dev/null); then + echo "jit_diff: FAIL: cannot enumerate the ownership corpus" >&2; exit 1 +fi +printf '%s\n' "$owned_tracked" > "$T/owned-tracked" +(cd "$ROOT" && find tests -type f -name '*.eigs') > "$T/owned-tests" +LC_ALL=C sort -u "$T/owned-tracked" "$T/owned-tests" > "$T/ownership-corpus" +python3 "$ROOT/tools/phase2_corpus_exclude.py" "$ROOT" "$T/ownership-corpus" || exit 1 if [ -n "${JIT_DIFF_OBS_ONLY:-}" ]; then case "$JIT_DIFF_OBS_ONLY" in /*|../*|*/../*) echo "jit_diff: FAIL: JIT_DIFF_OBS_ONLY must be repository-relative" >&2; exit 1 ;; esac obs_files=$JIT_DIFF_OBS_ONLY @@ -134,8 +205,12 @@ elif ! obs_files=$(git -c safe.directory="$ROOT" -C "$ROOT" ls-files '*.eigs' \ echo "jit_diff: FAIL: cannot enumerate the tracked OBS corpus" >&2; exit 1 fi [ -n "$obs_files" ] || { echo "jit_diff: FAIL: tracked OBS corpus is empty" >&2; exit 1; } +if [ -n "${JIT_DIFF_OBS_ONLY:-}" ] && ! grep -qxF -- "$obs_files" "$T/ownership-corpus"; then + echo "jit_diff: FAIL: JIT_DIFF_OBS_ONLY is outside the unowned OBS corpus: $obs_files" >&2; exit 1 +fi while IFS= read -r rel; do [ -n "$rel" ] || continue + grep -qxF -- "$rel" "$T/ownership-corpus" || continue [ -f "$ROOT/$rel" ] || { echo "jit_diff: FAIL: OBS corpus entry missing: $rel" >&2; exit 1; } if grep -q 'gfx_open[[:space:]]*of' "$ROOT/$rel"; then n_deny=$((n_deny + 1)) @@ -144,14 +219,19 @@ while IFS= read -r rel; do fi case "$rel" in *paint*|*_game*) n_deny=$((n_deny + 1)); echo "jit_diff: $rel OBS: excluded (interactive example)"; continue ;; - *seeded_race*|*userrace*) n_deny=$((n_deny + 1)); echo "jit_diff: $rel OBS: excluded (intentionally nondeterministic)"; continue ;; + *seeded_race*) n_deny=$((n_deny + 1)); echo "jit_diff: $rel OBS: excluded (intentionally nondeterministic)"; continue ;; + *userrace*) n_deny=$((n_deny + 1)); echo "jit_diff: $rel OBS: excluded (deliberate #1489 positive-control race)"; continue ;; *state_at*|*statedump*) n_deny=$((n_deny + 1)); echo "jit_diff: $rel OBS: excluded (hash-order output)"; continue ;; esac row=$(row_of "$rel"); prog="$ROOT/$rel" - run "$T/ref" 25 "${REF[@]}" + obs_seconds=$(obs_deadline "$rel") + if [ "$obs_seconds" != 25 ]; then + echo "jit_diff: $rel OBS: finite loop-boundary regression uses ${obs_seconds}s in both arms" + fi + run "$T/ref" "$obs_seconds" "${REF[@]}" arm_env obs - run "$T/arm" 25 "${AE[@]}" - consider obs 25 + run "$T/arm" "$obs_seconds" "${AE[@]}" + consider obs "$obs_seconds" considered=$? if [ "$considered" -eq 2 ]; then n_timeout=$((n_timeout + 1)) @@ -164,22 +244,37 @@ EOF sort -o "$got" "$got" if [ -z "${JIT_DIFF_OBS_ONLY:-}" ]; then [ "$n" -ge 100 ] || { echo "jit_diff: only $n JIT programs found -- the scan is vacuous"; exit 1; } - [ "$n_suite_env" -eq 10 ] || { echo "jit_diff: FAIL: suite-environment programs completed=$n_suite_env, expected=10"; exit 1; } + [ "$n_suite_env" -eq 16 ] || { echo "jit_diff: FAIL: suite-environment programs completed=$n_suite_env, expected=16"; exit 1; } [ "$n_obs" -ge 500 ] || { echo "jit_diff: FAIL: OBS examined=$n_obs denied=$n_deny (floor 500)"; exit 1; } fi echo "jit_diff: OBS examined=$n_obs denied=$n_deny timeout-both=$n_timeout" -[ -n "${JIT_DIFF_OBS_ONLY:-}" ] && exit 0 +if [ -n "${JIT_DIFF_OBS_ONLY:-}" ] && [ -z "${JIT_DIFF_SELFTEST_COMPARE:-}" ]; then + [ "${1:-}" = "--selftest" ] && cat "$got" + exit 0 +fi if [ "${1:-}" = "--record" ]; then { awk '/^#/ { print }' "$BASE"; echo; cat "$got"; } > "$T/ledger" cp "$T/ledger" "$BASE" - echo "jit_diff: baseline recorded ($(ledger_count "$BASE") rows, JIT $n, OBS examined=$n_obs denied=$n_deny, $adjudicated arms adjudicated)" + echo "jit_diff: baseline recorded ($(ledger_count "$BASE") rows, JIT $n, OBS examined=$n_obs denied=$n_deny, $adjudicated arms adjudicated, $could_not_adjudicate could not adjudicate)" exit 0 fi [ -f "$BASE" ] || { echo "jit_diff: no baseline at $BASE (run with --record)"; cat "$got"; exit 1; } nled=$(ledger_count "$BASE") -if diff <(ledger_data "$BASE") "$got" > "$T/d"; then - echo "jit_diff: OK ($n programs x {jit, osr} vs the interpreter; suite-env completed=$n_suite_env; OBS examined=$n_obs denied=$n_deny; $adjudicated arms adjudicated; $nled ledgered)"; exit 0 +# COULD_NOT_ADJUDICATE records an unstable pair that may agree on a later run. +# Its absence is therefore informational. All present and ordinary rows remain +# an exact, bidirectional ledger comparison. +ledger_data "$BASE" | awk '$NF == "COULD_NOT_ADJUDICATE"' > "$T/optional" +comm -23 "$T/optional" "$got" > "$T/absent_optional" +ledger_data "$BASE" | awk '$NF != "COULD_NOT_ADJUDICATE"' > "$T/required" +comm -23 "$T/required" "$got" > "$T/absent_required" +comm -13 <(ledger_data "$BASE") "$got" > "$T/unexpected" +if [ ! -s "$T/absent_required" ] && [ ! -s "$T/unexpected" ]; then + while IFS= read -r absent; do + [ -n "$absent" ] && echo "jit_diff: NOTE: ledgered COULD_NOT_ADJUDICATE row absent (pair agreed): $absent" + done < "$T/absent_optional" + echo "jit_diff: OK ($n programs x {jit, osr} vs the interpreter; suite-env completed=$n_suite_env; OBS examined=$n_obs denied=$n_deny; $adjudicated arms adjudicated; $could_not_adjudicate could not adjudicate; $nled ledgered)"; exit 0 fi +diff <(ledger_data "$BASE") "$got" > "$T/d" || true echo "jit_diff: LEDGER CHANGED (JIT $n, OBS examined=$n_obs denied=$n_deny)" echo " '<' = ledgered and now identical (improvement -- remove it)" echo " '>' = newly diverging from the interpreter (REGRESSION)" diff --git a/tools/num_read_allowlist.txt b/tools/num_read_allowlist.txt index 96d72b92a..7d4acc71c 100644 --- a/tools/num_read_allowlist.txt +++ b/tools/num_read_allowlist.txt @@ -1,51 +1,53 @@ # tools/num_read_allowlist.txt -- every raw number token, reviewed (#1637 round 4). -# file|function|count|reason. tools/num_read_check.sh counts VAL_NUM_RAW / -# SLOT_NUM_RAW / VAL_NUM_OFFSET tokens per function and fails when a count +# file|function|count|reason. tools/num_read_check.sh counts VAL_F64_RAW / VAL_I64_RAW / +# SLOT_F64_RAW / VAL_F64_OFFSET tokens per function and fails when a count # differs (a new raw read, however it is spelled across lines, changes the # count), when a function with raw tokens is missing here, or a row is stale. -# Builtins never belong here: they read through eigs_num_arg / eigs_list_num / -# eigs_opt_num / eigs_elem_num, which raise on a bool. +# Ordinary builtin argument reads use eigs_float_arg / eigs_list_float / +# eigs_opt_float / eigs_elem_float, which raise on a bool. The explicitly reviewed +# exceptions below preserve a numeric kind or exact int text (JSON and sorting), +# box a typed dispatch argument, or write a typed sentinel (matmul). +# A double bridge would lose int digits in the exact readers; each raw payload +# read is behind its own kind test. Exceptions still need exact per-function +# token counts and reasons; they do not exempt ordinary argument coercion. builtins_tensor.c|builtin_tensor_matmul|1|writes the NaN-boxed null sentinel into a result buffer cell (EIGS_STRICT=0 path); a write, no Value is read -container.h|dict_value_get_num|1|accessor numeric fast path: reads the slot only behind its own slot_is_num test (#1665 arm D) -container.h|dict_value_get_return|1|builtin-return read boxes an immediate only after slot_is_num -container.h|eigs_container_slot|1|Value-to-slot encoder reads a box only after type == VAL_NUM, then num_guards it -container.h|eigs_slot_store_borrow|1|storage normaliser re-guards an immediate only after slot_is_num (#1665 arm D B10) -container.h|eigs_slot_store_owned|1|storage normaliser re-guards an immediate only after slot_is_num (#1665 arm D B10) -container.h|eigs_slot_box|1|borrowed slot write boxes a double only after slot_is_num -container.h|eigs_slot_elem_num|1|element numeric accessor reads only after slot_is_num -container.h|eigs_slot_num|1|required numeric accessor reads only after slot_is_num -container.h|eigs_slot_opt_num|1|optional numeric accessor reads only after slot_is_num -container.h|list_get_num|1|accessor numeric fast path: reads the slot only behind its own slot_is_num test (#1665 arm D) -container.h|list_get_return|1|builtin-return read boxes an immediate only after slot_is_num -container.h|slot_view|2|numeric view copies a proven immediate into caller storage -chunk.c|chunk_disassemble|1|debug disassembly prints a constant behind its own type == VAL_NUM test -chunk.c|const_equal|2|constant-pool dedup compares two constants after testing both are VAL_NUM -chunk.c|const_hash_value|1|constant-pool hash of a constant inside its type == VAL_NUM branch -eigenscript.c|chan_clone_rec|1|channel deep-copy, switch case VAL_NUM -eigenscript.c|compute_entropy_impl|1|observer entropy, switch case VAL_NUM -eigenscript.c|ent_child_slot|1|observer entropy reads an immediate only after slot_is_num -eigenscript.c|eigs_num_arg_slow|1|checking accessor itself: reads only after type == VAL_NUM, raises otherwise -eigenscript.c|is_truthy|1|truthiness, switch case VAL_NUM (a bool has its own case) -eigenscript.c|make_num|1|constructor: writes the number of a Value it just typed VAL_NUM -eigenscript.c|make_num_permanent|1|constructor: writes the number of a Value it just typed VAL_NUM -eigenscript.c|module_ns_project|1|mirror refresh: reads the binding slot after slot_is_num and stores it over a numeric mirror slot -eigenscript.c|observer_entropy_now|1|observer entropy of a slot after slot_is_num -eigenscript.c|observer_slot_from_trajectory|6|trajectory snapshot fields each read right after its own type != VAL_NUM refusal -eigenscript.c|observer_slot_sample_gated|1|observer sample after newval->type == VAL_NUM -eigenscript.c|observer_slot_update_locked|1|observer update after newval->type == VAL_NUM (moved into the #1867 fold worker) -eigenscript.c|observer_slot_update_value_num_shared|1|shared observer update after newval->type == VAL_NUM under the fold hold -eigenscript.c|slot_from_value|1|NaN-boxing boundary, inside its VAL_NUM branch -eigenscript.c|slot_to_value|1|NaN-boxing boundary after slot_is_num -eigenscript.c|values_equal_for_observation|2|observer-only non-raising equality, switch case VAL_NUM after equal type tags -eigenscript.c|values_equal_impl|2|equality, switch case VAL_NUM after the bool/num pair raised -eigenscript.c|value_to_string|1|printing, switch case VAL_NUM -eigenscript.h|#define VAL_NUM_OFFSET|1|the raw member offset definition (JIT) -eigenscript.h|#define VAL_NUM_RAW|1|the raw read definition -eigenscript.h|eigs_num_arg|1|inline fast path of the checking accessor: reads only after type == VAL_NUM -eigs_embed.c|eigs_value_as_num|1|embedding API: reads only after type == VAL_NUM; a bool answers NaN and other non-numbers 0.0 (docs/EMBEDDING.md) -embed_smoke.c|main|7|C test program: builds and inspects slots it constructed as numbers -jit.c|emit_load_numeric_operand|2|emits the load of data.num_ after the emitted type == VAL_NUM / refcount guard -jit.c|jit_compile_to_thunk|2|emits loads/stores of data.num_ behind emitted immediate-number checks; reads a VAL_NUM constant +builtins.c|eigs_json_encode_value|1|JSON encoder: case VAL_INT writes the int's exact digits (#1758 R1.2a) +container.h|dict_value_get_return|1|builtin-return read boxes an immediate only after slot_is_f64 +container.h|eigs_container_slot|2|Value-to-slot encoder reads a box only after type == VAL_FLOAT (then num_guards it) or type == VAL_INT (an SMI in range, design 2.1) +container.h|eigs_slot_store_borrow|1|storage normaliser re-guards an immediate only after slot_is_f64 (#1665 arm D B10) +container.h|eigs_slot_store_owned|1|storage normaliser re-guards an immediate only after slot_is_f64 (#1665 arm D B10) +container.h|eigs_slot_box|1|borrowed slot write boxes a double only after slot_is_f64 +container.h|list_get_return|1|builtin-return read boxes an immediate only after slot_is_f64 +chunk.c|chunk_disassemble|2|debug disassembly prints a constant behind its own type == VAL_FLOAT test +chunk.c|const_equal|4|constant-pool dedup compares two constants after testing both are VAL_FLOAT +chunk.c|const_hash_value|2|constant-pool hash of a constant inside its type == VAL_FLOAT branch +eigenscript.c|chan_clone_rec|2|channel deep-copy, switch cases VAL_FLOAT and VAL_INT preserve the payload and numeric kind +eigenscript.c|compute_entropy_impl|2|observer entropy, switch case VAL_FLOAT, and case VAL_INT (an int's entropy is its double's, design 7) +eigenscript.c|ent_child_slot|1|observer entropy reads an immediate only after slot_is_f64 +eigenscript.c|is_truthy|2|truthiness, switch case VAL_FLOAT and case VAL_INT (a bool has its own case) +eigenscript.c|make_float_pooled|1|shared float constructors: writes the number of a Value it just typed VAL_FLOAT +eigenscript.c|make_float_permanent|1|unpooled float constructor: writes the number of a Value it just typed VAL_FLOAT +eigenscript.c|module_ns_project|1|mirror refresh: reads the binding slot after slot_is_f64 and stores it over a numeric mirror slot +eigenscript.c|observer_entropy_now|1|observer entropy of a slot after slot_is_f64 +eigenscript.c|observer_slot_sample_gated|2|observer sample inside val_is_number: the VAL_INT box as its double, else the VAL_FLOAT (design 7) +eigenscript.c|observer_slot_update_locked|2|observer update inside val_is_number: the VAL_INT box explicitly converts to the observer double channel, else the VAL_FLOAT; both reads follow their kind test (design 7, main #1867) +eigenscript.c|observer_slot_update_value_num_shared|2|shared observer update called only by observer_slot_update inside val_is_number; the VAL_INT arm converts to double and the other arm reads VAL_FLOAT, while env_shared_fold_lock covers the fold (design 7, main #1867) +eigenscript.c|values_equal_for_observation|8|non-raising observer snapshot equality: cross-kind pairs are proven VAL_INT/VAL_FLOAT before eigs_cmp_int_float; equal-kind switch arms read both operands only after their shared VAL_FLOAT or VAL_INT type proof (N2, main #1992) +eigenscript.c|slot_from_value|2|NaN-boxing boundary, inside its VAL_FLOAT branch and its VAL_INT branch (an SMI in range, design 2.1) +eigenscript.c|slot_to_value|1|NaN-boxing boundary after slot_is_f64 +eigenscript.c|values_equal_impl|8|equality: the int/float pair by eigs_cmp_int_float (N2), each read inside its own type test; switch case VAL_FLOAT and case VAL_INT (both ints) +eigenscript.c|value_to_string|2|printing, switch case VAL_FLOAT and case VAL_INT (exact digits) +eigenscript.h|#define VAL_F64_OFFSET|1|the raw double member offset definition (JIT; #1758 R1.2a: was VAL_NUM_OFFSET) +eigenscript.h|#define VAL_F64_RAW|1|the raw double read definition (#1758 R1.2a: was VAL_NUM_RAW) +eigenscript.h|#define VAL_I64_RAW|1|the raw int64 read definition (#1758 R1.2a) +eigs_embed.c|eigs_value_as_num|2|embedding API: reads only after type == VAL_FLOAT; a bool answers NaN and other non-numbers 0.0 (docs/EMBEDDING.md) +eigs_embed.c|eigs_value_as_int|2|Q3: int payload read only after VAL_INT; float payload only after VAL_FLOAT, exact int64 checked before cast under the default floating environment +embed_smoke.c|test_embed_int|1|Q3/#1969 C fixture: deliberate raw float payload write including nonfinites and subnormal before hostile-host conversion tests +embed_smoke.c|main|6|C test: known float payloads; S9-K1 assignment count now uses the int accessor +ext_store.c|store_json_encode|2|store record encoder: case VAL_INT writes exact digits; case VAL_FLOAT preserves the historical payload including non-finite data until N13 (R2.3) +ext_store.c|store_json_encode_slot|1|store record encoder: slot_is_f64 proves the float payload, preserving historical non-finite formatting until N13 (R2.3) +jit.c|emit_load_numeric_operand|2|emits the load of data.f64_ after the emitted type == VAL_FLOAT / refcount guard +jit.c|jit_compile_to_thunk|2|emits loads/stores of data.f64_ behind emitted immediate-number checks; reads a VAL_FLOAT constant jit_smoke.c|jit_helper_index_get|2|JIT smoke test program (stubbed helpers over slots it built as numbers) jit_smoke.c|jit_helper_iter_next|1|JIT smoke test program (stubbed helpers over slots it built as numbers) jit_smoke.c|jit_helper_set_name|4|JIT smoke test program (stubbed helpers over slots it built as numbers) @@ -53,29 +55,38 @@ jit_smoke.c|run_exit_cases|3|JIT smoke test program (stubbed helpers over slots jit_smoke.c|run_index_bail_cases|2|JIT smoke test program (stubbed helpers over slots it built as numbers) jit_smoke.c|run_reader_bail_cases|8|JIT smoke test program (stubbed helpers over slots it built as numbers) jit_smoke.c|run_store_cases|4|JIT smoke test program (stubbed helpers over slots it built as numbers) -trace.c|write_slot|1|tape writer after slot_is_num -trace.c|write_value_ptr|1|tape writer, switch case VAL_NUM -trace.c|write_value_ptr_full|1|tape writer, switch case VAL_NUM -value_slot.h|#define SLOT_NUM_RAW|1|the raw slot read definition -value_slot.h|slot_from_num|1|constructor: writes a number into a fresh slot -value_slot.h|slot_truthy_immediate|1|truthiness after slot_is_num -vm.c|#define ARITH_FAST|2|arithmetic fast path after both operands tested VAL_NUM; NUM_REUSE writes -vm.c|#define INT_BINOP_R|2|integer op fast path after both slots slot_is_num -vm.c|#define NUM_BINOP|6|arithmetic after a->type == VAL_NUM && b->type == VAL_NUM; NUM_REUSE writes -vm.c|jit_helper_dot_set|1|JIT helper: number store after slot_is_num -vm.c|jit_helper_index_get|9|JIT helper: index read after slot_is_num / VAL_NUM tests on index and element -vm.c|jit_helper_index_set|10|JIT helper: index store after slot_is_num / VAL_NUM tests on index and value -vm.c|jit_helper_iter_next|1|JIT helper: loop state list [iterable, idx, snap] built by the VM, each read behind a VAL_NUM test -vm.c|jit_helper_local_dot_set|1|JIT helper: number store after slot_is_num -vm.c|jit_helper_observe_assign_local|2|JIT helper: observer update after slot_is_num -vm.c|jit_helper_observe_name_post|2|JIT helper: observer update after slot_is_num -vm.c|loop_iter_store|1|writes the loop counter slot after slot_is_num -vm.c|obs_dump_scope|1|SIGUSR1 observer dump after slot_is_num -vm.c|obs_dump_value|2|SIGUSR1 observer dump after slot_is_num / case VAL_NUM -vm.c|slot_as_double|2|VM numeric operand read: returns 0 (not a number) unless slot_is_num or VAL_NUM -vm.c|slot_bridge_unwrap|1|NaN-boxing boundary after slot_is_num -vm.c|slot_truthy|1|truthiness after slot_is_num -vm.c|vm_leaf_accessor_exec|1|leaf-accessor fast path: constants the scanner admitted only as VAL_NUM (chunk_scan_leaf_accessor) and values behind VAL_NUM tests -vm.c|vm_run_ex|57|the interpreter loop: every opcode reads numbers behind slot_is_num / type == VAL_NUM dispatch; NUM_REUSE in-place writes; the iterator state list is VM-built -vm.c|vm_slot_lift|1|NaN-boxing boundary after slot_is_num -vm.c|vm_store_local_slot|2|in-place local number store after slot_is_num +trace.c|write_slot|1|tape writer after slot_is_f64 +trace.c|write_value_ptr|2|v9 tape writer, switch cases VAL_FLOAT and VAL_INT mark floats and preserve exact integer digits +trace.c|write_value_ptr_full|2|full v9 tape writer, switch cases VAL_FLOAT and VAL_INT mark floats and preserve exact integer digits +value_slot.h|#define SLOT_F64_RAW|1|the raw slot read definition (#1758: was SLOT_NUM_RAW) +value_slot.h|slot_f64_value|2|float reader (#1758: was vm.c slot_as_double): an immediate after slot_is_f64, a box after type == VAL_FLOAT; 0 for an int +value_slot.h|slot_from_f64|1|constructor: writes a double into a fresh slot (#1758: was slot_from_num) +value_slot.h|slot_truthy_immediate|1|truthiness after slot_is_f64 +vm.c|#define ARITH_FAST|2|arithmetic fast path after both operands tested VAL_FLOAT; NUM_REUSE writes +vm.c|#define NUM_BINOP|6|arithmetic after a->type == VAL_FLOAT && b->type == VAL_FLOAT; NUM_REUSE writes +vm.c|jit_helper_dot_set|1|JIT helper: number store after slot_is_f64 +vm.c|jit_helper_index_get|3|JIT helper: index read after slot_is_f64 / VAL_FLOAT tests on index and element +vm.c|jit_helper_index_set|5|JIT helper: index store after slot_is_f64 / VAL_FLOAT tests on index and value +vm.c|jit_helper_local_dot_set|1|JIT helper: number store after slot_is_f64 +vm.c|obs_dump_scope|1|SIGUSR1 observer dump after slot_is_f64 +vm.c|obs_dump_value|3|SIGUSR1 observer dump after slot_is_f64 / case VAL_FLOAT / case VAL_INT (exact digits) +vm.c|obs_slot_num|2|observer numeric channel (#1758): reads an immediate after slot_is_f64; an int explicitly converts to the observer double channel (precision can round above 2^53) +vm.c|slot_bridge_unwrap|1|NaN-boxing boundary after slot_is_f64 +vm.c|slot_truthy|1|truthiness after slot_is_f64 +vm.c|vm_leaf_accessor_exec|1|leaf-accessor fast path: constants the scanner admitted only as VAL_FLOAT (chunk_scan_leaf_accessor) and values behind VAL_FLOAT tests +vm.c|vm_run_ex|34|typed interpreter payload accesses: float slots/boxes behind slot_is_f64, VAL_FLOAT or NUM_REUSE; INT_CONST is verifier-typed, other int reads behind VAL_INT guards including both IDIV/MOD operands (#1758 N4) and temporal/slice/dispatch diagnostics; iterator state is VM-built +vm.c|vm_slot_lift|1|NaN-boxing boundary after slot_is_f64 +vm.c|vm_store_local_slot|2|in-place local number store after slot_is_f64 +builtins.c|builtin_json_build|1|VAL_INT text is exact digits; float text retains the bridge (#1758 R1.2d) +builtins.c|builtin_json_path|1|VAL_INT scalar text is exact digits before releasing root (#1758 R1.2d) +builtins.c|builtin_sort_by|1|numeric key kind retained for exact mixed sorting (#1758 N2) +builtins.c|sort_cmp_num|6|validated numeric slots viewed through slot_view; int comparisons are exact (#1758 N2) +value_slot.h|slot_int_value|1|boxed VAL_INT payload after its type test (#1758 R1.2i) +container.h|slot_view|2|typed float or int facade, preserving exact payload and kind (#1758 R1.2i) +ext_db.c|db_build_query|1|VAL_INT parameters encode as exact digits after the kind test (#1758 R1.2) +vm.c|vm_value_index_is_int|2|Guarded exact VAL_INT-to-C-index conversion; the other numeric kind keeps the float integrality test (#1758 R1.2) +vm.c|vm_subscript_index_is_int|2|subscript-only exact VAL_INT read after its kind test; all callers prove val_is_number so the other arm is VAL_FLOAT; int64 values outside C int clamp only to a necessarily out-of-bounds diagnostic endpoint (#1849, N5) +eigenscript.c|make_int|1|int constructor initializes the exact signed payload +eigenscript.c|eigs_number_read|2|final checked reader: exact int and float payloads are read only behind their corresponding Value type tests (#1758 R1.6a) +eigenscript.c|buffer_index_at|1|writes the raw buffer float into a borrowed stack facade; eigs_int_arg performs the finite/exact int conversion (#1758 R1.6a) +builtins_host.c|replay_reader_size|2|v9 taped reader-size metadata: both exact int64 payload reads are dominated by v->type == VAL_INT; the first rejects negative sizes and the second returns the validated size without a float bridge (sync6 #1758 / main #1918) diff --git a/tools/num_read_check.sh b/tools/num_read_check.sh index b3c414778..be0fa6cd2 100644 --- a/tools/num_read_check.sh +++ b/tools/num_read_check.sh @@ -2,13 +2,16 @@ # num_read_check.sh -- #1637 round 4: raw number reads are TOKENS, and every # token is reviewed. # -# The Value union's number member is `num_` and the EigsSlot union's double is -# `d_`, so no unreviewed spelling compiles. Code reaches a number in exactly -# two ways: -# - the checking accessors eigs_num_arg / eigs_list_num / eigs_opt_num, +# The Value union's number members are `f64_` (a VAL_FLOAT) and `i64_` (a +# VAL_INT), and the EigsSlot union's double is `d_`, so no unreviewed spelling +# compiles. #1758 R1.2z removed the deprecated numeric aliases; the scanner +# also catches their reintroduction before compilation. Code reaches a number in +# exactly two ways: +# - the checking accessors eigs_float_arg / eigs_list_float / eigs_opt_float, # which raise a type error on a bool or any other non-number; -# - the raw macros VAL_NUM_RAW(v) / SLOT_NUM_RAW(s) (and VAL_NUM_OFFSET for -# JIT-emitted loads), for a value whose type is already proven. +# - the raw macros VAL_F64_RAW(v) / VAL_I64_RAW(v) / SLOT_F64_RAW(s) (and +# VAL_F64_OFFSET for JIT-emitted loads), for a value whose type is +# already proven. # This gate enumerates every raw token in src/*.c and src/*.h. It attributes # each one to its enclosing function, or to its #define for a macro. Then it # requires each (file, function) to appear in tools/num_read_allowlist.txt as @@ -17,7 +20,7 @@ # a count and goes red until someone reviews it. # # It also fails on: -# - `data.num_` / `.d_` spelled out anywhere except the macro definitions +# - raw Value payload members / `.d_` outside the macro definitions # (a bypass of the macros); # - a bare `->data.builtin(` call: every builtin call must pass the bool # gate, eigs_call_builtin; @@ -36,6 +39,22 @@ trap 'rm -f "$TMP" "$TMP.b" "$TMP.c"' EXIT for f in "$SRC_DIR"/*.c "$SRC_DIR"/*.h; do [ -f "$f" ] || continue awk -v FILE="$(basename "$f")" -v BYPASS="$TMP.b" ' + # Attributes may precede the function name. Skip their balanced argument + # lists for attribution only; raw-token and bypass scanning stay intact. + function without_attributes(s, start, open, depth, i, c, end) { + while (match(s, /__attribute__[ \t]*\(/)) { + start = RSTART; open = RSTART + RLENGTH - 1; depth = 1; end = 0 + for (i = open + 1; i <= length(s); i++) { + c = substr(s, i, 1) + if (c == "(") depth++ + else if (c == ")") depth-- + if (depth == 0) { end = i; break } + } + if (!end) return s + s = substr(s, 1, start - 1) " " substr(s, end + 1) + } + return s + } { line = $0 # strip comments (block comments carry state across lines) and @@ -45,23 +64,24 @@ for f in "$SRC_DIR"/*.c "$SRC_DIR"/*.h; do while (match(line, /\/\*([^*]|\*[^\/])*\*\//)) line = substr(line, 1, RSTART - 1) " " substr(line, RSTART + RLENGTH) if (index(line, "/*")) { line = substr(line, 1, index(line, "/*") - 1); incmt = 1 } sub(/\/\/.*/, "", line) + attribution = without_attributes(line) } # attribution: a #define names its macro (continuations stay with it); # a column-0 line with a parameter list that is not a declaration starts # a function; a column-0 "}" ends it !incont && /^#[ \t]*define[ \t]/ { m = $0; sub(/^#[ \t]*define[ \t]+/, "", m); sub(/[^A-Za-z0-9_].*/, "", m); scope = "#define " m; inmac = 1 } - !incont && !/^#/ && /^[A-Za-z_][^;]*\(/ && $0 !~ /;[ \t]*$/ && $0 !~ /^(typedef|return|extern)/ { - fn = $0; sub(/\(.*/, "", fn); n = split(fn, w, /[ *]+/); scope = w[n]; inmac = 0 + !incont && !/^#/ && attribution ~ /^[A-Za-z_][^;]*\(/ && attribution !~ /;[ \t]*$/ && attribution !~ /^(typedef|return|extern)/ { + fn = attribution; sub(/\(.*/, "", fn); n = split(fn, w, /[ *]+/); scope = w[n]; inmac = 0 } { linemac = inmac } { s = line - while (match(s, /(VAL_NUM_RAW|SLOT_NUM_RAW)[ \t]*\(|VAL_NUM_OFFSET/)) { + while (match(s, /(VAL_NUM_RAW|VAL_F64_RAW|VAL_I64_RAW|SLOT_F64_RAW)[ \t]*\(|VAL_NUM_OFFSET|VAL_F64_OFFSET/)) { tok = substr(s, RSTART, RLENGTH); sub(/[ \t]*\($/, "", tok) printf "%s|%s|%s|%d\n", FILE, (scope == "" ? "" : scope), tok, NR s = substr(s, RSTART + RLENGTH) } - if (line ~ /data\.num_|[A-Za-z0-9_)\]]\.d_([^A-Za-z0-9_]|$)/ && scope !~ /^#define (VAL_NUM_RAW|SLOT_NUM_RAW|VAL_NUM_OFFSET)$/) + if (line ~ /data\.(num_|f64_|i64_)|[A-Za-z0-9_)\]]\.d_([^A-Za-z0-9_]|$)/ && scope !~ /^#define (VAL_NUM_RAW|VAL_F64_RAW|VAL_I64_RAW|SLOT_F64_RAW|VAL_NUM_OFFSET|VAL_F64_OFFSET)$/) printf "%s:%d: %s\n", FILE, NR, $0 >> BYPASS if (line ~ /->data\.builtin[ \t]*\(/) printf "%s:%d: ungated builtin call: %s\n", FILE, NR, $0 >> BYPASS incont = ($0 ~ /\\$/) diff --git a/tools/observer_oracle/ci_lane.sh b/tools/observer_oracle/ci_lane.sh index c1580f521..a8046f4b4 100755 --- a/tools/observer_oracle/ci_lane.sh +++ b/tools/observer_oracle/ci_lane.sh @@ -54,10 +54,11 @@ make build || fail "make build failed" phase "score the candidate against the committed floor (score.py --floor)" rc=0 -python3 tools/observer_oracle/score.py src/eigenscript --floor tools/observer_oracle/floor.json | tee "$tmp/oracle.out" || rc=$? +python3 tools/observer_oracle/score.py src/eigenscript --floor tools/observer_oracle/floor.json \ + --declared tests/phase2/oracle_expect.txt | tee "$tmp/oracle.out" || rc=$? case "$rc" in 0) ;; - 1) fail "score.py exit 1: a verdict was lost or split between arms (the FLOOR VIOLATION, FINDING or CAPABILITY WRONG lines above). The gate is working: fix the change, or fix a wrong label through derive.py/certify.py and re-measure the floor" ;; + 1) fail "score.py exit 1: a verdict was lost or split between arms (the FLOOR VIOLATION, FINDING, CAPABILITY WRONG or STALE DECLARATION lines above). The gate is working: fix the change, or fix a wrong label through derive.py/certify.py and re-measure the floor" ;; *) fail "score.py exit $rc: a harness error (the OBSERVER_ORACLE_ERROR line above), not a verdict" ;; esac n=$(python3 -c 'import json; print(len(json.load(open("tools/observer_oracle/floor.json"))["verdicts"]))') \ diff --git a/tools/observer_oracle/plants.py b/tools/observer_oracle/plants.py index 6936482c7..0518631f3 100644 --- a/tools/observer_oracle/plants.py +++ b/tools/observer_oracle/plants.py @@ -56,7 +56,7 @@ HERE = os.path.dirname(os.path.abspath(__file__)) VENV = os.path.expanduser("~/.local/venvs/oracle/bin/python") -LOOP_HEAD, GUARD = " loop while true:\n", " if m > 0:\n" +LOOP_HEAD, GUARD = " loop while true:\n", " if m > 0.0:\n" INJECT = {"osr_bail": ("osr", "cw is CW_S + CW_S"), "jit_bail_body": ("jit", "cw is CW_S + CW_S"), "num_body": (None, "cw is 1.0 + 2.0")} PROBE_ANSWERS = {"int64_fake": {"9223372036854775806": "true", "num_type": "int"}, @@ -375,6 +375,7 @@ def counts(): # --- the plants ----------------------------------------------------------------- def plants(tag, ieee): + from score import literal F = ["--floor", "{d}/floor.json"] sc = lambda *a: ["python3", "{d}/score.py", *a] cf = ["{venv}", "{d}/certify.py"] @@ -513,13 +514,9 @@ def plants(tag, ieee): ("zero-sign", "the runtime's zero sign lines flipped (it would hold -0.0 where the generator holds 0.0)", lambda d: shim(d, "zero_sign", tag), sc("{d}/obs_zero_sign"), 2, r"runtime=0 \(sign bit True\) generator=0.0 \(values must agree in every bit\).*"), - ("intmin-literal", "CONTROL: an int row x <- x * INT64_MIN + 0 from 0: the spliced INT64_MIN literal" + ("intmin-literal", "CONTROL: x <- x * INT64_MIN + 0 from 0: the spliced INT64_MIN literal" " must keep its parentheses (x_1 = 0, not -1)", - lambda d: (edit_json(d, "corpus.json", keep_rows(extra=extra_row(d, "int_counter", id="intmin_a", - gen={"kind": "affine", "x0": 0, "a": -2 ** 63, "b": 0, "int": True}, n=10, checkpoints=[10], - props={"limit": 0.0, "tends_to": None, "bounded": True, "deterministic": True}))), - certify_in(d), subprocess.run(["python3", f"{d}/derive.py", "--write"], check=True)), - sc(tag), 0, r"observer oracle exit 0: no gate failed.*"), + None, [tag, "-e", "x is 0\nx is x * " + literal(-2 ** 63) + " + 0\nprint of x"], 0, r"^0$"), ("double-scored", "converged scored against MATH and CONVENTION (the reported flag removed)", lambda d: edit_json(d, "corpus.json", lambda c: [x.pop("reported", None) for x in c["definitions"]]), sc(tag), 2, r"two scored definitions.*"), @@ -576,7 +573,7 @@ def plants(tag, ieee): sc(tag), 2, r"\S+: checkpoint \d+ read after \['\d+'\] assignments,\s+not \d+.*"), ("late-read-loop", "loop-form read after one assignment too many", lambda d: (edit_text(d, "score.py", "st = steps(row, xs, c)", "st = steps(row, generate(row[\"gen\"], c + 1), c + 1)"), - edit_text(d, "score.py", 'f"r is run_row of {c + 1}"', 'f"r is run_row of {c + 2}"')), + edit_text(d, "score.py", 'f"r is run_row of {float(c + 1)!r}"', 'f"r is run_row of {float(c + 2)!r}"')), sc(tag), 2, r"\S+: checkpoint \d+ read after \['\d+'\] assignments,\s+not \d+.*"), ("L1", "a label flipped by hand in labels.json", lambda d: edit_text(d, "labels.json", '"geo_decay_0.5@60": {"converged": ["CONVENTION", true', diff --git a/tools/observer_oracle/score.py b/tools/observer_oracle/score.py index 5e27fa216..c7691e95c 100644 --- a/tools/observer_oracle/score.py +++ b/tools/observer_oracle/score.py @@ -2,7 +2,7 @@ """Score EigenScript's observer against a ground-truth corpus (#1746, #1758). usage: score.py BINARY [--corpus PATH] [--numbers ieee|clamp] [--expect CAP,...] - [--floor FILE] [--write-floor FILE [--note TEXT]] + [--floor FILE] [--declared FILE] [--write-floor FILE [--note TEXT]] labels.json must equal derive.py's derivation (the label gate). Each predicate is scored against ONE question (converged, stable, improving: @@ -29,7 +29,12 @@ computing x (from the body's MUL to its OBSERVE_ASSIGN) on an inf, NaN or clamped value (the JIT's designed bail, #1758 N12) is reported as interpreted and passes, so its readings grade the interpreted observer for -the bailed iterations; any other non-native run is VACUOUS (exit 2). No +the bailed iterations. Int64 capability rows retain exact int literals; +until R5 their JIT-enabled runs may explicitly witness an interpreted int +opcode refusal (INT_* before observing x; no function thunk, and OSR +absent or its actual prefix exit at that int opcode). +All floor rows use float scaffolding and float recurrence operands, as on +the pinned pre-int anchor; every other non-native run is VACUOUS (exit 2). No aggregate. Predicate reads always run interpreted: the JIT compiles neither PREDICATE_SLOT nor PREDICATE_NAME. Values are compared with the generator's exactly (a zero's sign through atan2, since the runtime prints -0.0 as 0), @@ -47,7 +52,13 @@ Exit 1: a FINDING (any reading, REPORTED ones included, that differs between arms), a wrong verdict on a capability row, an expected capability -absent, or (--floor) a floor verdict now wrong. There are no waivers: a +absent, or (--floor) a floor verdict now wrong. --declared FILE (#1758, #1830: +Phase 2's tests/phase2/oracle_expect.txt, the shape of cost_expect.txt) lists +known-wrong CAPABILITY verdicts, one TSV row each: arm, row@checkpoint, +predicate, recover_round, reason. A declared wrong verdict does not fail; an +undeclared one does, and so does a STALE declaration, one whose verdict is +right now or was not examined, so a fix forces the declaration out. A row the +parser cannot read is a harness error. There are no waivers on the floor: a floor verdict agrees with its label, so losing it is wrong unless the label is, and a wrong label is fixed through derive/certify, which re-measures the floor. The floor's anchor is the binary build_anchor.sh builds from tag @@ -61,6 +72,7 @@ refused floor, an internal exception). """ import contextlib +from decimal import Decimal, InvalidOperation import fcntl import hashlib import json @@ -140,8 +152,15 @@ def literal(v): def steps(row, xs, upto): """x_0..x_upto as the runtime assigns them: a literal, or x * a + b.""" if row["gen"]["kind"] in IN_PROGRAM: - return program(row["gen"], upto) - return [("set", v) for v in xs[:upto + 1]] + out = program(row["gen"], upto) + else: + out = [("set", v) for v in xs[:upto + 1]] + # The non-capability floor measures the original binary64 observer. + # Python ints in its corpus JSON are exact mathematical values, not a + # request to change the measured execution tier after the int flip. + if row.get("capability") != "int64": + out = [(s[0], *(float(v) for v in s[1:])) for s in out] + return out def ck_line(at, ys, form): @@ -204,18 +223,19 @@ def emit_loop(row, xs, ys, c, warm): observer state is its own.""" st = steps(row, xs, c) aff = [s for s in st if s[0] == "aff"] - zero = 0 if row["gen"].get("int") else 0.0 - out = table("OPS", [0 if s[0] == "set" else 1 for s in st]) + integer = row.get("capability") == "int64" + zero, one = (0, 1) if integer else (0.0, 1.0) + out = table("OPS", [zero if s[0] == "set" else one for s in st]) out += table("A", [zero if s[0] == "set" else s[1] for s in st] if aff else []) out += table("B", [s[1] if s[0] == "set" else s[2] for s in st]) out += table("YB", ys[:c + 1]) if ys is not None else [] - out += ["OUT is []", "OUTY is []", "define run_row(m) as:", " i is 0", " if m > 0:", - " loop while true:", " if OPS[i] == 0:", " x is B[i]", + out += ["OUT is []", "OUTY is []", "define run_row(m) as:", f" i is {literal(zero)}", " if m > 0.0:", + " loop while true:", f" if OPS[i] == {literal(zero)}:", " x is B[i]", " else:", " x is x * A[i] + B[i]", " append of [OUT, x]"] out += [" y is YB[i]", " append of [OUTY, y]"] if ys is not None else [] - out += [" i is i + 1", " if i >= m:", " break", - " " + ck_line(c, ys, "loop"), " return 0", - f"for k in range of {warm}:", " r is run_row of 0", f"r is run_row of {c + 1}", + out += [f" i is i + {literal(one)}", " if i >= m:", " break", + " " + ck_line(c, ys, "loop"), " return 0.0", + f"for k in range of {warm}:", " r is run_row of 0.0", f"r is run_row of {float(c + 1)!r}", "for k in range of (len of OUT):", ' print of f"V {k} {OUT[k]}"', "for k in range of (len of OUTY):", ' print of f"Y {k} {OUTY[k]}"'] out += [sign_line("V", k, f"OUT[{k}]") for k in range(c + 1) if is_zero(xs[k])] @@ -278,6 +298,21 @@ def run(binary, prog, env): return p.returncode, p.stdout.splitlines(), errors, p.stderr, hot.group(0) if hot else "" +def printed_count(token): + """Parse a printed count by exact value, including N6's integral floats. + + Decimal avoids both truncating a fractional count and rounding large ints + through binary64. Runtime values still use check_value's bitwise oracle. + """ + try: + value = Decimal(token) + if value.is_finite() and value == value.to_integral_value(): + return int(value) + except (InvalidOperation, ValueError, OverflowError): + pass + raise HarnessError(f"non-integral printed count {token!r}") + + def parse_run(row, cks, form, rc, lines, errors, xs, ys): rid = row["id"] if rc != 0 or errors or not lines or lines[-1] != f"ROW_DONE {rid}": @@ -287,23 +322,23 @@ def parse_run(row, cks, form, rc, lines, errors, xs, ys): for ln in lines[:-1]: f = ln.split() if len(f) == 3 and f[0] in vals: - i = int(f[1]) + i = printed_count(f[1]) if i != last[f[0]] + 1: raise HarnessError(f"{rid}: {f[0]} line {i} out of order after {last[f[0]]}") last[f[0]], vals[f[0]][i] = i, f[2] continue if len(f) == 4 and f[0] == "S" and f[1] in vals and f[3] in ("true", "false"): - signs[(f[1], int(f[2]))] = f[3] == "true" + signs[(f[1], printed_count(f[2]))] = f[3] == "true" continue if len(f) != 9 + (ys is not None) or f[0] != "CK": raise HarnessError(f"{rid}: unexpected output line {ln!r}") - at = int(f[1]) + at = printed_count(f[1]) if at in got: raise HarnessError(f"{rid}: duplicate checkpoint {at}") if form == "straight" and (last["V"] != at or (ys is not None and last["Y"] != at)): raise HarnessError(f"{rid}: checkpoint {at} read after value {last['V']}:" " a read must follow the assignment of x_at") - if any(w != str(at + 1) for w in f[8:]): + if any(printed_count(w) != at + 1 for w in f[8:]): raise HarnessError(f"{rid}: checkpoint {at} read after {f[8:]} assignments," f" not {at + 1}: a read must fall between x_at and x_{at + 1}") if any(b not in ("true", "false") for b in f[2:8]): @@ -346,8 +381,8 @@ def grades(rid, tier, form, err, cks): f" {sorted({o[0] for o in ops if o[0].startswith('OBSERVE_ASSIGN')})}") -def witness(tier, rid, err, hot, held, c): - """("native" | "value bail" | "VACUOUS", detail) for the measured call's +def witness(tier, rid, err, hot, held, c, integer=False, int64=False): + """("native" | "value bail" | "int refusal" | "VACUOUS", detail) for the measured call's loop. The thunk's last exit must lie past the loop's back-edge (the body ran to its `break` natively). jit: the function thunk, with bked = 0 (after a bail the rest of the call runs interpreted), so every iteration ran @@ -377,6 +412,25 @@ def witness(tier, rid, err, hot, held, c): stop = f"{ops[at][0]}@{at}" if at in ops else f"the end of run_row ({at})" if at >= 0 else "no thunk" if ok: return "native", f"exits at {stop}, past the back-edge at {jb}" + # R1.3 through R5: exact int capability rows intentionally contain int + # opcodes which both JIT scanners refuse by default. Keep all their + # JIT-enabled verdicts scored, but name the actual interpreted tier. + # A float row or an int opcode after x's assignment cannot claim this + # witness. An OSR prefix may run the float indexing/control before the + # refusal; its recorded exit must be AT the int opcode, before observing + # x. Disabling the JIT globally still reds the native float rows. + head = jb + 3 - int(ops[jb][1]) + observed = min((o for o, (op, _arg) in ops.items() + if o >= head and op.startswith("OBSERVE_ASSIGN")), default=head) + intstops = {o: op for o, (op, _arg) in ops.items() + if head <= o < observed and op in ("INT_ZERO", "INT_ONE", "INT_CONST")} + osr_at = int(hrow[10]) + int(hrow[9]) if hrow[8] == "yes" and int(hrow[10]) == head else -1 + absent = tier == "jit" and hrow[8] in ("?", "no") + function_absent = hrow[2] == "no" if tier == "jit" else hrow[2] in ("?", "no") + refused = (absent and hrow[11] in intstops.values()) or osr_at in intstops + if integer and int64 and intstops and function_absent and refused: + detail = "no OSR thunk" if absent else f"OSR prefix exits at {intstops[osr_at]}@{osr_at}, before x's observe@{observed}" + return "int refusal", f"x observed interpreted: no function thunk; {detail} (R5)" mul = min((o for o, (op, _a) in ops.items() if op == "MUL" and o < jb), default=jb) asg = min((o for o, (op, _a) in ops.items() if op.startswith("OBSERVE_ASSIGN") and o > mul), default=mul) bad = [v for v in held[max(0, k - 1):k + 1] if not math.isfinite(v) or abs(v) >= CLAMP] @@ -441,7 +495,9 @@ def measure(binary, corpus, caps): grades(rid, tier, form, err, cks) res["runs"] += 1 if tier != "interp": - res["wit"][(tier, rid, cks[0])] = witness(tier, rid, err, hot, xs, cks[0]) + res["wit"][(tier, rid, cks[0])] = witness( + tier, rid, err, hot, xs, cks[0], + integer=row.get("capability") == "int64", int64=caps["int64"]) for at in cks: for p in PREDS: res["read"][(tier, form, rid, at, p)] = got[at][p] @@ -478,6 +534,33 @@ def verdicts(res, corpus, labels): return out +def load_declared(path, rows): + """--declared FILE -> {(arm, row, checkpoint, predicate): (recover_round, reason)}. + Only capability verdicts can be declared; anything unreadable is refused.""" + arms, out = {f"{t}/{f}" for t, f in ARMS}, {} + with open(path) as f: + lines = f.read().splitlines() + for n, line in enumerate(lines, 1): + if not line.strip(): + continue + fields = [x.strip() for x in line.split("\t")] + if len(fields) != 5: + raise HarnessError(f"{path}:{n}: expected five TSV fields" + " (arm, row@checkpoint, predicate, recover_round, reason)") + arm, rc, p, rnd, why = fields + rid, _, at = rc.partition("@") + row = rows.get(rid) + if (arm not in arms or not row or not row.get("capability") or not at.isdigit() + or int(at) not in row["checkpoints"] or p not in PREDS): + raise HarnessError(f"{path}:{n}: not a capability verdict: {arm} {rc} {p}") + if not re.fullmatch(r"R[1-9][0-9]*(?:\.[1-9][0-9]*)?", rnd) or not why: + raise HarnessError(f"{path}:{n}: recover_round (a round name) and reason are required") + if (arm, rid, int(at), p) in out: + raise HarnessError(f"{path}:{n}: duplicate declaration {arm} {rc} {p}") + out[(arm, rid, int(at), p)] = (rnd, why) + return out + + def findings(res): """Every (row, checkpoint, predicate) whose readings differ between arms, REPORTED readings included: a split is a fault whatever the label.""" @@ -595,7 +678,8 @@ def report(v, res, corpus, numbers, caps, probes, labels, reported, expect): print(f" {'EXPECTED ' if cap in expect else 'WARNING: '}{line}ABSENT, {n} row(s) SKIPPED") print(f"PATHS, checked in each of the {res['runs']} programs' EIGS_DUMP_BC listing: " + "; ".join(f"the {form} form observes x through {a} and reads it through {r}" for form, (a, r) in GRADES.items())) - print("\nTIER WITNESS, per run (n native, v interpreted: a value bail on a non-finite or clamped value, V VACUOUS;" + print("\nTIER WITNESS, per run (n native, v interpreted: a value bail on a non-finite or clamped value," + " i interpreted int opcode refusal until R5, V VACUOUS;" " jit: every iteration, x_0 included; osr: the last iteration only, #1768):") for row in corpus["rows"]: if row["id"] in res["held"]: @@ -605,10 +689,10 @@ def report(v, res, corpus, numbers, caps, probes, labels, reported, expect): for t in ("jit", "osr"): w = {k[1:]: x for k, x in res["wit"].items() if k[0] == t} print(f" {t}: {sum(1 for x in w.values() if x[0] == 'native')} of {len(w)} runs witness a native body;" - f" minimum: every run that is not a value bail, and at least one") + f" minimum: every run except a value bail or witnessed int opcode refusal, and at least one") for (rid, c), (status, detail) in w.items(): if status != "native": - print(f" {status.upper() if status == 'VACUOUS' else 'interpreted (value bail)'} {t}" + print(f" {status.upper() if status == 'VACUOUS' else 'interpreted (' + status + ')'} {t}" f" {rid}@{c}: {detail}") print(" predicate reads run interpreted in every tier: the JIT compiles neither PREDICATE_SLOT nor PREDICATE_NAME") print("\nSCORED VERDICTS, one question per predicate: TP FP FN TN right/total") @@ -655,7 +739,7 @@ def report(v, res, corpus, numbers, caps, probes, labels, reported, expect): def main(argv): args, opts = argv[1:], {} - for flag in ("--corpus", "--numbers", "--expect", "--floor", "--write-floor", "--note"): + for flag in ("--corpus", "--numbers", "--expect", "--floor", "--declared", "--write-floor", "--note"): if flag in args: i = args.index(flag) if i + 1 >= len(args): @@ -665,7 +749,7 @@ def main(argv): expect = set(filter(None, opts.get("--expect", "").split(","))) if len(args) != 1 or opts.get("--numbers", "clamp") not in ("ieee", "clamp") or expect - set(CAPS): raise HarnessError("usage: score.py BINARY [--corpus PATH] [--numbers ieee|clamp] [--expect ieee,int64]" - " [--floor FILE] [--write-floor FILE [--note TEXT]]") + " [--floor FILE] [--declared FILE] [--write-floor FILE [--note TEXT]]") binary = os.path.abspath(args[0]) if not (os.path.isfile(binary) and os.access(binary, os.X_OK)): raise HarnessError(f"binary not found or not executable: {binary}") @@ -680,6 +764,7 @@ def main(argv): labels = load_labels(corpus, certs, os.path.join(cdir, "labels.json")) pre = floor_preflight(opts["--floor"]) if "--floor" in opts else None rows = {r["id"]: r for r in corpus["rows"]} + declared = load_declared(opts["--declared"], rows) if "--declared" in opts else None ieee, caps, probes = capabilities(binary, opts.get("--numbers")) with anchor_lock() if stamp else contextlib.nullcontext(): res = measure(binary, corpus, caps) @@ -700,9 +785,22 @@ def main(argv): missing = sorted(c for c in expect if not caps[c]) print(f"\nCAPABILITY ROWS (every scored verdict must be right): {len(ran)} run, {len(res['skipped'])} skipped," f" {len(capwrong)} wrong verdict(s); expected and absent: {missing or 'none'}") + stale = [] + if declared is not None: + stale = sorted(k for k in declared if k not in capwrong) + print(f"DECLARED (--declared {opts['--declared']}): {len(declared)} declaration(s)," + f" {sum(1 for k in capwrong if k in declared)} wrong as declared," + f" {sum(1 for k in capwrong if k not in declared)} undeclared wrong, {len(stale)} stale") + for k in sorted(k for k in capwrong if k in declared): + print(f" DECLARED WRONG {k[0]} {k[1]}@{k[2]} {k[3]}: expected={str(v[k][1]).lower()}" + f" recover_round={declared[k][0]} reason={declared[k][1]}") + for k in stale: + print(f" STALE DECLARATION {k[0]} {k[1]}@{k[2]} {k[3]}: " + + ("the verdict is right now; remove the declaration" if k in v else "not examined")) + capwrong = [k for k in capwrong if k not in declared] for arm, rid, at, p in capwrong: print(f" CAPABILITY WRONG {arm} {rid}@{at} {p}: expected={str(v[(arm, rid, at, p)][1]).lower()}") - fail = 1 if split or capwrong or missing else 0 + fail = 1 if split or capwrong or stale or missing else 0 if pre: fail |= check_floor(pre, opts["--floor"], v, rows, corpus, labels) if stamp: diff --git a/tools/phase2_builtins.sh b/tools/phase2_builtins.sh new file mode 100755 index 000000000..512ad8f53 --- /dev/null +++ b/tools/phase2_builtins.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# The pre-registered bar owns the predicates and probe execution. +# R1.7-D5: earlier closures retain A2; R1.7 closures prove D2 per line. +set -eu +ROOT=$(cd "$(dirname "$0")/.." && pwd) +[ "$#" -eq 2 ] || { echo "usage: tools/phase2_builtins.sh " >&2; exit 2; } +exec python3 "$ROOT/tests/phase2/builtins/make_table.py" --final --run "$1" "$2" diff --git a/tools/phase2_check.py b/tools/phase2_check.py new file mode 100755 index 000000000..2c2fe4fd8 --- /dev/null +++ b/tools/phase2_check.py @@ -0,0 +1,1529 @@ +#!/usr/bin/env python3 +"""tools/phase2_check.py -- #1758 Phase 2's row checker (design 9.2). Run it +through tools/phase2_check.sh; `--help` lists the options. + +ROWS. tests/phase2/MAP.md maps the #1758 items to rows; a row is +tests/phase2/rows/.eigs (or .sh, run as `bash .sh `) with +its exact branch stdout in .out and a header (`# ROW:`, `# ITEMS:`, +`# EXPECT-RC:`, `# SHAPE:`, `# WITNESS:`, `# OPS:`, optional `# TIERS:`). +A row that needs a capability build declares `# PROFILE: server|db|zlib`: +it runs on $TAG_BIN_

and $BRANCH_BIN_

, the per-builtin table's matched +builds (ruling A3), each first proven by its _profiles/

.eigs witness. +Controls (ctl.*) live in tests/phase2/controls/ and must pass on BOTH +binaries. `--through Rk` selects the rows whose [Rn] has landed by Rk. + +TIERS. off (EIGS_JIT_OFF=1), default, osr (EIGS_JIT_OSR_THRESHOLD=1) and +trace (EIGS_TRACE, default JIT: the leaf evaluator is off there, so this +tier is the leaf differential). Every run: 30 s bound, stdin /dev/null, a +fresh scratch cwd, every inherited EIGS_* variable removed. + +VERDICTS per row and tier: branch PASS = rc equals EXPECT-RC and stdout +equals the .out; tag FAIL = not that; VACUOUS = the branch passed but the +row's tier witness (design 9.1) does not hold. rc 124 is TIMEOUT and rc >= +128 SIGNAL: a FAIL on the branch, counted apart on the tag. + +WITNESSES (read on the branch, from EIGS_JIT_HOT / STATS / STOPS and +EIGS_DUMP_BC; nothing in production changes so a witness can see it): + leaf F... JIT-on normal runs (default, osr) count no entry for F, and + the trace run counts K >= 1: the leaf computed every call. + (With EIGS_JIT_OFF the hot table counts no calls at all, so + the off tier cannot witness.) + leaf-gen F... the default run and the trace run count the same K >= 1. + fn F default and osr runs: `jit yes`, `adv RET`, compiled >= 1. + fn-stop F OP default run: `jit yes`, the op at offset `adv` is OP. + osr C osr run: `osr yes` and oadv == len (the return sentinel). + osr-stop C OP osr run: `osr yes`, oadv != len, the op at oent+oadv is OP. +A -stop whose op is the chunk's compile stop must also be named by +EIGS_JIT_STOPS. + +EVERY MODE runs two gates: the token gate (every token a landed round +deleted is absent from code) and the inventory's own verdict +(tools/phase2_inventory.py --check: every slot classifier has a reviewed +disposition, and the committed docs/design/phase2-inventory.txt is exactly +the generated one, #1804). + +MECHANICAL ROUNDS (R1.1, R1.2, R1.6a/e/f/z, R2.1) have no rows of their own; --through +one of them runs its gates instead: the int-source check in two halves -- +static: before R1.3 no runtime code calls slot_from_int or make_int outside +the canonicalizing writers, make_int does not exist, and TAG_SMI or an SMI +bit pattern is spelled only at its value_slot.h homes; dynamic (--smi-trap, +R1.1/R1.2): the corpus differential against a `make smi-trap` build, on +which every slot classifier aborts at an SMI -- the C slot test, and the +corpus differential against --baseline (the binary from before the round). +The differential runs each file under its run conditions from the suite's +loader (tests/suite_program_env.sh) and requires every nonzero exit to be +declared (tests/phase2/corpus_nonzero.txt); its header is the reference. +A gate tool that writes to stderr fails, with its words (tool()). The embed +smoke's deliberate wrong-input diagnostics are allowed explicitly; sanitizer +output still fails at every exit status. The checker runs under `python3 -W +error` (tools/phase2_check.sh). + +Exit 0 iff the population checks pass, every tier has examined == tag_fail +== branch_pass > 0 (or the round is mechanical and every gate passed), +vacuous == 0, and every landed control passes on both binaries. +`--calibrate` runs the design's calibration list (9.2) on fixtures in +tests/phase2/calib/ and prints `calibration: cases=N as-expected=N`. +""" +import argparse +import importlib.util +import os +import re +import shutil +import subprocess +import sys +import tempfile + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, 'tools')) +import phase2_inventory as inv # noqa: E402 (strip_c: one C-comment grammar) + +# The no-int-source mechanism ended at R1.3. Its static calibration uses +# the exact last no-producer source; current runtime safety is the MAP/ASan gate. +STATIC_INT_SOURCE_REF = '0c43ec88ca8915f9d365e3e63549cb1e322c76d3' +ROUNDS = ['R1.1', 'R1.2', 'R1.3', 'R1.4', 'R1.5', + 'R1.6a', 'R1.6b', 'R1.6c', 'R1.6d', 'R1.6e', 'R1.6f', 'R1.6g', 'R1.6z', 'R1.7', 'R1.8', + 'R2.1', 'R2.2', 'R2.3', 'R2.4', 'R2.5', 'R2.6', 'R3'] +# Gates every mode runs: the token gate (a landed round's deleted tokens stay +# deleted) and the inventory's own verdict, which includes the committed +# docs/design/phase2-inventory.txt being exactly the generated one (#1804). +EVERY_MODE = ('tokens', 'inventory') +MECHANICAL = { + 'R1.1': ('int-source', 'slot-test', 'corpus-diff', 'smi-trap'), + 'R1.2': ('int-source', 'slot-test', 'corpus-diff', 'smi-trap'), + 'R1.6a': ('reader-test', 'builtins-table'), + 'R1.6e': ('builtins-table',), + 'R1.6f': ('builtins-table',), + 'R1.6z': ('builtins-table', 'bridge-deletion-compile'), + 'R1.8': ('embed-int',), + 'R2.1': ('slot-test', 'corpus-diff'), +} +ITEMS = ['N%d' % i for i in range(1, 15)] + ['N4b', 'A', 'B', 'T1', 'O1', 'O2', 'O3', 'O4'] +# design 2.7: the tokens each round deletes. A later round adds its list here +# in the commit that deletes them (R1.6's bridge, 21 tokens, with R1.6z). +DELETED_TOKENS = { + 'R1.1': ['slot_is_num', 'slot_is_numeric', 'SLOT_NUM_RAW', 'slot_from_num', 'slot_as_double'], + 'R1.2': ['VAL_NUM', 'VAL_NUM_RAW', 'VAL_NUM_OFFSET', 'num_'], + 'R1.3': ['eigs_int_unwired'], + 'R1.6z': ['eigs_num_arg', 'eigs_num_arg_slow', 'eigs_list_num', 'eigs_elem_num', + 'eigs_opt_num', 'eigs_slot_num', 'eigs_slot_elem_num', 'eigs_slot_opt_num', + 'buf_num_arg', 'make_num', 'make_num_permanent', 'list_set_num', + 'list_append_num', 'list_rewrite_num', 'dict_value_set_num', + 'dict_value_rewrite_num', 'list_get_num', 'dict_value_get_num', + 'finite_num_to_byte', 'buffer_read_byte', 'buffer_read_num'], + 'R2.2': ['num_guard', 'num_guard_named', 'EIGS_NUM_MAX'], +} +# design 2.1: the canonicalizing writers convert an existing VAL_INT, so they +# are the only runtime callers of an int writer allowed once VAL_INT exists +# (R1.2). In R1.1 no VAL_INT exists, so no runtime code may call one at all. +INT_SOURCE_ALLOWED = { + 'R1.1': set(), + 'R1.2': {'eigs_container_slot', 'eigs_slot_adopt', 'eigs_slot_store_owned', + 'eigs_slot_store_borrow', 'slot_from_value'}, +} +# The SMI tag's own definitions, the only code that may name TAG_SMI or spell +# its bits: a producer that writes the bits itself calls no writer, so a scan +# of writer names never sees it (the R1.1 critic's P3 and P5). +SMI_TAG_HOMES = {('value_slot.h', '#define TAG_SMI'), ('value_slot.h', '#define SLOT_SMI_TRAP'), + ('value_slot.h', 'slot_is_smi'), ('value_slot.h', 'slot_from_int')} +# TAG_SMI, or a literal whose top 16 bits are the tag: 0xFFFA itself (a tag +# compare) or a 16-digit 0xFFFA............ (the full tag word). +SMI_BITS_RE = re.compile(r'\bTAG_SMI\b|\b0[xX][fF]{3}[aA](?:[0-9a-fA-F]{12})?[uUlL]*\b') +WITNESS_KINDS = {'none', 'leaf', 'leaf-gen', 'fn', 'fn-stop', 'osr', 'osr-stop'} +TIERS = ['off', 'default', 'osr', 'trace'] +HOT_HDR = re.compile(r'^chunk\s+exec\s+jit\s+pct\s+adv\s+len\s+nat%\s+bked\s+osr\s+oadv\s+oent\s+stop$') + + +def rindex(r): + return ROUNDS.index(r) + + +def read_text(path, **kw): + with open(path, encoding='utf-8', **kw) as f: + return f.read() + + +def write_text(path, text): + with open(path, 'w', encoding='utf-8') as f: + f.write(text) + + +# ---- the MAP --------------------------------------------------------------- +ROW_LINE = re.compile(r'^- `([^`]+)` (?:\[([^\]]+)\]|\((control)\))') +SHAPE_LINE = re.compile(r'^\s+shape:\s*([LFO])\s+witness:\s*(.*?)\s+ops:\s*(.*?)\s*$') + + +def parse_witness(text): + text = re.sub(r'\([^)]*\)', ' ', text).split() + if not text: + return None + kind = text[0] + if kind.endswith('-stop'): + return {'kind': kind, 'chunks': text[1:-1], 'op': text[-1] if len(text) > 2 else None} + return {'kind': kind, 'chunks': text[1:], 'op': None} + + +def parse_map(path): + rows, bad, item = [], [], None + lines = read_text(path).split('\n') + for n, line in enumerate(lines, 1): + m = re.match(r'^### (\S+)\s*$', line) + if m: + item = m.group(1) + continue + m = ROW_LINE.match(line) + if not m: + continue + rid, rnd, ctl = m.group(1), m.group(2), m.group(3) + row = {'id': rid, 'item': item, 'line': n, 'shape': 'S', 'sketch': line[m.end():], + 'witness': {'kind': 'none', 'chunks': [], 'op': None}, 'ops': []} + if rid.startswith('ctl.') != bool(ctl): + bad.append('MAP:%d: `%s`: a control must be marked (control), and only a control' % (n, rid)) + row['kind'] = 'control' if ctl else 'gate' if rid.startswith('gate.') else 'row' + if rnd: + round_text, *qualifiers = [part.strip() for part in rnd.split(',')] + if qualifiers not in ([], ['invariant']) or (qualifiers and row['kind'] != 'row'): + bad.append('MAP:%d: `%s` has invalid round qualifiers [%s]' % (n, rid, rnd)) + continue + row['invariant'] = qualifiers == ['invariant'] + parts = round_text.split('/') + if any(p not in ROUNDS for p in parts): + bad.append('MAP:%d: `%s` names an unknown round [%s]' % (n, rid, rnd)) + continue + row['round'] = max(parts, key=rindex) + else: + row['round'] = None + for follow in lines[n:n + 4]: + if follow.startswith('- ') or follow.startswith('#'): + break + ms = SHAPE_LINE.match(follow) + if ms: + row['shape'] = ms.group(1) + w = parse_witness(ms.group(2)) + if not w or w['kind'] not in WITNESS_KINDS: + bad.append('MAP:%d: `%s` has an unknown witness %r' % (n, rid, ms.group(2))) + else: + row['witness'] = w + row['ops'] = ms.group(3).split() + rows.append(row) + return rows, bad + + +def header(path): + h = {} + for line in read_text(path).split('\n'): + m = re.match(r'^#\s*([A-Z-]+):\s*(.*?)\s*$', line) + if not m: + if line.strip() and not line.startswith('#'): + break + continue + h[m.group(1)] = m.group(2) + return h + + +def row_file(d, rid): + for ext in ('.eigs', '.sh'): + p = os.path.join(d, rid + ext) + if os.path.exists(p): + return p + return None + + +def builtin_landed(path=None): + """Use the table checker's declaration parser; one grammar for both gates.""" + module_path = os.path.join(ROOT, 'tests/phase2/builtins/make_table.py') + spec = importlib.util.spec_from_file_location('phase2_builtin_table', module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module.read_landed(path or os.path.join(ROOT, 'tests/phase2/builtins_landed.txt')) + + +def derive_through(map_rows, rows_dir=None, landed=None): + """Last complete round before a gap, with diagnostics for partial rounds. + + R1.6b..z need their table declaration as well as every MAP row file. + R1.6a migrates no builtin and need not be declared; its gates always run + in CI. An explicit a declaration can select its mechanical round. + Other rounds retain the existing all-files rule; rowless non-R1.6 rounds + do not select themselves. This function is also the CI entry point. + """ + rows_dir = rows_dir or os.path.join(ROOT, 'tests/phase2/rows') + landed = set(builtin_landed() if landed is None else landed) + have = {} + for row in map_rows: + if row['kind'] == 'row' and row['round']: + have.setdefault(row['round'], []).append( + (row['id'], bool(row_file(rows_dir, row['id'])))) + through, complete, diagnostics = None, True, [] + for rnd in ROUNDS: + entries = have.get(rnd, []) + if rnd == 'R1.6a' and rnd not in landed: + continue + declared_round = rnd.startswith('R1.6') + if not entries and not declared_round: + continue + got = [rid for rid, exists in entries if exists] + ready = len(got) == len(entries) and (not declared_round or rnd in landed) + if complete and ready: + through = rnd + continue + complete = False + if got: + diagnostics.append('%s has %d of %d row files (%s): not checked until all land%s' % ( + rnd, len(got), len(entries), ' '.join(got), + '; table round is undeclared' if declared_round and rnd not in landed else '')) + return through, diagnostics + + +# ---- running --------------------------------------------------------------- +def run(binary, path, env_extra, scratch): + env = {k: v for k, v in os.environ.items() if not k.startswith('EIGS_')} + env.update(env_extra) + cwd = tempfile.mkdtemp(dir=scratch) + if 'EIGS_TRACE' in env and env['EIGS_TRACE'] == '@': + env['EIGS_TRACE'] = os.path.join(cwd, 't.tape') + cmd = (['bash', path, binary] if path.endswith('.sh') else [binary, path]) + tmo = shutil.which('timeout') or shutil.which('gtimeout') + if tmo: + cmd = [tmo, '30'] + cmd + try: + p = subprocess.run(cmd, cwd=cwd, env=env, stdin=subprocess.DEVNULL, + capture_output=True, timeout=60) + return p.returncode, p.stdout.decode('utf-8', 'replace'), p.stderr.decode('utf-8', 'replace') + except subprocess.TimeoutExpired: + return 124, '', 'phase2-check: the 60 s backstop fired' + finally: + shutil.rmtree(cwd, ignore_errors=True) + + +PROFILES = ('server', 'db', 'zlib') +PROFILE_WITNESS = os.path.join(ROOT, 'tests', 'phase2', 'builtins', 'builtins', '_profiles') + + +def profile_bins(profile, scratch): + """The (tag, branch) binaries for a `# PROFILE:` row, or (None, why). + A row never rewrites its own binary path. R1.6f-D4: CI passes + src/eigenscript, which the rows' release->server rewrite did not match, + so both server rows ran a build without HTTP or NET. Each matched build + must also print profile-ok from its capability witness.""" + if profile not in PROFILES: + return None, 'PROFILE %r is not one of release|%s' % (profile, '|'.join(PROFILES)) + picked = [] + for side in ('TAG', 'BRANCH'): + var = '%s_BIN_%s' % (side, profile.upper()) + b = os.environ.get(var, '') + if not b or not os.access(b, os.X_OK): + return None, '$%s is unset or not executable' % var + rc, so, _ = run(b, os.path.join(PROFILE_WITNESS, profile + '.eigs'), {}, scratch) + # The table's own predicate (run_probes.sh profile_ok): rc 0 and a + # line that is exactly profile-ok. The server witness's model probe + # prints a loader banner first. + if rc != 0 or 'profile-ok' not in so.split('\n'): + return None, '$%s (%s) lacks the %s capability (witness rc=%d)' % (var, b, profile, rc) + picked.append(os.path.abspath(b)) + return tuple(picked), None + + +TIER_ENV = {'off': {'EIGS_JIT_OFF': '1'}, 'default': {}, 'osr': {'EIGS_JIT_OSR_THRESHOLD': '1'}, + 'trace': {'EIGS_TRACE': '@'}} + + +HOT_TITLE = re.compile(r'^=== Hot chunks \(top (\d+) of (\d+)\) ===$') + + +def hot_rows(err): + """The EIGS_JIT_HOT table, read against its own shape: the title declares + N rows; the column header, exactly N twelve-column rows and the + `native-byte share` line follow. Anything else is an instrument failure + (None), never a shorter table.""" + lines = [l.strip() for l in err.split('\n')] + titles = [k for k, l in enumerate(lines) if HOT_TITLE.match(l)] + if len(titles) != 1: + return None + t = titles[0] + want = int(HOT_TITLE.match(lines[t]).group(1)) + if t + 3 + want >= len(lines) or not lines[t + 1].startswith('total chunk entries:') \ + or not HOT_HDR.match(lines[t + 2]): + return None + rows = {} + for l in lines[t + 3:t + 3 + want]: + w = l.split() + if len(w) != 12 or not w[1].isdigit() or w[0] in rows: + return None + rows[w[0]] = dict(exec=int(w[1]), jit=w[2], adv=w[4], len=int(w[5]), osr=w[8], + oadv=int(w[9]), oent=int(w[10]), stop=w[11]) + if not lines[t + 3 + want].startswith('native-byte share'): + return None + return rows + + +def compiled(err): + m = re.search(r'\[jit\] scanned=\d+ compiled=(\d+)', err) + return int(m.group(1)) if m else -1 + + +def bytecode(text, chunk): + """offset -> opcode for from EIGS_DUMP_BC. The chunk's header must + occur exactly once and its offsets must start at 0 and rise; otherwise {} + (an instrument failure, which the witness reports).""" + ops, cur, headers, last = {}, None, 0, -1 + for l in text.split('\n'): + m = re.match(r'^=== (\S+) \((\S+?), (\d+) bytes', l) + if m: + cur = m.group(2) + headers += cur == chunk + continue + m = re.match(r'^(\d{4}) \[L\d+\] (\S+)', l) + if m and cur == chunk: + off = int(m.group(1)) + if off <= last or (last < 0 and off != 0): + return {} + ops[off] = m.group(2) + last = off + return ops if headers == 1 else {} + + +def witness(binary, path, w, scratch): + """(holds, evidence). Instrument failures (no hot table) do not hold.""" + kind, chunks, op = w['kind'], w['chunks'], w['op'] + diag = {'EIGS_JIT_HOT': '1', 'EIGS_JIT_STATS': '1', 'EIGS_JIT_STOPS': '1'} + ev = [] + if kind in ('leaf', 'leaf-gen'): + _, _, terr = run(binary, path, dict(diag, EIGS_TRACE='@'), scratch) + thot = hot_rows(terr) + if thot is None: + return False, 'no EIGS_JIT_HOT table in the trace run' + ok = True + for tier in ('default', 'osr'): + _, _, err = run(binary, path, dict(TIER_ENV[tier], **diag), scratch) + hot = hot_rows(err) + if hot is None: + return False, 'no EIGS_JIT_HOT table (%s)' % tier + for c in chunks: + kn, kt = hot.get(c, {}).get('exec', 0), thot.get(c, {}).get('exec', 0) + ev.append('%s %s=%d trace=%d' % (c, tier, kn, kt)) + if kt < 1 or (kind == 'leaf' and kn != 0) or (kind == 'leaf-gen' and kn != kt): + ok = False + if kind == 'leaf-gen': + break # the generic path is the same in both JIT tiers + return ok and bool(chunks), '; '.join(ev) + tiers = ('default', 'osr') if kind == 'fn' else ('default',) if kind == 'fn-stop' else ('osr',) + c = chunks[0] if chunks else None + ok = bool(c) + for tier in tiers: + env = dict(TIER_ENV[tier], **diag) + _, _, err = run(binary, path, env, scratch) + hot = hot_rows(err) + r = (hot or {}).get(c) + if r is None: + return False, '%s: no EIGS_JIT_HOT row for %s' % (tier, c) + ev.append('%s %s jit=%s adv=%s len=%d osr=%s oadv=%d oent=%d stop=%s compiled=%d' % ( + tier, c, r['jit'], r['adv'], r['len'], r['osr'], r['oadv'], r['oent'], r['stop'], + compiled(err))) + if compiled(err) < 1: + ok = False + if kind == 'fn': + ok = ok and r['jit'] == 'yes' and r['adv'] == 'RET' + elif kind == 'osr': + ok = ok and r['osr'] == 'yes' and r['oadv'] == r['len'] + else: + # EIGS_DUMP_BC writes to stderr; the program's own stdout is + # never read as a dump line (the R1.1 critic's class) + _, _, derr = run(binary, path, dict(TIER_ENV[tier], EIGS_DUMP_BC='1'), scratch) + ops = bytecode(derr, c) + if kind == 'fn-stop': + at = None if r['adv'] in ('RET', '?') else int(r['adv']) + ok = ok and r['jit'] == 'yes' and at is not None and ops.get(at) == op + else: + at = r['oent'] + r['oadv'] + ok = ok and r['osr'] == 'yes' and r['oadv'] != r['len'] and ops.get(at) == op + ev.append('op_at_stop=%s' % (ops.get(at) if at is not None else 'RET')) + if ok and r['stop'] == op and not re.search(r'\b%s\b' % re.escape(op), err.split( + '=== Hot chunks')[0]): + ok = False + ev.append('compile stop %s not named by EIGS_JIT_STOPS' % op) + return ok, '; '.join(ev) + + +# ---- the row check ----------------------------------------------------------- +def check(map_rows, rows_dir, ctl_dir, tag, branch, through, scratch, out): + """Runs the landed rows and controls; returns (ok, counters).""" + landed = [r for r in map_rows if r['kind'] == 'row' and r['round'] and + rindex(r['round']) <= rindex(through)] + controls = [r for r in map_rows if r['kind'] == 'control'] + gates = [r for r in map_rows if r['kind'] == 'gate' and r['round'] and + rindex(r['round']) <= rindex(through)] + bad = [] + ids = {r['id'] for r in map_rows} + files = sorted(f for f in (os.listdir(rows_dir) if os.path.isdir(rows_dir) else []) + if f.endswith(('.eigs', '.sh'))) + for f in files: + stem = f.rsplit('.', 1)[0] + if stem not in ids: + bad.append('row file %s has no MAP id' % f) + # controls land with the first round that has rows of its own (R1.3) + want_controls = any(rindex(r['round']) <= rindex(through) for r in map_rows + if r['kind'] == 'row' and r['round']) + ctl_files = sorted(f for f in (os.listdir(ctl_dir) if os.path.isdir(ctl_dir) else []) + if f.endswith(('.eigs', '.sh'))) + for f in ctl_files: + if f.rsplit('.', 1)[0] not in ids: + bad.append('control file %s has no MAP id' % f) + todo, profile_cache = [], {} + for r in landed + (controls if want_controls else []): + d = ctl_dir if r['kind'] == 'control' else rows_dir + p = row_file(d, r['id']) + if not p: + bad.append('MAP id %s [%s] has no file in %s' % (r['id'], r['round'] or 'control', d)) + continue + h = header(p) + if h.get('ROW') != r['id']: + bad.append('%s: header ROW %r is not its id' % (p, h.get('ROW'))) + if h.get('SHAPE', 'S') != r['shape']: + bad.append('%s: SHAPE %s, the MAP says %s' % (p, h.get('SHAPE'), r['shape'])) + hw = parse_witness(h.get('WITNESS', 'none')) or {} + if (hw.get('kind'), hw.get('chunks'), hw.get('op')) != ( + r['witness']['kind'], r['witness']['chunks'], r['witness']['op']): + bad.append('%s: WITNESS %r differs from the MAP' % (p, h.get('WITNESS'))) + if not os.path.exists(p.rsplit('.', 1)[0] + '.out'): + bad.append('%s: no .out' % p) + continue + bins = (tag, branch) + if h.get('PROFILE', 'release') != 'release': + if h['PROFILE'] not in profile_cache: + profile_cache[h['PROFILE']] = profile_bins(h['PROFILE'], scratch) + bins, why = profile_cache[h['PROFILE']] + if not bins: + bad.append('%s: %s' % (p, why)) + continue + todo.append((r, p, h, bins)) + for g in gates: + # a gate names a check the Done-when runs on its own: its file or + # make target must exist (it is listed, never run or counted here) + named = [t for t in re.findall(r'`([^`]+)`', g['sketch']) + if t.startswith('make ') or ('/' in t and ' ' not in t)] + for t in named: + found = (subprocess.run(['make', '-n', '-C', ROOT] + t.split()[1:], capture_output=True + ).returncode == 0 if t.startswith('make ') + else os.path.exists(os.path.join(ROOT, t))) + if not found: + bad.append('gate %s names %r, which does not exist' % (g['id'], t)) + if not named: + bad.append('gate %s names no file or make target' % g['id']) + cnt = {t: dict(examined=0, tag_fail=0, branch_pass=0, vacuous=0, tag_timeout=0, tag_signal=0) + for t in TIERS} + ctl_n = ctl_both = witnessed = ctl_witnessed = ctl_vacuous = 0 + invariant_n = invariant_pass = 0 + for r, p, h, (row_tag, row_branch) in todo: + want = read_text(p.rsplit('.', 1)[0] + '.out') + want_rc = int(h.get('EXPECT-RC', '0')) + tiers = h.get('TIERS', ' '.join(TIERS)).split() + if not tiers or len(tiers) != len(set(tiers)) or any(t not in TIERS for t in tiers): + bad.append('%s: invalid or empty TIERS' % p) + continue + is_ctl = r['kind'] == 'control' + is_invariant = r.get('invariant', False) + passes = {} + for t in tiers: + res = {} + # R1.7-D3: invariants preserve the landed branch contract. The + # v0.46 anchor predates ints; base-green is separately preregistered. + arms = [('branch', row_branch)] if is_invariant else [('tag', row_tag), ('branch', row_branch)] + for who, b in arms: + rc, so, se = run(b, p, TIER_ENV[t], scratch) + res[who] = (rc == want_rc and so == want, rc) + passes[t] = res + if is_ctl or is_invariant: + continue + c = cnt[t] + c['examined'] += 1 + ok_tag, rc_tag = res['tag'] + ok_br, rc_br = res['branch'] + if not ok_tag: + c['tag_fail'] += 1 + c['tag_timeout'] += rc_tag == 124 + c['tag_signal'] += rc_tag >= 128 + if ok_br: + c['branch_pass'] += 1 + else: + out(' BRANCH-FAIL %s tier=%s rc=%d%s' % (r['id'], t, rc_br, + ' TIMEOUT' if rc_br == 124 else ' SIGNAL' if rc_br >= 128 else '')) + if ok_tag: + out(' TAG-PASS %s tier=%s: the row does not fail on the tag' % (r['id'], t)) + if is_invariant: + invariant_n += 1 + if all(res['branch'][0] for res in passes.values()): + invariant_pass += 1 + else: + out(' INVARIANT-FAIL %s: %s' % (r['id'], passes)) + if r['witness']['kind'] != 'none' and all(res['branch'][0] for res in passes.values()): + holds, ev = witness(row_branch, p, r['witness'], scratch) + if not holds: + invariant_pass -= 1 + out(' INVARIANT-VACUOUS %s [%s]' % (r['id'], ev)) + else: + out(' INVARIANT-WITNESS %s [%s]' % (r['id'], ev)) + continue + if is_ctl: + ctl_n += 1 + if all(v[0] for res in passes.values() for v in res.values()): + ctl_both += 1 + else: + out(' CONTROL-FAIL %s: %s' % (r['id'], passes)) + # A control may claim a real tier too. Its preserved stdout is + # insufficient evidence that a JIT/leaf arm actually ran. + if r['witness']['kind'] != 'none' and all(passes[t]['branch'][0] for t in tiers): + holds, ev = witness(row_branch, p, r['witness'], scratch) + if holds: + ctl_witnessed += 1 + out(' CONTROL-WITNESS %s %s holds [%s]' % (r['id'], r['witness']['kind'], ev)) + else: + ctl_vacuous += 1 + out(' CONTROL-VACUOUS %s %s [%s]' % (r['id'], r['witness']['kind'], ev)) + continue + if r['witness']['kind'] != 'none' and all(passes[t]['branch'][0] for t in tiers): + holds, ev = witness(row_branch, p, r['witness'], scratch) + if holds: + witnessed += 1 + out(' WITNESS %s %s holds [%s]' % (r['id'], r['witness']['kind'], ev)) + else: + for t in tiers: + cnt[t]['vacuous'] += 1 + out(' VACUOUS %s %s [%s]' % (r['id'], r['witness']['kind'], ev)) + for b in bad: + out(' POPULATION: ' + b) + for t in TIERS: + c = cnt[t] + out('tier=%s examined=%d rows tag_fail=%d branch_pass=%d vacuous=%d tag_timeout=%d tag_signal=%d' + % (t, c['examined'], c['tag_fail'], c['branch_pass'], c['vacuous'], c['tag_timeout'], + c['tag_signal'])) + out('controls=%d both_pass=%d witnessed=%d mapped=%d gates=%d through=%s' % ( + ctl_n, ctl_both, witnessed, len(landed), len(gates), through)) + out('controls_witnessed=%d controls_vacuous=%d' % (ctl_witnessed, ctl_vacuous)) + out('invariants=%d branch_pass=%d' % (invariant_n, invariant_pass)) + rows_ok = all(c['examined'] == c['tag_fail'] == c['branch_pass'] > 0 and c['vacuous'] == 0 + for c in cnt.values()) + return (not bad, rows_ok, ctl_n == ctl_both and ctl_vacuous == 0 and invariant_n == invariant_pass, len(landed), cnt, witnessed) + + +# ---- mechanical gates ---------------------------------------------------------- +def code_text(path): + return inv.strip_c(read_text(path, errors='replace')) + + +def c_files(root, dirs=('src',), recurse_tests=True): + out = [] + for d in dirs: + out += [os.path.join(root, d, f) for f in sorted(os.listdir(os.path.join(root, d))) + if f.endswith(('.c', '.h'))] + if recurse_tests: + for base, _, fs in os.walk(os.path.join(root, 'tests')): + out += [os.path.join(base, f) for f in sorted(fs) if f.endswith(('.c', '.h'))] + return out + + +def token_gate(root, through, out): + toks = [t for r, ts in DELETED_TOKENS.items() if rindex(r) <= rindex(through) for t in ts] + files = c_files(root) + hits = [] + for p in files: + text = code_text(p) + for t in toks: + for m in re.finditer(r'\b%s\b' % re.escape(t), text): + hits.append('%s:%d %s' % (os.path.relpath(p, root), text.count('\n', 0, m.start()) + 1, t)) + for h in hits: + out(' TOKEN %s (deleted by a landed round, still in code)' % h) + out('tokens: rounds<=%s tokens=%d files=%d hits=%d' % (through, len(toks), len(files), len(hits))) + return bool(files) and bool(toks) and not hits + + +def int_source(src, out, through='R1.1'): + """The static half of "no int before R1.3": no runtime call of + slot_from_int / make_int outside the round's allowed writers; make_int is + not defined; slot_from_int is defined exactly once; and nothing outside the + tag's own definitions names TAG_SMI or spells its bits. The dynamic half + is the smi-trap build (value_slot.h EIGS_SMI_TRAP).""" + allowed = INT_SOURCE_ALLOWED[through] + files = inv.source_files(src) # the tracked sources (a generated header is not one) + defs = {'slot_from_int': 0, 'make_int': 0} + calls, bad, tags, homes = [], [], [], set() + for p in files: + rel = os.path.basename(p) + text = code_text(p) + scope = inv.scopes_for(text, rel) + for ln, line in enumerate(text.split('\n'), 1): + for m in re.finditer(r'\b(slot_from_int|make_int)\s*\(', line): + fn = scope[ln].split('/')[0] + name = m.group(1) + if fn == '' or fn == name: + defs[name] += 1 # a definition or a prototype + continue + calls.append((rel, ln, fn, name)) + if fn not in allowed: + bad.append('%s:%d: %s calls %s' % (rel, ln, fn, name)) + for m in SMI_BITS_RE.finditer(line): + fn = scope[ln].split('/')[0] + if (rel, fn) in SMI_TAG_HOMES: + homes.add((rel, fn)) + continue + tags.append('%s:%d: %s spells the SMI tag (%s)' % (rel, ln, fn, m.group(0))) + for b in bad: + out(' INT-SOURCE %s: no int may be produced before R1.3' % b) + for t in tags: + out(' INT-SOURCE %s outside its definitions: a producer that writes the bits itself' % t) + if defs['make_int']: + out(' INT-SOURCE make_int is defined before R1.3') + if defs['slot_from_int'] != 1: + out(' INT-SOURCE slot_from_int is defined %d times, want 1 (did the scan find it?)' + % defs['slot_from_int']) + if homes != SMI_TAG_HOMES: + out(' INT-SOURCE the tag definitions found %d of %d homes (%s missing): the scan is blind' + % (len(homes), len(SMI_TAG_HOMES), sorted(SMI_TAG_HOMES - homes))) + out('int-source: through=%s files=%d slot_from_int_defs=%d make_int_defs=%d calls=%d ' + 'outside_writers=%d tag_homes=%d/%d tag_spellings_outside=%d' % ( + through, len(files), defs['slot_from_int'], defs['make_int'], len(calls), len(bad), + len(homes), len(SMI_TAG_HOMES), len(tags))) + return (bool(files) and not bad and not tags and not defs['make_int'] + and defs['slot_from_int'] == 1 and homes == SMI_TAG_HOMES) + + +def tool_output(cmd, p, out, tail_re, allow_test_diagnostics=False): + """Judge the real subprocess result, shared with the one-time calibration. + + Gate checkers must keep stderr empty. A behavioral host can deliberately + exercise errors, so its caller may allow those diagnostics, while still + rejecting every sanitizer report (including recoverable UBSan at rc=0). + """ + text = p.stdout.decode('utf-8', 'replace') + err = p.stderr.decode('utf-8', 'replace') + for l in text.split('\n'): + if re.match(tail_re, l): + out(' ' + l) + who = next((os.path.basename(a) for a in cmd if a.endswith(('.py', '.sh'))), cmd[0]) + for l in err.split('\n'): + if l: + out(' STDERR %s: %s' % (who, l)) + # This policy rejects *all* sanitizer output, rather than tolerating an + # LSan-only exit. Keep it strict like tests/test_lsp.py; no separate copy + # of tests/lsan_classify.sh's tolerance decision is needed. + sanitizer = allow_test_diagnostics and any( + marker in text + err for marker in ('Sanitizer', 'runtime error:')) + ok = p.returncode == 0 and (not err or allow_test_diagnostics) and not sanitizer + if p.returncode != 0 or err or sanitizer or allow_test_diagnostics: + out(' tool %s: rc=%d stderr_bytes=%d stderr_policy=%s verdict=%s' % ( + who, p.returncode, len(p.stderr), + 'test-diagnostics' if allow_test_diagnostics else 'empty', + 'PASS' if ok else 'FAIL')) + if sanitizer: + out(' tool %s: FAIL: sanitizer output rejected at rc=%d' % (who, p.returncode)) + elif err and not allow_test_diagnostics: + out(' tool %s: FAIL: unexpected stderr (this checker requires empty stderr)' % who) + return ok, text + + +def tool(cmd, out, tail_re, env=None, allow_test_diagnostics=False): + """Run a tool and apply its explicit stderr policy to the captured result.""" + p = subprocess.run(cmd, cwd=ROOT, stdin=subprocess.DEVNULL, capture_output=True, + env=dict(os.environ, **(env or {}))) + return tool_output(cmd, p, out, tail_re, allow_test_diagnostics) + + +def embed_int_result(ok, text, out): + """Require one positive, failure-free counter and the host's final OK.""" + counters = re.findall(r'^gate\.N7\.embed-int:.*$', text, re.M) + valid = re.findall(r'^gate\.N7\.embed-int: checks=([1-9][0-9]*) failures=0$', text, re.M) + smoke = re.findall(r'^embed_smoke:.*$', text, re.M) + final = re.findall(r'^embed_smoke: OK$', text, re.M) + out(' embed-int witnesses: counter_lines=%d valid_counters=%d checks=%s ' + 'smoke_lines=%d smoke_ok=%d' % ( + len(counters), len(valid), ','.join(valid) or '', len(smoke), len(final))) + if len(counters) != 1 or len(valid) != 1: + out(' embed-int: FAIL: expected exactly one checks>0 failures=0 counter; got %r' % counters) + if len(smoke) != 1 or len(final) != 1: + out(' embed-int: FAIL: expected exactly one embed_smoke: OK; got %r' % smoke) + return ok and len(counters) == len(valid) == len(smoke) == len(final) == 1 + + +def embed_int_gate(out): + # embed_smoke exercises wrong types, failed evals and replay refusals on + # purpose. Their printed errors are expected; its rc and witnesses own + # the verdict, with sanitizer output still an unconditional failure. + ok, text = tool(['make', '--no-print-directory', 'embed-smoke'], out, + r'^(gate\.N7\.embed-int:|embed_smoke:)', allow_test_diagnostics=True) + return embed_int_result(ok, text, out) + + +def tool_contract_calibration(out): + """Small result plants, through the same evaluator used by live tools.""" + good = 'gate.N7.embed-int: checks=219 failures=0\nembed_smoke: OK\n' + wrong_type = 'Error line 0: eigs_value_as_int: expected a number\n' + cases = [ + ('checker clean control', 0, good, '', False, False, True, None), + ('checker stderr remains forbidden', 0, good, wrong_type, False, False, False, + 'unexpected stderr'), + ('embed expected diagnostics control', 0, good, wrong_type, True, True, True, + 'stderr_policy=test-diagnostics'), + ('embed nonzero rc despite witnesses', 7, good, '', True, True, False, 'rc=7'), + ('embed empty stdout', 0, '', '', True, True, False, 'counter_lines=0'), + ('embed zero checks', 0, good.replace('checks=219', 'checks=0'), '', True, True, False, + 'expected exactly one checks>0 failures=0 counter'), + ('embed failed assertion counter', 0, good.replace('failures=0', 'failures=1'), '', + True, True, False, 'failures=1'), + ('embed absent final OK', 0, good.split('embed_smoke:')[0], '', True, True, False, + 'smoke_ok=0'), + ('embed duplicate counter', 0, good + good.splitlines()[0] + '\n', '', True, True, False, + 'counter_lines=2'), + ('embed duplicate final OK', 0, good + 'embed_smoke: OK\n', '', True, True, False, + 'smoke_ok=2'), + ('embed malformed extra counter', 0, good + 'gate.N7.embed-int: truncated\n', '', + True, True, False, 'counter_lines=2'), + ('embed UBSan at rc zero', 0, good, 'fixture.c:1: runtime error: signed integer overflow\n', + True, True, False, 'sanitizer output rejected at rc=0'), + ('embed ASan at rc zero', 0, good, 'ERROR: AddressSanitizer: heap-use-after-free\n', + True, True, False, 'sanitizer output rejected at rc=0'), + ('embed LSan at rc zero', 0, good, 'ERROR: LeakSanitizer: detected memory leaks\n', + True, True, False, 'sanitizer output rejected at rc=0'), + ('embed sanitizer on stdout', 0, good + 'SUMMARY: ThreadSanitizer: data race\n', '', + True, True, False, 'sanitizer output rejected at rc=0'), + ] + passed = 0 + for name, rc, stdout, stderr, allow, embed, want, diagnostic in cases: + lines = [] + p = subprocess.CompletedProcess(['fixture'], rc, stdout.encode(), stderr.encode()) + ok, text = tool_output(['fixture'], p, lines.append, r'.*', allow) + if embed: + ok = embed_int_result(ok, text, lines.append) + expected = ok == want and (diagnostic is None or any(diagnostic in l for l in lines)) + passed += expected + out(' tool-contract %s: %s' % (name, 'PASS' if expected else 'FAIL')) + if not expected: + for line in lines: + out(' | ' + line) + out('tool-contract-calibration: checks=%d passed=%d failures=%d' % ( + len(cases), passed, len(cases) - passed)) + return passed == len(cases) > 0 + + +INVENTORY_TAIL = r'^(FAIL: |UNCLASSIFIED: |slot classifiers: |examined=|phase2-inventory: )' + + +def inventory_gate(out, extra=()): + """The inventory's own verdict, under -W error, with --check: every slot + classifier has a reviewed disposition and the committed inventory file is + exactly the generated one. PASS only for rc 0, no stderr, and a last line + `phase2-inventory: PASS`.""" + ok, text = tool([sys.executable, '-W', 'error', os.path.join(ROOT, 'tools/phase2_inventory.py'), '--check'] + + list(extra), out, INVENTORY_TAIL) + lines = [l for l in text.split('\n') if l] + return ok and lines[-1:] == ['phase2-inventory: PASS'] + + +DIFF_TAIL = (r'^(corpus-diff: (seed pin|baseline|branch|files|examined|RESTRICTED|PASS|FAIL)|DIFF|DELETED|NEW|NONDET|' + r'REPLAYED|BOTH-|SAME-NONZERO|UNPINNED|INCOMPLETE|UNDECLARED|STALE|LEDGER|CAPPED)') + + +def every_mode(through, out): + res = {'tokens': token_gate(ROOT, through, out), 'inventory': inventory_gate(out)} + for g in EVERY_MODE: + out('gate %s: %s' % (g, 'PASS' if res[g] else 'FAIL')) + return all(res[g] for g in EVERY_MODE) + + +def mechanical(through, baseline, branch, out, smi_trap=None, tag=None): + res = {} + gates = list(MECHANICAL.get(through, ())) + # A landed gate stays live for every subsequent --through, including + # rounds with ordinary MAP rows and no other mechanical dispatch. + if ROUNDS.index(through) >= ROUNDS.index('R1.6e'): + gates.extend(('q7-readers', 'gfx-audio-boundary', 'gfx-byte-boundary')) + if ROUNDS.index(through) >= ROUNDS.index('R1.6f'): + # R1.6f-D2: net_send's raw stored-byte fixture builds once here, + # outside the MAP rows' per-tier 30 s timeout. + gates.append('net-byte-boundary') + if ROUNDS.index(through) >= ROUNDS.index('R1.6z'): + gates.append('embed-buffer-get-raw') + if ROUNDS.index(through) >= ROUNDS.index('R1.7'): + gates.extend(('const-dedup-bits', 'db-param-exact')) + if ROUNDS.index(through) > ROUNDS.index('R1.8'): + gates.append('embed-int') + for g in gates: + if g == 'int-source': + res[g] = int_source(os.path.join(ROOT, 'src'), out, through) + elif g == 'slot-test': + res[g], _ = tool(['bash', 'tests/phase2/slot_layer.sh', 'release'], out, + r'^(slot-layer|slot-layer-population|slot-layer-test: (examined|FAIL))') + elif g == 'corpus-diff': + if not baseline: + out(' corpus-diff: a mechanical round needs --baseline ') + res[g] = False + else: + res[g], _ = tool(['bash', 'tools/phase2_corpus_diff.sh', baseline, branch], out, DIFF_TAIL) + elif g == 'smi-trap': + # the dynamic half of the int-source check: the corpus on a build + # where every slot classifier aborts on an SMI (make smi-trap) + if not baseline or not smi_trap: + out(' smi-trap: %s needs --baseline and --smi-trap ' % through) + res[g] = False + else: + res[g], _ = tool(['bash', 'tools/phase2_corpus_diff.sh', baseline, smi_trap], out, DIFF_TAIL) + elif g == 'reader-test': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release'], out, + r'^phase2-readers: ') + res[g] = res[g] and len(re.findall(r'^phase2-readers: PASS variant=release checks=[1-9][0-9]*$', + text, re.M)) == 1 + elif g == 'q7-readers': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'q7_readers'], out, r'.*') + res[g] = res[g] and len(re.findall( + r'^phase2-q7-readers: PASS variant=release$', text, re.M)) == 1 + elif g == 'gfx-audio-boundary': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'gfx_audio_boundary'], out, r'.*') + res[g] = res[g] and len(re.findall( + r'^phase2-gfx-audio: PASS variant=release$', text, re.M)) == 1 + elif g == 'gfx-byte-boundary': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'gfx_byte_boundary'], out, r'.*') + res[g] = res[g] and len(re.findall( + r'^phase2-gfx-byte: PASS variant=release$', text, re.M)) == 1 + elif g == 'net-byte-boundary': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'net_byte_boundary'], out, r'.*') + res[g] = res[g] and len(re.findall( + r'^phase2-net-boundary: PASS variant=release$', text, re.M)) == 1 + elif g == 'embed-buffer-get-raw': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'embed_buffer_get_raw'], out, r'.*') + res[g] = res[g] and len(re.findall( + r'^phase2-embed-buffer-get: PASS variant=release$', text, re.M)) == 1 + elif g == 'const-dedup-bits': + res[g], text = tool(['bash', 'tools/phase2_readers.sh', 'release', 'const_dedup_bits'], out, r'.*') + res[g] = res[g] and len(re.findall(r'^phase2-const-dedup: PASS variant=release$', text, re.M)) == 1 + elif g == 'embed-int': + res[g] = embed_int_gate(out) + elif g == 'db-param-exact': + res[g], text = tool(['bash', '-c', 'make db-params-test && build/server-db/test_db_params'], out, r'.*') + res[g] = res[g] and len(re.findall(r'^db params: 14 passed, 0 failed \(14 declared\)$', text, re.M)) == 1 + elif g == 'builtins-table': + if not tag: + out(' builtins-table: needs the tag binary') + res[g] = False + else: + res[g], text = tool(['bash', 'tools/phase2_builtins.sh', tag, branch], out, + r'^phase2-builtins: ') + res[g] = res[g] and len(re.findall( + r'^phase2-builtins: PASS rows=[1-9][0-9]* landed=\S+ migrated=[0-9]+ unchanged=[0-9]+$', + text, re.M)) == 1 + elif g == 'bridge-deletion-compile': + # token_gate checks the deleted vocabulary in both src and tests; + # a normal build proves no remaining caller depends on an alias. + res[g], _ = tool(['make', '--no-print-directory', 'build'], out, r'.*') + out('mechanical %s: %s' % (g, 'PASS' if res[g] else 'FAIL')) + return all(res.values()) and bool(res) + + +# ---- the plan-level checks ------------------------------------------------------- +def plan_checks(map_rows, bad, out): + for item in ITEMS: + if not any(r['item'] == item for r in map_rows if r['kind'] != 'control'): + bad.append('item %s has no row in the MAP' % item) + arms, lines, inside = {}, read_text(os.path.join(ROOT, 'docs/design/phase2-inventory.txt')).split('\n'), False + for l in lines: + if l.startswith('## Numeric arms'): + inside = True + continue + if inside and (l.startswith('## ') or not l.strip()): + break + if inside: + w = l.split() + arms['%s %s' % (w[0], w[1])] = set(w[2:]) + # a leaf arm is exercised by a leaf (L) row, an emitter arm by an F or O row + shaped = [r for r in map_rows if r['shape'] in ('L', 'F', 'O')] + ops = {'leaf_exec': {('OP_' + o) for r in shaped if r['shape'] == 'L' for o in r['ops']}, + 'jit_emit': {('OP_' + o) for r in shaped if r['shape'] != 'L' for o in r['ops']}} + covered = [a for a, chain in arms.items() if chain & ops.get(a.split()[0], set())] + for a in sorted(set(arms) - set(covered)): + bad.append('numeric arm %s is exercised by no L/F/O row ops' % a) + rounds = {r['round'] for r in map_rows if r['round']} + for rd in ROUNDS: + if rd not in rounds and rd not in MECHANICAL: + bad.append('round %s has neither rows nor mechanical gates' % rd) + out('rounds: ' + ' '.join('%s=%d%s' % (rd, sum(r['round'] == rd for r in map_rows if r['kind'] == 'row'), + '+mechanical' if rd in MECHANICAL else + '+gates' if any(r['round'] == rd for r in map_rows + if r['kind'] == 'gate') else '') + for rd in ROUNDS)) + out('plan: items=%d rows=%d shaped=%d arms=%d covered=%d' % ( + len(ITEMS), sum(r['kind'] == 'row' for r in map_rows), len(shaped), len(arms), len(covered))) + if not arms: + bad.append('the inventory lists no numeric arm (scan found nothing)') + return len(arms) + + +def banner(tag, branch, out): + for who, b in (('tag', tag), ('branch', branch)): + v = subprocess.run([b, '--version'], capture_output=True).stdout.decode().strip() + h = subprocess.run(['sha256sum', b], capture_output=True).stdout.decode().split()[:1] + out('%s: %s (%s) sha256 %s' % (who, b, v, (h or ['?'])[0][:16])) + + +def main_check(args, out): + banner(args.tag, args.branch, out) + map_rows, bad = parse_map(args.map) + if not map_rows: + bad.append('the MAP lists no row') + plan_checks(map_rows, bad, out) + for b in bad: + out(' PLAN: ' + b) + with tempfile.TemporaryDirectory(prefix='eigs-p2-') as scratch: + pop_ok, rows_ok, ctl_ok, landed, cnt, _ = check( + map_rows, args.rows, args.controls, args.tag, args.branch, args.through, scratch, out) + always_ok = every_mode(args.through, out) + mech_ok = None + if args.through in MECHANICAL or ROUNDS.index(args.through) >= ROUNDS.index('R1.6e'): + mech_ok = mechanical(args.through, args.baseline, args.branch, out, args.smi_trap, args.tag) + if landed == 0: + if args.through not in MECHANICAL: + out(' ZERO-ROWS: %s has no landed row and no round-specific mechanical gates' % args.through) + verdict = args.through in MECHANICAL and bool(mech_ok) and pop_ok and not bad and ctl_ok and always_ok + else: + verdict = rows_ok and pop_ok and not bad and ctl_ok and mech_ok is not False and always_ok + out('phase2-check: %s' % ('PASS' if verdict else 'FAIL')) + return 0 if verdict else 1 + + +# ---- calibration (design 9.2, one-time) ------------------------------------------- +def calibrate(args, out): + cal = os.path.join(ROOT, 'tests/phase2/calib') + map_rows, bad = parse_map(os.path.join(cal, 'MAP.md')) + results = [] + # Keep early-round calibration meaningful after the first int producers + # exist. Export only tracked src bytes from the pinned R1.2 checkpoint. + historical = tempfile.mkdtemp(prefix='eigs-p2-int-source-r12-') + _PLANT_DIR.append(historical) + try: + archive = subprocess.run(['git', '-C', ROOT, 'archive', STATIC_INT_SOURCE_REF, 'src'], + capture_output=True, check=True) + subprocess.run(['tar', '-x', '-C', historical], input=archive.stdout, check=True) + except subprocess.CalledProcessError as error: + out('phase2-check: FAIL: historical R1.2 int-source export failed: %s' % error) + return 2 + historical_src = os.path.join(historical, 'src') + out('calibration: historical-int-source=%s (static only; dynamic no-SMI mode retired at R1.3)' % + STATIC_INT_SOURCE_REF) + + def case(name, got, want, detail=None): + """One calibration case. An UNEXPECTED case prints the gate's own + output (`detail`, its last 40 lines with the elision counted), so a + failure on another machine says why (the R1.1 CI lane printed only + `got False`).""" + ok = got == want + results.append(ok) + out('CAL %-58s want %-28s got %-28s %s' % (name, want, got, 'ok' if ok else 'UNEXPECTED')) + if not ok: + shown = [l for l in (detail or []) if l.strip()] + if len(shown) > 40: + out(' | ... %d earlier line(s) of the gate\'s output' % (len(shown) - 40)) + for l in shown[-40:]: + out(' | ' + l) + if not shown: + out(' | (this case runs no gate with output of its own)') + + def quiet(_): + pass + + lines = [] + case('tool stderr policy, process rc and embed counter/OK plants', + tool_contract_calibration(lines.append), True, detail=lines) + + def sub(rows, ids, mutate=None, tag=None, branch=None, log=quiet): + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + rd = os.path.join(d, 'rows') + os.mkdir(rd) + for r in rows: + if r['id'] in ids: + for ext in ('.eigs', '.out'): + shutil.copy(os.path.join(cal, r['id'] + ext), rd) + if mutate: + mutate(rd) + sel = [r for r in rows if r['id'] in ids] + lines = [] + res = check(sel, rd, os.path.join(d, 'none'), tag or args.tag, branch or args.branch, + 'R1.3', os.path.join(d), lambda s: (lines.append(s), log(s))) + return res, lines + + honest = [r['id'] for r in map_rows if not r['id'].endswith('-plant')] + out('calibration fixtures: %d rows (%d honest) in %s' % (len(map_rows), len(honest), cal)) + case('fixture MAP parses', not bad and len(map_rows) > 0, True, detail=bad) + (pop, rows_ok, _, n, cnt, wit), lines = sub(map_rows, honest, log=out) + case('honest rows: every tier tag_fail == branch_pass == examined', rows_ok, True, detail=lines) + case('honest rows: every witness holds', wit, sum(r['witness']['kind'] != 'none' + for r in map_rows if r['id'] in honest), detail=lines) + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, honest, tag=args.branch) + case('the branch passed as the tag too: branch_pass=0?', cnt['default']['tag_fail'], 0, detail=lines) + case('... and the check fails', rows_ok, False, detail=lines) + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, honest, branch=args.tag) + case('the tag passed as the branch: branch_pass', cnt['default']['branch_pass'], 0, detail=lines) + victim = honest[0] + + def corrupt(rd): + with open(os.path.join(rd, victim + '.out'), 'a') as f: + f.write('corrupted\n') + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, honest, mutate=corrupt) + case('a corrupted .out fails, naming the row', (rows_ok, any( + ('BRANCH-FAIL %s ' % victim) in l for l in lines)), (False, True), detail=lines) + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, []) + case('an empty rows directory: examined=0 fails', (rows_ok, cnt['default']['examined']), (False, 0), detail=lines) + + def drop(rd): + os.remove(os.path.join(rd, victim + '.eigs')) + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, honest, mutate=drop) + case('a MAP id with no file fails', (pop, any('has no file' in l for l in lines)), (False, True), detail=lines) + + def stray(rd): + shutil.copy(os.path.join(rd, victim + '.eigs'), os.path.join(rd, 'stray.row.eigs')) + (pop, rows_ok, _, n, cnt, _), lines = sub(map_rows, honest, mutate=stray) + case('a file with no MAP id fails', (pop, any('stray.row.eigs has no MAP id' in l for l in lines)), + (False, True), detail=lines) + for r in map_rows: + if r['id'].endswith('-plant'): + (pop, rows_ok, _, n, cnt, wit), lines = sub(map_rows, [r['id']]) + case('plant %s: vacuous, named' % r['id'], (cnt['default']['vacuous'], any( + ('VACUOUS %s ' % r['id']) in l for l in lines)), (1, True), detail=lines) + # the plan-level checks against a mutated copy of the real MAP + real, rbad = parse_map(os.path.join(ROOT, 'tests/phase2/MAP.md')) + lines = [] + plan_checks(real, list(rbad), lines.append) + pb = [] + plan_checks(real, pb, quiet) + case('plan checks on the real MAP pass', (rbad, pb), ([], []), detail=pb) + pb = [] + plan_checks([r for r in real if r['item'] != 'N4b'], pb, quiet) + case('a MAP with no N4b row fails, naming N4b', any('item N4b' in b for b in pb), True, detail=pb) + pb = [] + n_arms = plan_checks([dict(r, ops=[]) for r in real], pb, quiet) + case('a MAP whose rows exercise no arm fails per arm (%d arms)' % n_arms, + sum('numeric arm' in b for b in pb) == n_arms > 0, True, detail=pb) + pb = [] + plan_checks([r for r in real if r['round'] != 'R2.3'], pb, quiet) + case('a round with neither rows nor gates fails', any('round R2.3' in b for b in pb), True, detail=pb) + # the int-source check against a planted production call + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + shutil.copytree(historical_src, os.path.join(d, 'src'), ignore=c_sources_only) + p = os.path.join(d, 'src', 'trace.c') + text = read_text(p) + anchor = ' *out = slot_from_f64((double)count);' + n_anchor = text.count(anchor) + write_text(p, text.replace(anchor, ' *out = slot_from_int(count);')) + lines = [] + ok = int_source(os.path.join(d, 'src'), lines.append) + case('int-source plant (slot_from_int(count) in trace.c) applied once', n_anchor, 1) + case('int-source plant: the check fails, naming the caller', (ok, any( + 'trace.c' in l and 'calls slot_from_int' in l for l in lines)), (False, True), detail=lines) + lines = [] + # at R1.2: from R1.2 the canonicalizing writers may convert a VAL_INT + # (design 8), and the branch is past R1.1 + case('int-source on pinned R1.2 source passes', + int_source(historical_src, lines.append, 'R1.2'), True, detail=lines) + # the token gate against a reintroduced deleted token + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + os.mkdir(os.path.join(d, 'src')) + os.mkdir(os.path.join(d, 'tests')) + for f in os.listdir(os.path.join(ROOT, 'src')): + if f.endswith(('.c', '.h')): + shutil.copy(os.path.join(ROOT, 'src', f), os.path.join(d, 'src')) + with open(os.path.join(d, 'src', 'vm.c'), 'a') as f: + f.write('\nstatic int stray_reader(EigsSlot s) { return slot_is_num(s); }\n') + lines = [] + case('token plant (slot_is_num reintroduced): the gate fails', + (token_gate(d, 'R1.1', lines.append), any('slot_is_num' in l for l in lines)), (False, True), detail=lines) + # the int-source plants of the R1.1 critic, static half on the historical source + for name, edits in INTSRC_PLANTS: + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + shutil.copytree(historical_src, os.path.join(d, 'src'), ignore=c_sources_only) + counts = [] + for f, old, new in edits: + p = os.path.join(d, 'src', f) + text = read_text(p) + counts.append(text.count(old)) + write_text(p, text.replace(old, new)) + if counts != [1] * len(edits): + case('%s: every edit applied once' % name, counts, [1] * len(edits)) + continue + for through in ('R1.1', 'R1.2'): + lines = [] + ok = int_source(os.path.join(d, 'src'), lines.append, through) + want = not (name.startswith('P4') and through == 'R1.2') # R1.2 allows that writer + case('%s, static half at %s: %s' % (name, through, 'fails, naming it' if want + else 'passes (the dynamic half owns it)'), + (not ok, any('INT-SOURCE' in l for l in lines)) if want else ok, + (True, True) if want else True, detail=lines) + # the corpus differential's plants: A and B are wrapper branches, D a build + base = args.branch + files = ['tests/test_dispatch.eigs', 'tests/test_bool.eigs'] + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + a = wrapper_tree(os.path.join(d, 'A'), args.branch, files, + 'case "$*" in *test_dispatch.eigs*) "$REAL" "$@"; rc=$?; echo "PLANTED A"; exit $rc ;; esac\n' + 'exec "$REAL" "$@"') + rc, text, v = diff_restricted(base, a, files) + case('differential plant A (a deterministic file prints one more line): DIFF, control same', + (rc, v.get('tests/test_dispatch.eigs'), v.get('tests/test_bool.eigs'), v.get('')), + (3, 'DIFF', 'same', None), detail=text.splitlines()) + for phase in (0, 1): + bdir = os.path.join(d, 'B%d' % phase) + bw = wrapper_tree(bdir, args.branch, files, + 'case "$*" in *test_dispatch.eigs*) if [ $(((N + %d) %% 2)) -eq 0 ]; then\n' + ' "$REAL" "$@"; rc=$?; echo "PLANTED B"; exit $rc; fi ;; esac\nexec "$REAL" "$@"' % phase) + rc, text, v = diff_restricted(base, bw, files) + case('differential plant B, phase %d (wrong on alternate runs): DIFF, never NONDET' % phase, + (rc, v.get('tests/test_dispatch.eigs'), v.get('tests/test_bool.eigs'), v.get('')), + (3, 'DIFF', 'same', None), detail=text.splitlines()) + # the run conditions and the nonzero ledger (the second R1.1 critic): + # fake trees that run the real branch binary with one input changed + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + files = ['tests/test_numeric_guard.eigs', 'tests/test_buffer_nonfinite_read.eigs', 'tests/test_bool.eigs'] + rc, text, v = diff_restricted(base, args.branch, files) + case('unplanted branch, conditioned files included: all same, nothing undeclared', + (rc,) + tuple(v.get(f) for f in files) + (v.get(''), + bool(re.search(r' incomplete=0 undeclared=0 stale=0 ledger_refused=0 ', text))), + (3, 'same', 'same', 'same', None, True), detail=text.splitlines()) + changed = wrapper_tree(os.path.join(d, 'modified'), args.branch, files[2:], 'exec "$REAL" "$@"', + {'tests/test_bool.eigs': lambda t: t + '\n# modified-test fixture\n'}) + rc, text, v = diff_restricted(base, changed, files[2:]) + case('a changed program the baseline completes is compared and listed MODIFIED', + (rc, v.get('tests/test_bool.eigs'), + 'MODIFIED (tree copies differ) tests/test_bool.eigs' in text, + ' modified=1 modified_failed=0 branch_only=0 ' in text), + (3, 'same', True, True), detail=text.splitlines()) + failing_base = wrapper_tree(os.path.join(d, 'failing-base'), args.branch, files[2:], + 'case "$*" in *test_bool.eigs*|*test_phase2_new_fixture.eigs*) exit 19 ;; esac\n' + 'exec "$REAL" "$@"') + changed_bad = wrapper_tree(os.path.join(d, 'modified-bad'), args.branch, files[2:], + 'case "$*" in *test_bool.eigs*) exit 20 ;; esac\nexec "$REAL" "$@"', + {'tests/test_bool.eigs': lambda t: t + '\n# modified failing fixture\n'}) + rc, text, v = diff_restricted(failing_base, changed_bad, files[2:]) + case('a modified program the baseline cannot complete and branch fails is MODIFIED-FAIL', + (rc, v.get('tests/test_bool.eigs'), ' modified=1 modified_failed=1 branch_only=1 ' in text), + (3, 'MODIFIED-FAIL', True), detail=text.splitlines()) + changed_good = wrapper_tree(os.path.join(d, 'modified-good'), args.branch, files[2:], 'exec "$REAL" "$@"', + {'tests/test_bool.eigs': lambda t: t + '\n# modified passing fixture\n'}) + rc, text, v = diff_restricted(failing_base, changed_good, files[2:]) + case('a modified program the baseline cannot complete and branch passes is DIFF-BEHAVIOUR', + (rc, v.get('tests/test_bool.eigs'), + 'DIFF-BEHAVIOUR tests/test_bool.eigs (changed; the baseline cannot complete it: rc 19/19)' in text, + ' diff=1 ' in text, ' modified=1 modified_failed=0 branch_only=1 ' in text, + '[ "$ndiff" -eq 0 ]' in read_text(os.path.join(ROOT, 'tools/phase2_corpus_diff.sh'))), + (3, 'DIFF', True, True, True, True), detail=text.splitlines()) + changed_good_conditioned = wrapper_tree( + os.path.join(d, 'modified-good-conditioned'), args.branch, files[2:], 'exec "$REAL" "$@"', + {'tests/test_bool.eigs': lambda t: t + '\n# modified conditioned passing fixture\n', + 'tests/suite_program_env.txt': lambda t: t + 'test_bool.eigs EIGS_STRICT=0\n'}) + rc, text, v = diff_restricted(failing_base, changed_good_conditioned, files[2:]) + case('a conditioned modified program the baseline cannot complete and branch passes is DIFF-BEHAVIOUR', + (rc, v.get('tests/test_bool.eigs'), + 'DIFF-BEHAVIOUR tests/test_bool.eigs (changed; the baseline cannot complete it: rc 19/19)' in text, + ' diff=1 ' in text, ' incomplete=0 undeclared=0 ' in text, + ' modified=1 modified_failed=0 branch_only=1 ' in text), + (3, 'DIFF', True, True, True, True), detail=text.splitlines()) + new_rel = 'tests/test_phase2_new_fixture.eigs' + new_branch = wrapper_tree(os.path.join(d, 'new-bad'), args.branch, files[2:], + 'case "$*" in *test_phase2_new_fixture.eigs*) exit 20 ;; esac\nexec "$REAL" "$@"') + os.rename(os.path.join(os.path.dirname(os.path.dirname(new_branch)), 'tests/test_bool.eigs'), + os.path.join(os.path.dirname(os.path.dirname(new_branch)), new_rel)) + rc, text, v = diff_restricted(failing_base, new_branch, [new_rel]) + case('a new program the baseline cannot complete and branch fails is NEW-FAIL', + (rc, v.get(new_rel), ' new=1 new_failed=1 ' in text, ' branch_only=1 ' in text), + (3, 'NEW-FAIL', True, True), detail=text.splitlines()) + cut = wrapper_tree(os.path.join(d, 'cut'), args.branch, files[:1] + files[2:], 'exec "$REAL" "$@"', + {'tests/suite_program_env.txt': + lambda t: re.sub(r'(?m)^test_numeric_guard\.eigs .*\n', '', t)}) + rc, text, v = diff_restricted(base, cut, files[:1] + files[2:]) + case('a policy row removed: its file exits early on both, UNDECLARED-NONZERO', + (rc, 'UNDECLARED-NONZERO tests/test_numeric_guard.eigs rc=1' in text, + ' undeclared=1 ' in text, v.get('')), (3, True, True, None), detail=text.splitlines()) + stale = wrapper_tree(os.path.join(d, 'stale'), args.branch, files[2:], 'exec "$REAL" "$@"', + {'tests/phase2/corpus_nonzero.txt': + lambda t: t + 'tests/test_bool.eigs 1 planted: test_bool completes\n'}) + rc, text, v = diff_restricted(base, stale, files[2:]) + case('a ledger row for a file that completes: STALE', + (rc, 'STALE tests/test_bool.eigs:' in text, ' stale=1 ' in text), (3, True, True), detail=text.splitlines()) + inc = wrapper_tree(os.path.join(d, 'inc'), args.branch, ['tests/test_assert_fail.eigs'], 'exec "$REAL" "$@"', + {'tests/suite_program_env.txt': lambda t: t + 'test_assert_fail.eigs EIGS_STRICT=0\n', + 'tests/phase2/corpus_nonzero.txt': + lambda t: re.sub(r'(?m)^tests/test_assert_fail\.eigs .*\n', '', t)}) + rc, text, v = diff_restricted(base, inc, ['tests/test_assert_fail.eigs']) + case('a conditioned file that does not complete: INCOMPLETE, never compared', + (rc, v.get('tests/test_assert_fail.eigs'), ' incomplete=1 ' in text), (3, 'INCOMPLETE', True), detail=text.splitlines()) + # the inventory gate (#1804): a classifier with no reviewed disposition, + # and a committed inventory that is not the generated one + with tempfile.TemporaryDirectory(prefix='eigs-p2cal-') as d: + shutil.copytree(os.path.join(ROOT, 'src'), os.path.join(d, 'src'), ignore=c_sources_only) + os.makedirs(os.path.join(d, 'docs/design')) + for f in ('docs/design/phase2-numbers.md', 'docs/design/phase2-inventory.txt'): + shutil.copy(os.path.join(ROOT, f), os.path.join(d, f)) + inv_args = ['--src', os.path.join(d, 'src'), '--doc', os.path.join(d, 'docs/design/phase2-numbers.md'), + '--out', os.path.join(d, 'docs/design/phase2-inventory.txt')] + lines = [] + case('inventory gate on an unchanged copy passes', inventory_gate(lines.append, inv_args), True, detail=lines) + inv_path = os.path.join(d, 'docs/design/phase2-inventory.txt') + committed = read_text(inv_path) + write_text(inv_path, committed.replace('dispositions=', 'dispositions=0', 1)) + lines = [] + case('a committed inventory that is not the generated one: the gate fails, saying stale', + (inventory_gate(lines.append, inv_args), any('is stale' in l for l in lines)), (False, True), detail=lines) + write_text(inv_path, committed) + with open(os.path.join(d, 'src', 'vm.c'), 'a') as f: + f.write('\nint critic_new_reader(Value *v) {\n EigsSlot s = slot_from_heap(v);\n' + ' return slot_is_bool(s) ? 1 : (slot_is_null(s) ? 2 : 0);\n}\n') + lines = [] + case('an unreviewed slot classifier (critic_new_reader): the gate fails, naming it', + (inventory_gate(lines.append, inv_args), + any('`vm.c:critic_new_reader` has no disposition row' in l for l in lines)), (False, True), detail=lines) + if args.build_plants: + args.planted_leaf_scan = build_plant('leaf-scan', out) + args.planted_leaf_mul = build_plant('leaf-mul', out) + args.planted_random_int = build_plant('random-int', out) + args.planted_value_eq = build_plant('value-eq-identity', out) + for label, b, files, want in ( + ('plant C (buffer NaN identity shortcut), release build', args.planted_value_eq, + ['tests/test_numeric_guard.eigs', 'tests/test_bool.eigs'], ('DIFF', 'same')),): + if not b: + out('CAL %-58s NOT RUN (no binary; --build-plants builds it)' % label) + results.append(False) + continue + rc, text, v = diff_restricted(base, b, files) + case('differential %s: DIFF where its conditioned file reaches it' % label, + (rc,) + tuple(v.get(f) for f in files) + (v.get(''),), (3,) + want + (None,), detail=text.splitlines()) + if args.planted_random_int: + # test_system.eigs asserts random_int's bounds over 300 draws, so it + # sees the plant whatever the seed; test_optimize.eigs prints a search + # driven by random_int and is comparable live only because the seed + # is pinned (unpinned, its baseline disagrees with itself and a tape + # would serve the baseline's draws): this case calibrates the pin. + files = ['tests/test_system.eigs', 'tests/test_optimize.eigs', 'tests/test_bool.eigs'] + rc, text, v = diff_restricted(base, args.planted_random_int, files) + case('differential plant D (random_int answers lo+1..hi+1): both files DIFF live', + (rc,) + tuple(v.get(f) for f in files) + (v.get(''),), (3, 'DIFF', 'DIFF', 'same', None), detail=text.splitlines()) + else: + out('CAL %-58s NOT RUN (no --planted-random-int binary; --build-plants builds it)' % 'plant D') + results.append(False) + # P4/B2's dynamic no-SMI plants are retired: ints are legitimate at + # R1.3 and the trap mode is intentionally no-op. Their static source + # plants above still calibrate the historical R1.1/R1.2 mechanism. + for label, flag, b in (('leaf-scanner-early-return', '--planted-leaf-scan', args.planted_leaf_scan), + ('leaf-mul-plus-one', '--planted-leaf-mul', args.planted_leaf_mul)): + if not b: + out('CAL %-58s NOT RUN (no %s binary; --build-plants builds it)' % (label, flag)) + results.append(False) + continue + leaf = [r['id'] for r in map_rows if r['witness']['kind'] == 'leaf' and r['id'] in honest] + (pop, rows_ok, _, n, cnt, wit), lines = sub(map_rows, leaf, branch=b) + if label == 'leaf-scanner-early-return': + case('leaf row vs a scratch build whose leaf scanner returns early', + (cnt['default']['vacuous'] > 0, rows_ok), (True, False), detail=lines) + else: + case('L row vs a scratch build whose leaf MUL adds 1: trace differs', + (cnt['trace']['branch_pass'], cnt['default']['branch_pass'] > 0, rows_ok), (len(leaf), False, False), detail=lines) + n_ok = sum(results) + out('calibration: cases=%d as-expected=%d unexpected=%d' % (len(results), n_ok, len(results) - n_ok)) + return 0 if results and n_ok == len(results) else 1 + + +# The calibration's planted builds: (file, original, planted, make target). +# Each is `git archive HEAD` into a scratch tree, one exact-once replacement, +# plain make; the binary is that tree's src/eigenscript. +BUILD_PLANTS = { + # design 9.2: a leaf scanner that never admits a chunk; a leaf MUL that adds 1 + 'leaf-scan': ('src/chunk.c', ' c->leaf_accessor = 0;\n if (c->param_count > LEAF_ACCESSOR_MAX_DEPTH) return;', + ' c->leaf_accessor = 0;\n return; /* PLANT: the leaf scanner never admits a chunk */\n' + ' if (c->param_count > LEAF_ACCESSOR_MAX_DEPTH) return;', 'build'), + 'leaf-mul': ('src/vm.c', ' r = a * b;\n', ' r = a * b + 1; /* PLANT */\n', 'build'), + # The R1.1 critic's plant D: random_int answers lo+1..hi+1. + # R1.6b/N3 preserves the int result; the fault remains the shifted range. + 'random-int': ('src/builtins.c', 'make_int(lo_i + (eigs_random_long() % (int64_t)span))', + 'make_int(lo_i + 1 + (eigs_random_long() % (int64_t)span))', 'build'), + # R1.6z-D1: identity would make a NaN-holding buffer equal to itself. + 'value-eq-identity': ('src/eigenscript.c', + ' if (a == b && (!a || a->type != VAL_BUFFER)) {', + ' if (a == b) return 1; /* PLANT: bypass IEEE buffer elements */\n if (a == b && (!a || a->type != VAL_BUFFER)) {', 'build'), + +} +_PLANT_DIR = [] + + +def build_plant(name, out): + f, old, new, target = BUILD_PLANTS[name] + d = tempfile.mkdtemp(prefix='eigs-p2plant-') + _PLANT_DIR.append(d) + subprocess.run('git -C %s archive HEAD | tar -x -C %s' % (ROOT, d), shell=True, check=True) + p = os.path.join(d, f) + text = read_text(p) + if text.count(old) != 1: + out(' PLANT %s: the original occurs %d times in %s, want 1 (BROKEN)' % (name, text.count(old), f)) + return None + write_text(p, text.replace(old, new)) + r = subprocess.run(['make', target], cwd=d, capture_output=True) + b = os.path.join(d, 'src', 'eigenscript') + ok = r.returncode == 0 and os.access(b, os.X_OK) + out(' PLANT %s built in %s (make %s): %s' % (name, d, target, + 'ok' if ok else 'BROKEN (make rc=%d)' % r.returncode)) + return b if ok else None + + +# The R1.1 critic's int-source plants (/tmp/phase2-r11-critic/intsrc), as +# exact-once replacements in a copy of src/: (name, [(file, old, new), ...]). +_NUM_ONE = ' CASE(NUM_ONE): {\n vm_push_slot(slot_from_f64(1.0));' +INTSRC_PLANTS = [ + ('P1 a call in builtins.c', [('builtins.c', 'Value* builtin_len(Value *arg) {', + 'static EigsSlot p1_smi(int64_t n) { return slot_from_int(n); }\nValue* builtin_len(Value *arg) {')]), + ('P2 a header macro that calls the writer', [ + ('value_slot.h', '/* Constructors */', '/* Constructors */\n#define SLOT_SMI_OF(i) slot_from_int(i)'), + ('vm.c', _NUM_ONE, ' CASE(NUM_ONE): {\n vm_push_slot(SLOT_SMI_OF(1));')]), + ('P3 raw TAG_SMI bits in vm.c', [ + ('vm.c', _NUM_ONE, ' CASE(NUM_ONE): {\n { EigsSlot one; one.u = TAG_SMI | 1u; vm_push_slot(one); }')]), + ('P4 a producer inside an allowed writer', [ + ('eigenscript.c', ' double n = VAL_F64_RAW(v);\n val_decref(v);\n return slot_from_f64(n);', + ' double n = VAL_F64_RAW(v);\n val_decref(v);\n if (n == 1.0) return slot_from_int(1);\n' + ' return slot_from_f64(n);')]), + ('P5 a raw-bits header macro', [ + ('value_slot.h', '/* Constructors */', '/* Constructors */\n' + '#define SLOT_SMI_BITS(i) ((EigsSlot){ .u = TAG_SMI | ((uint64_t)(i) & SLOT_PAYLOAD_MASK) })'), + ('vm.c', _NUM_ONE, ' CASE(NUM_ONE): {\n vm_push_slot(SLOT_SMI_BITS(1));')]), +] + + +def c_sources_only(d, names): + """copytree ignore: keep directories and C sources (the int-source check + reads nothing else), skip binaries and objects. In src/ itself, only the + TRACKED sources (inv.source_files): a copy must look like a fresh clone, + or a generated header such as src/lsp_stdlib_index.h makes the copy's + inventory differ from the committed one (the R1.1 CI lane).""" + keep = None + if os.path.realpath(d) == os.path.realpath(os.path.join(ROOT, 'src')): + keep = {os.path.basename(f) for f in inv.source_files(d)} + return [n for n in names if not os.path.isdir(os.path.join(d, n)) and + (not n.endswith(('.c', '.h')) or (keep is not None and n not in keep))] + + +def diff_restricted(base, branch, files): + """The corpus differential over a restricted list (calibration only): + (rc, stdout, {file: 'DIFF'|'same'|...}). Anything on its stderr is shown + and recorded as the verdict '' (the tool never writes there).""" + with tempfile.TemporaryDirectory(prefix='eigs-p2diff-') as d: + lst = os.path.join(d, 'files') + write_text(lst, '\n'.join(files) + '\n') + p = subprocess.run(['bash', os.path.join(ROOT, 'tools/phase2_corpus_diff.sh'), base, branch], + cwd=ROOT, stdin=subprocess.DEVNULL, capture_output=True, + env=dict(os.environ, PHASE2_DIFF_FILES=lst)) + text = p.stdout.decode('utf-8', 'replace') + err = p.stderr.decode('utf-8', 'replace') + verdicts = {} + for l in text.split('\n'): + m = re.match(r'^\.\. \d+/\d+ (\S+) (\S+) ', l) + if m: + verdicts[m.group(1)] = m.group(2) + if err: + for l in err.split('\n'): + print(' STDERR phase2_corpus_diff.sh: ' + l, flush=True) + verdicts[''] = 'present' + return p.returncode, text, verdicts + + +RUN_POLICY = ('tests/suite_program_env.sh', 'tests/suite_program_env.txt', 'tests/phase2/corpus_nonzero.txt') + + +def wrapper_tree(d, real, files, script, edit=None): + """A fake branch tree: src/eigenscript runs `script` (bash, $REAL is the + real binary, $N the per-tree invocation count), tests/ holds the files and + the run-condition loader, its table and the nonzero ledger, each of which + `edit` ({path: function(text) -> text}) may change.""" + os.makedirs(os.path.join(d, 'src')) + for f in list(files) + list(RUN_POLICY): + os.makedirs(os.path.join(d, os.path.dirname(f)), exist_ok=True) + text = read_text(os.path.join(ROOT, f)) + write_text(os.path.join(d, f), (edit or {}).get(f, lambda t: t)(text)) + w = os.path.join(d, 'src', 'eigenscript') + write_text(w, '#!/usr/bin/env bash\nREAL=%s\nSTATE=%s\nN=$(cat "$STATE" 2>/dev/null || echo 0)\n' + 'echo $((N + 1)) > "$STATE"\n%s\n' % (real, os.path.join(d, 'n'), script)) + os.chmod(w, 0o755) + return w + + +TAG = 'v0.46.0' + + +def selftest(out): + """--selftest, a tools/selftests.txt row: the calibration runs when the + checker changes (docs/PRINCIPLES.md 5), never on every run. The branch + binary is this tree's src/eigenscript (the self-test job builds it first); + the tag binary is $PHASE2_TAG_BIN, else a build of v0.46.0 from `git + archive`; the planted binaries are built from HEAD. Plain make, one build + at a time.""" + branch = os.path.join(ROOT, 'src', 'eigenscript') + if not os.access(branch, os.X_OK): + out('phase2-check: FAIL: --selftest needs a built %s (run make first)' % branch) + return 2 + tag = os.environ.get('PHASE2_TAG_BIN') + if not tag: + if subprocess.run(['git', '-C', ROOT, 'rev-parse', '-q', '--verify', TAG + '^{commit}'], + capture_output=True).returncode != 0: + out('phase2-check: FAIL: --selftest needs the tag %s in this clone (git fetch --tags)' % TAG) + return 2 + d = tempfile.mkdtemp(prefix='eigs-p2tag-') + _PLANT_DIR.append(d) + subprocess.run('git -C %s archive %s | tar -x -C %s' % (ROOT, TAG, d), shell=True, check=True) + r = subprocess.run(['make'], cwd=d, capture_output=True) + tag = os.path.join(d, 'src', 'eigenscript') + if r.returncode != 0 or not os.access(tag, os.X_OK): + out('phase2-check: FAIL: the %s build failed (make rc=%d)' % (TAG, r.returncode)) + return 2 + out(' TAG %s built in %s' % (TAG, d)) + args = argparse.Namespace(tag=os.path.abspath(tag), branch=branch, build_plants=True, + planted_leaf_scan=None, planted_leaf_mul=None, + planted_random_int=None, planted_value_eq=None) + return calibrate(args, out) + + +def main(): + if sys.argv[1:] == ['--selftest']: + try: + return selftest(lambda s: print(s, flush=True)) + finally: + for d in _PLANT_DIR: + shutil.rmtree(d, ignore_errors=True) + ap = argparse.ArgumentParser(prog='tools/phase2_check.sh') + ap.add_argument('--selftest', action='store_true', + help='the calibration with every plant built (alone: a tools/selftests.txt row)') + ap.add_argument('tag', help='the v0.46.0 tag binary') + ap.add_argument('branch', help='the branch binary') + ap.add_argument('--through', default=ROUNDS[-1], choices=ROUNDS) + ap.add_argument('--baseline', help='the binary from before a mechanical round (corpus differential)') + ap.add_argument('--smi-trap', help='R1.1/R1.2: a `make smi-trap` build of the branch (in its own tree)') + ap.add_argument('--map', default=os.path.join(ROOT, 'tests/phase2/MAP.md')) + ap.add_argument('--rows', default=os.path.join(ROOT, 'tests/phase2/rows')) + ap.add_argument('--controls', default=os.path.join(ROOT, 'tests/phase2/controls')) + ap.add_argument('--calibrate', action='store_true') + ap.add_argument('--planted-leaf-scan', help='calibration: a build whose leaf scanner returns early') + ap.add_argument('--planted-leaf-mul', help='calibration: a build whose leaf MUL computes a*b+1') + ap.add_argument('--planted-random-int', help='calibration: a build whose random_int answers lo+1..hi+1') + ap.add_argument('--planted-value-eq', help='calibration: a build whose buffer equality bypasses NaN elements (C)') + ap.add_argument('--build-plants', action='store_true', + help='calibration: build the four active planted binaries from HEAD') + args = ap.parse_args() + if args.selftest: + print('phase2-check: FAIL: --selftest takes no other argument') + return 2 + for b in (args.tag, args.branch, args.baseline, args.smi_trap, args.planted_leaf_scan, + args.planted_leaf_mul, args.planted_random_int, args.planted_value_eq): + if b and not os.access(b, os.X_OK): + print('phase2-check: FAIL: %s is not an executable' % b) + return 2 + args.tag, args.branch = os.path.abspath(args.tag), os.path.abspath(args.branch) + + def out(s): + print(s, flush=True) + if args.calibrate: + try: + return calibrate(args, out) + finally: + for d in _PLANT_DIR: + shutil.rmtree(d, ignore_errors=True) + return main_check(args, out) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/phase2_check.sh b/tools/phase2_check.sh new file mode 100755 index 000000000..54bd8005c --- /dev/null +++ b/tools/phase2_check.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# tools/phase2_check.sh [--through Rk] [--baseline BIN] [--smi-trap BIN] [--calibrate] +# #1758 Phase 2's row checker (docs/design/phase2-numbers.md 9.2): the MAP's +# landed rows on the v0.46.0 tag and the branch in four tiers, their tier +# witnesses, and a mechanical round's gates. The work is in +# tools/phase2_check.py, whose docstring is the reference. +# -W error: a Python warning (a DeprecationWarning on a newer Python, an +# unclosed file) is a FAIL here, never a line inside a parsed output (the +# R1.1 critic: Python 3.13's warning was read as two classifiers). +command -v python3 >/dev/null 2>&1 || { echo "phase2-check: FAIL: python3 is required"; exit 2; } +exec python3 -W error "$(dirname "$0")/phase2_check.py" "$@" diff --git a/tools/phase2_ci.sh b/tools/phase2_ci.sh new file mode 100755 index 000000000..d138d93ed --- /dev/null +++ b/tools/phase2_ci.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# tools/phase2_ci.sh +# #1758 Phase 2's CI lane (.github/workflows/phase2.yml): the MAP rows that have +# landed (tools/phase2_check.sh, design 9.2) and the stdlib int differential +# (tools/phase2_lib_diff.sh, design 9.5). The workflow builds the three +# binaries: the v0.46.0 anchor (make observer-anchor), origin/main and this +# commit. +# +# --through is DERIVED by phase2_check.py's shared helper. A round needs +# all its MAP row files; each R1.6b..z sub-round additionally needs its declaration +# in tests/phase2/builtins_landed.txt. Rowless R1.6 rounds can therefore land +# explicitly. R1.6a migrates none, so its declaration is optional. A partial round stops the prefix even when later files exist. +# The table and C reader test always run, including R1.6a's empty declaration. +# Matched profile binaries use TAG_BIN_SERVER/ZLIB/DB and +# BRANCH_BIN_SERVER/ZLIB/DB; DATABASE_URL supplies PostgreSQL. +# +# Each tool must exit 0 AND print its own PASS line, whole: a tool that died +# after its last successful command never prints it. +set -u +[ $# -eq 3 ] || { echo "usage: tools/phase2_ci.sh "; exit 2; } +bins=() +for b in "$@"; do case "$b" in /*) bins+=("$b") ;; *) bins+=("$PWD/$b") ;; esac; done +cd "$(dirname "$0")/.." || exit 2 +tmp=$(mktemp "${TMPDIR:-/tmp}/phase2-ci.XXXXXX") || exit 2 +trap 'rm -f "$tmp"' EXIT +python3 -W error - > "$tmp" <<'EOF' || { cat "$tmp"; echo "phase2-ci: FAIL: could not derive --through"; exit 1; } +import os, sys +sys.path.insert(0, 'tools') +import phase2_check as p +rows, bad = p.parse_map(os.path.join(p.ROOT, 'tests/phase2/MAP.md')) +if bad: + print('phase2-ci: the MAP does not parse: ' + '; '.join(bad)) + sys.exit(1) +try: + through, diagnostics = p.derive_through(rows) +except (OSError, ValueError) as error: + print('phase2-ci: invalid landed declaration: ' + str(error)) + sys.exit(1) +for diagnostic in diagnostics: + print('phase2-ci: ' + diagnostic) +if through is None: + print('phase2-ci: no round has all of its row files') + sys.exit(1) +print('THROUGH ' + through) +EOF +grep -v '^THROUGH ' "$tmp" +through=$(sed -n 's/^THROUGH //p' "$tmp") +echo "phase2-ci: through=$through (the complete declared prefix of MAP rounds)" +fails="" +verdict() { # verdict NAME RC OUTPUT VERDICT-ERE: the tool's output; rc 0 and its verdict line, once + printf '%s\n' "$3" + [ "$2" -eq 0 ] && [ "$(grep -cE "$4" <<< "$3")" -eq 1 ] || fails="$fails $1(rc=$2)" +} +out=$(bash tools/phase2_check.sh --through "$through" "${bins[0]}" "${bins[2]}" 2>&1); rc=$? +verdict phase2-check "$rc" "$out" '^phase2-check: PASS$' +out=$(bash tools/phase2_lib_diff.sh "${bins[1]}" "${bins[2]}" 2>&1); rc=$? +verdict phase2-lib-diff "$rc" "$out" '^phase2-lib-diff: listed_functions=[0-9]+ undeclared=0 stale=0 PASS$' +out=$(bash tools/phase2_builtins.sh "${bins[0]}" "${bins[2]}" 2>&1); rc=$? +verdict phase2-builtins "$rc" "$out" '^phase2-builtins: PASS rows=[1-9][0-9]* landed=[^ ]+ migrated=[0-9]+ unchanged=[0-9]+$' +for variant in release asan; do + out=$(ASAN_OPTIONS=detect_leaks=1 bash tools/phase2_readers.sh "$variant" 2>&1); rc=$? + verdict "phase2-readers-$variant" "$rc" "$out" "^phase2-readers: PASS variant=$variant checks=[1-9][0-9]*$" +done +if [ -n "$fails" ]; then + echo "phase2-ci: FAIL:$fails" + exit 1 +fi +echo "phase2-ci: PASS (through=$through)" diff --git a/tools/phase2_corpus_diff.sh b/tools/phase2_corpus_diff.sh new file mode 100755 index 000000000..73da1121e --- /dev/null +++ b/tools/phase2_corpus_diff.sh @@ -0,0 +1,496 @@ +#!/usr/bin/env bash +# tools/phase2_corpus_diff.sh -- #1758: the +# behaviour differential of a mechanical round (docs/design/phase2-numbers.md +# 8, R1.1 and R1.2): "unchanged" is measured, not inferred from a green suite. +# +# Every non-excluded .eigs file under the BRANCH tree's tests/ normally runs twice with each binary, +# tests/phase2/corpus_exclude.txt declares directory ownership, with an owning +# harness and reason. The same exclusions filter both inventories. Empty, +# stale, overlapping, unsafe or ownerless declarations fail; each is reported. +# all four runs from the branch tree's src/ (the suite's cwd) as +# `../tests/`, so a file and whatever it loads by a relative path are the +# same bytes for both; only the binary and the stdlib next to it differ. +# stdin /dev/null, a 60 s bound, `env -u XAUTHORITY DISPLAY=:99 +# SDL_AUDIODRIVER=dummy` (the X server is Xvfb; SDL's dummy audio device +# because a real capture device delivers samples or not by timing, so +# test_audio.eigs printed a SKIP on some runs and two agreeing baseline runs +# did not make it deterministic: a branch run that differed read as a DIFF). +# stdout, stderr and the exit code are compared byte for byte after one +# normalisation: each tree's absolute root becomes ``. +# +# RUN CONDITIONS. Each file runs under its conditions from the ONE loader the +# suite, tools/jit_diff.sh and tools/replay_diff.sh use +# (tests/suite_program_env.sh, policy tests/suite_program_env.txt): a +# fail-soft test runs with EIGS_STRICT=0 as the suite runs it, instead of +# dying at its first strict raise on both binaries and comparing two early +# deaths (the R1.1 critic's plants C and B2 passed that way). A file with +# conditions must complete (rc 0) on both baseline runs, jit_diff's rule; if +# it does not it is INCOMPLETE, never compared, and the run fails. +# +# DECLARED NONZERO EXITS. A file whose baseline exits nonzero (the same code +# on both runs) compares little or nothing, so it must be declared with that +# code and a reason in tests/phase2/corpus_nonzero.txt (a deliberate error, a +# fixture of another host, a capability the release build lacks, the runaway +# guard's own fixture). An undeclared one is UNDECLARED-NONZERO, and a row +# whose file now exits otherwise is STALE: either fails the run. So a file +# that loses its run conditions, or a new test that dies early, is red here. +# Every declared file is listed with its code. +# +# THE SEED PIN. The runtime has no seed flag or variable. An LD_PRELOAD shim, +# built here, pins what the runtime seeds from the clock: +# - drand48 (random, random_int, random_normal, the samplers): the self-seed +# is srand48(ts.tv_sec ^ ts.tv_nsec ^ getpid()) from a CLOCK_MONOTONIC +# reading taken just before (src/builtins.c, random_seed_once). The shim +# records each thread's last CLOCK_MONOTONIC reading and pins an srand48 +# seed that equals that formula, whatever its value; a script's +# `seed_random of N` reaches srand48 unchanged (an explicit seed equal to +# the formula by chance would be pinned too). +# - libc rand() (audio_noise): main() seeds it with srand(time(NULL)); every +# srand call is pinned (main's are its only callers). +# random_hex reads /dev/urandom by design (cryptographic) and stays unpinned: +# the shim marks each run that opens /dev/urandom, and those files are listed +# as UNPINNED with their verdict. The run checks its precondition first: two +# pinned `random` draws must agree. That the pin makes a random file +# comparable live is calibrated once (phase2_check.py --selftest, plant D). +# +# VERDICTS. A file is DETERMINISTIC when the baseline's two runs agree; then +# any branch run that disagrees is a DIFF row (no second chance: a branch +# that is wrong on alternate runs is wrong). When the baseline disagrees with +# itself even with the seed pin (timing, thread scheduling) the file is +# adjudicated on a TAPE, the last resort (docs/TRACE.md: a tape recorded by +# one binary replays on another): the baseline records it and replays it +# twice, the branch replays it once, and the branch must match +# (`same_under_replay`) or it is a DIFF. Replay serves the baseline's +# recorded nondeterministic values, so a builtin's own behaviour is +# INVISIBLE on those files; the report lists them. Replays that disagree +# with each other (the documented thread/subprocess boundary) are NONDET. +# A timeout (rc 124) or a crash (rc >= 128) that all four runs agree on is +# counted as timeout_both or crash_both, never as same. +# +# MEMORY. No blanket cap (a 2 GiB `ulimit -v` made handles_full.eigs's 300 +# spawns fail at 134-214, which read as nondeterminism -- the R1.1 critic). +# Only a file that drives the gfx/ui/audio surface runs under +# `ulimit -v 1500000` (an unbounded GUI run can freeze the 4 GB box); the +# capped files are listed. +# +# CHANGED FILES (the rule, defined here once). When a MODIFIED or NEW file's +# baseline cannot complete it -- its run conditions produce a nonzero baseline +# exit (formerly INCOMPLETE), or its repeated nonzero baseline exit is +# undeclared (formerly UNDECLARED-NONZERO) -- the branch must complete (rc 0, +# or the declared code). If the branch also fails, the result is a red +# MODIFIED-FAIL/NEW-FAIL. If the branch completes, the result is one red +# `DIFF-BEHAVIOUR (changed; the baseline cannot complete it: rc X/Y)` +# row, counted in `diff`; it is never INCOMPLETE or UNDECLARED-NONZERO. This +# rule applies whether or not the file has run conditions. Every other changed +# file is compared exactly like an unchanged file. `modified` and `new` count +# every changed file examined; `branch_only` counts and lists just the files +# whose branch completion was judged without a baseline comparison. +# A file only the branch tree has (a new fixture) is counted as `new`; a file +# only the baseline tree has is a DELETED row (a deleted test hides a change). +# Prints `.. / s` per file, the named rows, then +# `corpus-diff: examined=N ...` and `corpus-diff: PASS|FAIL`. Exit 0 only for +# examined equal to the files listed (> 0), diff=0, deleted=0, incomplete=0, +# undeclared=0, stale=0, and a seed pin that proved itself. +# +# PHASE2_DIFF_FILES= restricts the corpus to the listed relative +# paths, for the calibration (tools/phase2_check.sh --calibrate). A restricted +# run prints `corpus-diff: RESTRICTED ...` instead of a verdict and exits 3: +# it can never pass for the whole corpus. +# PHASE2_DIFF_EVIDENCE_DIR optionally names an absent or empty task-owned +# directory. Every DIFF keeps all relevant runs' complete comparer files +# (.out/.err/.rc) AND unmodified process bytes (.rawout/.rawerr), plus its +# exact diagnostic. Replay differences also keep the tape. No verdict or +# counter depends on the contents of the evidence; retention errors are loud. +set -u +[ $# -eq 2 ] || { echo "usage: tools/phase2_corpus_diff.sh "; exit 2; } +abspath() { (cd "$(dirname "$1")" && printf '%s/%s\n' "$(pwd -P)" "$(basename "$1")"); } +BASE_BIN=$(abspath "$1") || exit 2 +BR_BIN=$(abspath "$2") || exit 2 +BASE_ROOT=$(cd "$(dirname "$BASE_BIN")/.." && pwd -P) +BR_ROOT=$(cd "$(dirname "$BR_BIN")/.." && pwd -P) +for b in "$BASE_BIN" "$BR_BIN"; do [ -x "$b" ] || { echo "corpus-diff: FAIL: $b is not executable"; exit 2; }; done +command -v timeout >/dev/null 2>&1 || { echo "corpus-diff: FAIL: needs coreutils timeout (local tool)"; exit 2; } +command -v cc >/dev/null 2>&1 || { echo "corpus-diff: FAIL: needs a C compiler for the seed pin"; exit 2; } +[ -f "$BR_ROOT/tests/suite_program_env.sh" ] || { echo "corpus-diff: FAIL: no run-condition loader in the branch tree"; exit 2; } +LEDGER="$BR_ROOT/tests/phase2/corpus_nonzero.txt" +[ -f "$LEDGER" ] || { echo "corpus-diff: FAIL: no ledger $LEDGER"; exit 2; } +export EIGS_TEST_DIR="$BR_ROOT/tests" +. "$BR_ROOT/tests/suite_program_env.sh" || { echo "corpus-diff: FAIL: the run-condition loader did not load"; exit 2; } +TMP=$(mktemp -d "${TMPDIR:-/tmp}/eigs-corpus-diff.XXXXXX") || exit 2 +DONE=0 +trap 'rc=$?; [ "$DONE" = 1 ] || echo "corpus-diff: FAIL: exited ($rc) before a verdict, at: $BASH_COMMAND"; rm -rf "$TMP"' EXIT + +# ---- retained DIFF evidence (optional; never normalise these copies) ------ +EVIDENCE_DIR=${PHASE2_DIFF_EVIDENCE_DIR:-} +evidence_fail() { echo "corpus-diff: FAIL: evidence: $*" >&2; return 1; } +evidence_init() { + [ -n "$EVIDENCE_DIR" ] || return 0 + local contents + [ ! -L "$EVIDENCE_DIR" ] || { evidence_fail "directory is a symlink: $EVIDENCE_DIR"; return 1; } + mkdir -p "$EVIDENCE_DIR" || { evidence_fail "cannot create directory: $EVIDENCE_DIR"; return 1; } + contents=$(find "$EVIDENCE_DIR" -mindepth 1 -maxdepth 1 -print -quit) \ + || { evidence_fail "cannot inspect directory: $EVIDENCE_DIR"; return 1; } + [ -z "$contents" ] || { evidence_fail "directory is not empty: $EVIDENCE_DIR"; return 1; } + # Noclobber makes two callers racing to claim the same empty directory fail. + (set -C; : > "$EVIDENCE_DIR/.phase2-corpus-evidence") 2>/dev/null \ + || { evidence_fail "directory claim collision: $EVIDENCE_DIR"; return 1; } + EVIDENCE_DIR=$(cd "$EVIDENCE_DIR" && pwd -P) \ + || { evidence_fail 'cannot resolve directory'; return 1; } + printf 'path\tverdict\tarms\n' > "$EVIDENCE_DIR/index.tsv" \ + || { evidence_fail 'cannot create index'; return 1; } +} +retain_diff() { + [ -n "$EVIDENCE_DIR" ] || return 0 + local rel="$1" verdict="$2" diagnostic="$3" row arm suffix source + shift 3 + case "$rel" in tests/*.eigs) ;; *) evidence_fail "unsafe row path: $rel"; return 1 ;; esac + case "/$rel/" in */../*|*/./*|*//* ) evidence_fail "unsafe row path: $rel"; return 1 ;; esac + [ "$#" -gt 0 ] || { evidence_fail "no run arms: $rel"; return 1; } + row="$EVIDENCE_DIR/$rel" + mkdir -p "$(dirname "$row")" || { evidence_fail "cannot create row parent: $rel"; return 1; } + mkdir "$row" 2>/dev/null || { evidence_fail "row collision: $rel"; return 1; } + printf '%s\n' "$diagnostic" > "$row/verdict.txt" \ + || { evidence_fail "cannot write diagnostic: $rel"; return 1; } + for arm in "$@"; do + case "$arm" in b1|b2|r1|r2|t0|p1|p2|q1) ;; *) evidence_fail "unknown run arm: $arm"; return 1 ;; esac + for suffix in out err rc rawout rawerr; do + source="$TMP/$arm.$suffix" + [ -f "$source" ] || { evidence_fail "missing run file: $rel $arm.$suffix"; return 1; } + # Keep both the already-normalised comparer inputs and raw captures. + cp "$source" "$row/$arm.$suffix" && cmp -s "$source" "$row/$arm.$suffix" \ + || { evidence_fail "copy failed: $rel $arm.$suffix"; return 1; } + done + done + if [ "$verdict" = DIFF-under-replay ]; then + [ -s "$TMP/tape" ] || { evidence_fail "missing replay tape: $rel"; return 1; } + cp "$TMP/tape" "$row/tape" && cmp -s "$TMP/tape" "$row/tape" \ + || { evidence_fail "tape copy failed: $rel"; return 1; } + fi + printf '%s\t%s\t%s\n' "$rel" "$verdict" "$*" >> "$EVIDENCE_DIR/index.tsv" \ + || { evidence_fail "cannot index row: $rel"; return 1; } + echo "corpus-diff: retained $verdict evidence: $row" +} +evidence_init || { DONE=1; exit 2; } + +# ---- the seed pin -------------------------------------------------------- +cat > "$TMP/seedpin.c" <<'CSRC' +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +/* phase2_corpus_diff.sh's seed pin (the tool's header says what it pins). */ +static __thread struct timespec last_mono; +static __thread int have_mono; +int clock_gettime(clockid_t id, struct timespec *ts) { + static int (*real)(clockid_t, struct timespec *); + if (!real) real = (int (*)(clockid_t, struct timespec *))dlsym(RTLD_NEXT, "clock_gettime"); + int r = real(id, ts); + if (r == 0 && id == CLOCK_MONOTONIC) { last_mono = *ts; have_mono = 1; } + return r; +} +void srand48(long seed) { + static void (*real)(long); + if (!real) real = (void (*)(long))dlsym(RTLD_NEXT, "srand48"); + if (have_mono && seed == (long)(last_mono.tv_sec ^ last_mono.tv_nsec ^ (long)getpid())) seed = 0x5EEDL; + real(seed); +} +void srand(unsigned int seed) { + static void (*real)(unsigned int); + if (!real) real = (void (*)(unsigned int))dlsym(RTLD_NEXT, "srand"); + (void)seed; + real(0x5EEDu); +} +FILE *fopen(const char *path, const char *mode) { + static FILE *(*real)(const char *, const char *); + if (!real) real = (FILE *(*)(const char *, const char *))dlsym(RTLD_NEXT, "fopen"); + const char *mark = getenv("PHASE2_URANDOM_MARK"); + if (mark && path && strcmp(path, "/dev/urandom") == 0) { + int fd = open(mark, O_WRONLY | O_CREAT, 0644); + if (fd >= 0) close(fd); + } + return real(path, mode); +} +CSRC +WERROR_FLAGS_FILE="$(dirname "$0")/werror_flags.txt" +. "$(dirname "$0")/read_werror_flags.sh" 2>&1 || exit 2 +# shellcheck disable=SC2086 # the shared flags are one word each +if ! cc -Wall -Wextra $WERROR_FLAGS -shared -fPIC -O2 -o "$TMP/seedpin.so" "$TMP/seedpin.c" -ldl > "$TMP/seedpin.log" 2>&1 \ + || [ -s "$TMP/seedpin.log" ]; then + sed 's/^/ seedpin cc: /' "$TMP/seedpin.log" + echo "corpus-diff: FAIL: the seed pin did not build cleanly"; DONE=1; exit 1 +fi +SHIM="$TMP/seedpin.so" +printf 'print of (random of null)\n' > "$TMP/seedprobe.eigs" +# Each probe answers one number on stdout and nothing on stderr; anything +# else is shown and fails (the probe's grammar, not a substring of it). +p1=$(cd "$TMP" && LD_PRELOAD="$SHIM" "$BR_BIN" seedprobe.eigs 2> "$TMP/probe1.err" < /dev/null) +p2=$(cd "$TMP" && LD_PRELOAD="$SHIM" "$BR_BIN" seedprobe.eigs 2> "$TMP/probe2.err" < /dev/null) +echo "corpus-diff: seed pin: two pinned draws $p1 / $p2" +sed 's/^/ seed probe stderr: /' "$TMP/probe1.err" "$TMP/probe2.err" +if [ -s "$TMP/probe1.err" ] || [ -s "$TMP/probe2.err" ] || ! grep -Eqx '[0-9]+([.][0-9]+)?(e-[0-9]+)?' <<< "$p1" \ + || [ "$p1" != "$p2" ]; then + echo "corpus-diff: FAIL: the seed pin did not take (two pinned draws must print one equal number, no stderr)" + DONE=1; exit 1 +fi + +# ---- the ledger of declared nonzero exits -------------------------------- +# A row: ` `; `#` lines and blank lines +# are prose. Any other line is refused by name (a whitelist, not a skip). +: > "$TMP/ledger" +lnum=0; ledger_bad=0 +while IFS= read -r line || [ -n "$line" ]; do + lnum=$((lnum + 1)) + case "$line" in ''|'#'*) continue ;; esac + lpath=${line%% *}; rest=${line#* }; lrc=${rest%% *}; why=${rest#* } + if ! grep -Eq '^tests/[A-Za-z0-9_./-]+[.]eigs$' <<< "$lpath" || ! grep -Eq '^[1-9][0-9]{0,2}$' <<< "$lrc" \ + || [ "$lrc" -gt 255 ] || [ "$why" = "$rest" ] || [ -z "$why" ]; then + echo "LEDGER-REFUSED corpus_nonzero.txt:$lnum: not ' ': $line" + ledger_bad=$((ledger_bad + 1)); continue + fi + if grep -q "^$lpath " "$TMP/ledger"; then + echo "LEDGER-REFUSED corpus_nonzero.txt:$lnum: $lpath declared twice"; ledger_bad=$((ledger_bad + 1)); continue + fi + if [ -n "$(suite_program_env "$lpath" 2>/dev/null)" ]; then + echo "LEDGER-REFUSED corpus_nonzero.txt:$lnum: $lpath has run conditions, so it must complete; it cannot be declared nonzero" + ledger_bad=$((ledger_bad + 1)); continue + fi + printf '%s %s\n' "$lpath" "$lrc" >> "$TMP/ledger" +done < "$LEDGER" +declared=$(grep -c . "$TMP/ledger") + +# ---- the population ------------------------------------------------------ +(cd "$BR_ROOT" && find tests -name '*.eigs' -type f | LC_ALL=C sort) > "$TMP/files" +# The exclusion ledger owns the population policy; fixtures run through their +# named harness instead of being launched bare by a second implementation. +(cd "$BASE_ROOT" && find tests -name '*.eigs' -type f | LC_ALL=C sort) > "$TMP/basefiles" +printf '0\n' > "$TMP/excluded-count" +if [ -f "$BR_ROOT/tests/phase2/corpus_exclude.txt" ]; then + python3 "$BR_ROOT/tools/phase2_corpus_exclude.py" "$BR_ROOT" "$TMP/files" "$TMP/basefiles" "$TMP/excluded-count" \ + || { echo "corpus-diff: FAIL: declared exclusions"; DONE=1; exit 1; } +elif [ -z "${PHASE2_DIFF_FILES:-}" ]; then + echo 'corpus-diff: FAIL: missing tests/phase2/corpus_exclude.txt'; DONE=1; exit 1 +fi +if [ -n "${PHASE2_DIFF_FILES:-}" ]; then + LC_ALL=C sort "$PHASE2_DIFF_FILES" > "$TMP/requested" + while IFS= read -r rel; do + [ -f "$BR_ROOT/$rel" ] || { echo "corpus-diff: FAIL: restricted list names $rel, absent from the branch tree"; DONE=1; exit 1; } + done < "$TMP/requested" + LC_ALL=C comm -12 "$TMP/files" "$TMP/requested" > "$TMP/selected" + mv "$TMP/selected" "$TMP/files" +fi +excluded=$(cat "$TMP/excluded-count") +listed=$(grep -c . "$TMP/files") +[ "$listed" -gt 0 ] || { echo "corpus-diff: FAIL: no .eigs file to run"; DONE=1; exit 1; } +echo "corpus-diff: baseline $BASE_BIN ($("$BASE_BIN" --version 2>&1 | head -1)) sha256 $(sha256sum "$BASE_BIN" | cut -c1-16)" +echo "corpus-diff: branch $BR_BIN ($("$BR_BIN" --version 2>&1 | head -1)) sha256 $(sha256sum "$BR_BIN" | cut -c1-16)" +echo "corpus-diff: files=$listed excluded=$excluded declared_nonzero=$declared${PHASE2_DIFF_FILES:+ (RESTRICTED by PHASE2_DIFF_FILES)}" + +# run BIN REL OUT CAP [VAR=value] -> OUT.out OUT.err OUT.rc (and OUT.urandom +# when the run opened /dev/urandom), root paths normalised; CAP=1 runs under +# the GUI memory cap. The file's run conditions come from the shared loader. +run() { + local bin="$1" rel="$2" out="$3" cap="$4" extra="${5:-PHASE2_DIFF=1}" rc + rm -f "$out.urandom" + # The run's stderr is its file. The brace group's 2>/dev/null takes only + # bash's own report of a run killed by a signal ("line N: PID Aborted"), + # which would otherwise reach this tool's stderr, where a gate refuses it. + { + ( + cd "$BR_ROOT/src" || exit 99 + [ "$cap" = 1 ] && ulimit -v 1500000 + suite_program_run "$rel" env -u XAUTHORITY DISPLAY=:99 SDL_AUDIODRIVER=dummy LD_PRELOAD="$SHIM" \ + PHASE2_URANDOM_MARK="$out.urandom" "$extra" timeout 60 "$bin" "../$rel" + ) < /dev/null > "$out.rawout" 2> "$out.rawerr" + rc=$? + } 2> /dev/null + echo "$rc" > "$out.rc" + sed "s|$BASE_ROOT||g; s|$BR_ROOT||g" "$out.rawout" > "$out.out" + sed "s|$BASE_ROOT||g; s|$BR_ROOT||g" "$out.rawerr" > "$out.err" +} +same() { cmp -s "$1.out" "$2.out" && cmp -s "$1.err" "$2.err" && cmp -s "$1.rc" "$2.rc"; } +# The one definition of MODIFIED: both tree copies exist and differ bytewise. +modified() { [ -f "$BASE_ROOT/$1" ] && ! cmp -s "$BASE_ROOT/$1" "$BR_ROOT/$1"; } +show_diff() { + diff "$1.out" "$2.out" | head -8 | sed 's/^/ stdout: /' + diff "$1.err" "$2.err" | head -8 | sed 's/^/ stderr: /' +} + +examined=0; nsame=0; same_nonzero=0; nreplay=0; nondet=0; ndiff=0; new=0; new_failed=0; modified_count=0; modified_failed=0; branch_only_count=0; crash_both=0; timeout_both=0 +capped=0; conditioned=0; incomplete=0; undeclared=0; stale=0; unpinned=0 +for f in new modified branch_only diffs nondet crash replayed capped samenz urandom used; do : > "$TMP/$f"; done +# Baseline inventory was filtered with the branch inventory above. +if [ -n "${PHASE2_DIFF_FILES:-}" ]; then + : > "$TMP/deleted" +else + LC_ALL=C comm -23 "$TMP/basefiles" "$TMP/files" > "$TMP/deleted" +fi +deleted=$(grep -c . "$TMP/deleted") +sed 's/^/DELETED (in the baseline tree, not the branch): /' "$TMP/deleted" +took() { echo $(( $(date +%s) - t0 )); } +# replay_adjudicate REL CAP: 0 same under replay, 1 a DIFF, 2 still NONDET +replay_adjudicate() { + rm -f "$TMP/tape" + run "$BASE_BIN" "$1" "$TMP/t0" "$2" "EIGS_TRACE=$TMP/tape" + [ -s "$TMP/tape" ] || return 2 + run "$BASE_BIN" "$1" "$TMP/p1" "$2" "EIGS_REPLAY=$TMP/tape" + run "$BASE_BIN" "$1" "$TMP/p2" "$2" "EIGS_REPLAY=$TMP/tape" + same "$TMP/p1" "$TMP/p2" || return 2 + [ "$(cat "$TMP/p1.rc")" -lt 124 ] || return 2 + run "$BR_BIN" "$1" "$TMP/q1" "$2" "EIGS_REPLAY=$TMP/tape" + same "$TMP/p1" "$TMP/q1" && return 0 + return 1 +} +while IFS= read -r rel; do + examined=$((examined + 1)) + t0=$(date +%s) + changed= + if [ ! -f "$BASE_ROOT/$rel" ]; then + new=$((new + 1)); echo "$rel" >> "$TMP/new"; changed=NEW + elif modified "$rel"; then + modified_count=$((modified_count + 1)); echo "$rel" >> "$TMP/modified"; changed=MODIFIED + fi + if ! cond=$(suite_program_check "$rel" 2> "$TMP/cond.err"); then + sed 's/^/ /' "$TMP/cond.err" + echo "corpus-diff: FAIL: the run conditions of $rel were refused"; DONE=1; exit 1 + fi + [ -n "$cond" ] && conditioned=$((conditioned + 1)) + cap=0 + if grep -Eq '(gfx|ui|audio)_[a-z_]+' "$BR_ROOT/$rel"; then + cap=1; capped=$((capped + 1)); echo "$rel" >> "$TMP/capped" + fi + run "$BASE_BIN" "$rel" "$TMP/b1" "$cap" + run "$BASE_BIN" "$rel" "$TMP/b2" "$cap" + run "$BR_BIN" "$rel" "$TMP/r1" "$cap" + run "$BR_BIN" "$rel" "$TMP/r2" "$cap" + rb1=$(cat "$TMP/b1.rc"); rb2=$(cat "$TMP/b2.rc") + want=$(awk -v p="$rel" '$1 == p { print $2 }' "$TMP/ledger") + # The runs that opened /dev/urandom (random_hex): its values are live. + urand=0 + for a in b1 b2 r1 r2; do [ -e "$TMP/$a.urandom" ] && urand=1; done + # Apply the changed-file exception only at the two former noise outcomes. + branch_only= + if [ -n "$changed" ]; then + if [ -n "$cond" ] && { [ "$rb1" != 0 ] || [ "$rb2" != 0 ]; }; then + branch_only=$changed + elif [ "$rb1" = "$rb2" ] && [ "$rb1" != 0 ] && [ -z "$want" ]; then + branch_only=$changed + fi + fi + if [ -n "$branch_only" ]; then + branch_only_count=$((branch_only_count + 1)); echo "$rel $branch_only" >> "$TMP/branch_only" + rr=$(cat "$TMP/r1.rc"); rr2=$(cat "$TMP/r2.rc") + [ -n "$want" ] && [ "$want" = "$rr" ] && [ "$want" = "$rr2" ] && echo "$rel" >> "$TMP/used" + if { [ -n "$cond" ] && [ "$rr" = 0 ] && [ "$rr2" = 0 ]; } \ + || { [ -z "$cond" ] && { { [ "$rr" = 0 ] && [ "$rr2" = 0 ]; } \ + || { [ -n "$want" ] && [ "$rr" = "$want" ] && [ "$rr2" = "$want" ]; }; }; }; then + ndiff=$((ndiff + 1)) + echo "$rel (changed; the baseline cannot complete it: rc $rb1/$rb2)" >> "$TMP/diffs" + echo ".. $examined/$listed $rel DIFF $(took)s" + retain_diff "$rel" DIFF-BEHAVIOUR "DIFF-BEHAVIOUR $rel (changed; the baseline cannot complete it: rc $rb1/$rb2)" b1 b2 r1 r2 \ + || { DONE=1; exit 2; } + else + [ "$branch_only" = MODIFIED ] && modified_failed=$((modified_failed + 1)) || new_failed=$((new_failed + 1)) + echo ".. $examined/$listed $rel $branch_only-FAIL rc=$rr $(took)s" + echo "$branch_only-FAIL $rel: branch exits $rr/$rr2; expected ${want:-0}${cond:+ under its run conditions ($cond)}" + sed -n '1,6p' "$TMP/r1.err" | sed 's/^/ branch stderr: /' + fi + continue + fi + # jit_diff's rule for unchanged files: a conditioned baseline completes. + if [ -n "$cond" ] && { [ "$rb1" != 0 ] || [ "$rb2" != 0 ]; }; then + incomplete=$((incomplete + 1)) + echo ".. $examined/$listed $rel INCOMPLETE $(took)s" + echo "INCOMPLETE $rel: the baseline exits $rb1/$rb2 under its run conditions ($cond); a file with conditions must complete (rc 0), so it was not compared" + sed -n '1,6p' "$TMP/b1.err" | sed 's/^/ baseline stderr: /' + continue + fi + # A nonzero exit the baseline repeats must be declared with that code. + if [ "$rb1" = "$rb2" ] && [ "$rb1" != 0 ]; then + if [ -z "$want" ]; then + undeclared=$((undeclared + 1)) + echo "UNDECLARED-NONZERO $rel rc=$rb1: the baseline exits $rb1 and tests/phase2/corpus_nonzero.txt does not declare it (an early exit compares nothing: give it run conditions in tests/suite_program_env.txt, or declare it with a reason)" + sed -n '1,4p' "$TMP/b1.err" | sed 's/^/ baseline stderr: /' + elif [ "$want" = "$rb1" ]; then + echo "$rel" >> "$TMP/used" + fi + fi + if same "$TMP/b1" "$TMP/b2"; then + if same "$TMP/b1" "$TMP/r1" && same "$TMP/b1" "$TMP/r2"; then + if [ "$rb1" -eq 124 ]; then + timeout_both=$((timeout_both + 1)); echo "$rel rc=124" >> "$TMP/crash"; verdict=timeout-both + elif [ "$rb1" -ge 128 ]; then + crash_both=$((crash_both + 1)); echo "$rel rc=$rb1" >> "$TMP/crash"; verdict=crash-both + else + nsame=$((nsame + 1)); verdict=same + if [ "$rb1" != 0 ]; then same_nonzero=$((same_nonzero + 1)); echo "$rel rc=$rb1" >> "$TMP/samenz"; fi + fi + echo ".. $examined/$listed $rel $verdict rc=$rb1 $(took)s" + [ "$urand" = 1 ] && echo "$rel $verdict" >> "$TMP/urandom" + continue + fi + ndiff=$((ndiff + 1)) + echo "$rel" >> "$TMP/diffs" + echo ".. $examined/$listed $rel DIFF $(took)s" + if same "$TMP/b1" "$TMP/r1"; then wrong=r2; else wrong=r1; fi + diff_diagnostic="DIFF $rel: the baseline is deterministic (two runs agree); branch run $wrong differs: rc baseline=$rb1 branch=$(cat "$TMP/$wrong.rc")" + echo "$diff_diagnostic" + retain_diff "$rel" DIFF "$diff_diagnostic" b1 b2 r1 r2 || { DONE=1; exit 2; } + show_diff "$TMP/b1" "$TMP/$wrong" + [ "$urand" = 1 ] && echo "$rel DIFF" >> "$TMP/urandom" + continue + fi + replay_adjudicate "$rel" "$cap"; ra=$? + if [ "$ra" -eq 0 ]; then + nreplay=$((nreplay + 1)); echo "$rel" >> "$TMP/replayed"; verdict=same-under-replay + echo ".. $examined/$listed $rel same-under-replay $(took)s" + elif [ "$ra" -eq 1 ]; then + ndiff=$((ndiff + 1)); verdict=DIFF-under-replay + echo "$rel (under replay)" >> "$TMP/diffs" + echo ".. $examined/$listed $rel DIFF-under-replay $(took)s" + diff_diagnostic="DIFF $rel: replaying the baseline's tape, rc baseline=$(cat "$TMP/p1.rc") branch=$(cat "$TMP/q1.rc")" + echo "$diff_diagnostic" + retain_diff "$rel" DIFF-under-replay "$diff_diagnostic" b1 b2 r1 r2 t0 p1 p2 q1 || { DONE=1; exit 2; } + show_diff "$TMP/p1" "$TMP/q1" + else + nondet=$((nondet + 1)); echo "$rel" >> "$TMP/nondet"; verdict=nondet + echo ".. $examined/$listed $rel nondet $(took)s" + fi + [ "$urand" = 1 ] && echo "$rel $verdict" >> "$TMP/urandom" +done < "$TMP/files" + +# A declared row must be examined and must still exit with its code. +while IFS=' ' read -r lpath lrc; do + if ! grep -qxF "$lpath" "$TMP/files"; then + [ -n "${PHASE2_DIFF_FILES:-}" ] && continue + stale=$((stale + 1)); echo "STALE $lpath: declared rc=$lrc in tests/phase2/corpus_nonzero.txt, but no such corpus file was examined" + elif ! grep -qxF "$lpath" "$TMP/used"; then + stale=$((stale + 1)); echo "STALE $lpath: declared rc=$lrc in tests/phase2/corpus_nonzero.txt, but the baseline does not exit with it (correct or remove the row)" + fi +done < "$TMP/ledger" +unpinned=$(grep -c . "$TMP/urandom") +sed 's/^/NEW (only the branch tree has it) /' "$TMP/new" +sed 's/^/MODIFIED (tree copies differ) /' "$TMP/modified" +sed 's/^/BRANCH-ONLY (baseline cannot complete; judged on branch) /' "$TMP/branch_only" +sed 's/^/DIFF-BEHAVIOUR /' "$TMP/diffs" +sed 's/^/REPLAYED (compared on the baseline tape; its nondeterministic builtins did not run live) /' "$TMP/replayed" +sed 's/^/NONDET (the baseline disagrees with itself, and its tape replays too) /' "$TMP/nondet" +sed 's/^/BOTH-CRASH-OR-TIMEOUT /' "$TMP/crash" +sed 's/^/SAME-NONZERO (declared: both binaries exit so; compared up to the exit) /' "$TMP/samenz" +sed 's/^/UNPINNED (opened \/dev\/urandom: random_hex values are live, by design) /' "$TMP/urandom" +sed 's/^/CAPPED (gfx\/ui\/audio: ulimit -v 1500000) /' "$TMP/capped" +summary="examined=$examined excluded=$excluded same=$nsame same_nonzero=$same_nonzero same_under_replay=$nreplay nondet=$nondet diff=$ndiff new=$new new_failed=$new_failed modified=$modified_count modified_failed=$modified_failed branch_only=$branch_only_count deleted=$deleted crash_both=$crash_both timeout_both=$timeout_both conditioned=$conditioned incomplete=$incomplete undeclared=$undeclared stale=$stale ledger_refused=$ledger_bad unpinned=$unpinned capped=$capped" +DONE=1 +if [ -n "${PHASE2_DIFF_FILES:-}" ]; then + echo "corpus-diff: RESTRICTED $summary" + exit 3 +fi +echo "corpus-diff: $summary" +if [ "$examined" -eq "$listed" ] && [ "$examined" -gt 0 ] && [ "$ndiff" -eq 0 ] && [ "$new_failed" -eq 0 ] && [ "$modified_failed" -eq 0 ] && [ "$deleted" -eq 0 ] \ + && [ "$incomplete" -eq 0 ] && [ "$undeclared" -eq 0 ] && [ "$stale" -eq 0 ] && [ "$ledger_bad" -eq 0 ]; then + echo "corpus-diff: PASS"; exit 0 +fi +echo "corpus-diff: FAIL"; exit 1 diff --git a/tools/phase2_corpus_exclude.py b/tools/phase2_corpus_exclude.py new file mode 100755 index 000000000..a2539cf64 --- /dev/null +++ b/tools/phase2_corpus_exclude.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Apply the corpus's declared directory exclusions to both file inventories.""" +from pathlib import Path, PurePosixPath +import re +import sys + + +def is_owned(path, entries): + return any(path.startswith(prefix) for prefix, _, _ in entries) + + +def filter_paths(root, selected): + """Filter a broad corpus before callers narrow it to their own file shapes.""" + root = Path(root) + selected = list(selected) + entries = [] + for number, line in enumerate((root / 'tests/phase2/corpus_exclude.txt').read_text().splitlines(), 1): + if not line.strip() or line.lstrip().startswith('#'): + continue + fields = line.split(maxsplit=2) + if len(fields) != 3 or not fields[2].strip(): + raise ValueError('line %d needs directory, owner and reason' % number) + prefix, owner, reason = fields + for value in (prefix, owner): + if PurePosixPath(value).is_absolute() or '..' in PurePosixPath(value).parts or '.' in value.split('/'): + raise ValueError('unsafe path ' + value) + if not re.fullmatch(r'tests/[A-Za-z0-9_./-]+/', prefix): + raise ValueError('invalid directory ' + prefix) + if not re.fullmatch(r'(?:tools|tests)/[A-Za-z0-9_./-]+', owner) or not (root / owner).is_file(): + raise ValueError('missing owning harness ' + owner) + if any(prefix.startswith(old) or old.startswith(prefix) for old, _, _ in entries): + raise ValueError('duplicate or overlapping directory ' + prefix) + matches = [p for p in (root / prefix).rglob('*.eigs') if p.is_file()] + if not matches: + raise ValueError('stale directory ' + prefix) + entries.append((prefix, owner, reason)) + kept = [path for path in selected if not is_owned(path, entries)] + if not kept: + raise ValueError('zero programs remain after exclusions') + for prefix, owner, reason in entries: + size = sum(path.startswith(prefix) for path in selected) + if not size: + raise ValueError('stale directory %s excludes ZERO files from the corpus' % prefix) + print('EXCLUDED %s files=%d owner=%s reason=%s' % (prefix, size, owner, reason)) + return kept, entries + + +def main(): + if len(sys.argv) not in (3, 5): + raise ValueError('usage: phase2_corpus_exclude.py ROOT FILES [BASELINE COUNT]') + root, branch = map(Path, sys.argv[1:3]) + selected = branch.read_text().splitlines() + kept, entries = filter_paths(root, selected) + branch.write_text(''.join(path + '\n' for path in kept)) + if len(sys.argv) == 5: + baseline, count = map(Path, sys.argv[3:]) + base = [p for p in baseline.read_text().splitlines() + if not is_owned(p, entries)] + baseline.write_text(''.join(path + '\n' for path in base)) + count.write_text(str(len(selected) - len(kept)) + '\n') + return 0 + + +if __name__ == '__main__': + try: + sys.exit(main()) + except (OSError, ValueError) as error: + print('EXCLUSION-REFUSED ' + str(error)) + sys.exit(1) diff --git a/tools/phase2_cost.sh b/tools/phase2_cost.sh new file mode 100755 index 000000000..2f6ed58c7 --- /dev/null +++ b/tools/phase2_cost.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +# Compare release Ir on the in-repo CodSpeed workload set, in BOTH VM tiers. +# The target list has one home: codspeed.yml. Consumer targets live elsewhere. +# Undeclared rows allow +0.3%; declared temporary costs are bounded per row. +# TSV: workload, tier, max_local, max_ci, recover_round, reason (no header). +# #1939: this gate measures and enforces max_local; CI measures max_ci. +# Each column is ceil(delta+0.2, 0.1) from its own gate, never the other method. +# A declared row is bounded on BOTH sides. Above its max it fails (OVER). +# More than STALE_TOLERANCE points below its max it also fails (STALE): a +# round recovered cost and the declaration must come down, or a NEW +# regression could hide under the loose bound. A declaration is written as +# ceil to 0.1 of (measured delta + NOISE), so a fresh one fires neither way +# unless a re-run moves by more than NOISE (STALE_TOLERANCE = 2*NOISE + 0.1). +# NOISE = 0.2 points, the largest measured shift rounded up (R1.3 CI fix): +# run to run, a workload that prints elapsed time moved by 0.026 points; with +# the same binaries a different workload path moved bench/alloc_heavy by +# 0.160 points (the heap layout shifts a short string across a strlen +# alignment boundary, a million times). +# --declared TIER --method METHOD prints validated rows of one tier +# (workload, that method's max, recover_round); all includes tier as field 2. +# METHOD defaults to local. +# bench/check_regression.sh explicitly requests ci, and STALE-checks max_ci. +# --final requires the declarations file to be empty before integration merges, +# and the observer oracle's (tests/phase2/oracle_expect.txt, score.py --declared, +# #1830) as well: #1776 merges only when neither declares anything. +set -eu +cd "$(dirname "$0")/.." +exec python3 - "$@" <<'PY' +import argparse +import math +import struct +from decimal import Decimal +import os +from pathlib import Path +import re +import shlex +import shutil +import subprocess +import sys +import tempfile + +sys.path.insert(0, str(Path.cwd() / 'tools')) +from phase2_cost_declarations import (STALE_TOLERANCE, UNDECLARED_MAX, + assess_row, parse_declarations) + +THRESHOLD_PCT = UNDECLARED_MAX['local'] +# N6 is an explicit cost-only comparison mode. Corpus diffs remain exact. +# Legacy unadorned integer text can denote an integral float; allow its .0 or +# scientific repr only when conversion retains its exact integer value. +NUMBER = re.compile(rb'(?ms', out) + + +def output_tokens(text): + matches = list(NUMBER.finditer(text)) + parts, end = [], 0 + for match in matches: + parts.append(text[end:match.start()]) + end = match.end() + return parts + [text[end:]], [match.group() for match in matches] + + +def compare_result_text(old, new, n6_text=False): + if not n6_text: + if old != new: + raise ValueError('strict byte comparison differs') + return 0 + before, a = output_tokens(old) + after, b = output_tokens(new) + if before != after or len(a) != len(b): + raise ValueError('non-numeric bytes, numeric positions or token count differ') + changed = 0 + for ordinal, (left, right) in enumerate(zip(a, b), 1): + where = 'numeric token %d: ' % ordinal + x, y = float(left), float(right) + if not math.isfinite(x) or not math.isfinite(y): + raise ValueError(where + 'non-finite decimal token') + old_bits, new_bits = struct.pack('>d', x), struct.pack('>d', y) + if old_bits != new_bits: + raise ValueError(where + 'binary64 value/sign bits differ') + if left == right: + continue + if INTEGER.fullmatch(right): + raise ValueError(where + 'exact integer text changed or float became integer') + if INTEGER.fullmatch(left) and int(x) != int(left): + raise ValueError(where + 'integer-to-float conversion loses exact digits') + if right != repr(y).encode('ascii'): + raise ValueError(where + 'changed float text is not Python repr') + changed += 1 + return changed + + +def output_self_test(): + good = [ + (b'answer=[3, 0.10000000000000001]\n', b'answer=[3.0, 0.1]\n'), + (b'x=1e+15\n', b'x=1000000000000000.0\n'), + (b'x=-0\n', b'x=-0.0\n'), + (b'x=9007199254740994\n', b'x=9007199254740994.0\n'), + (b'x=10000000000000000\n', b'x=1e+16\n'), + (b'x=3 time=1.5ms\n', b'x=3.0 time=999ms\n'), + ] + bad = [ + ('value', b'x=1.0\n', b'x=1.0000000000000002\n'), + ('float-to-int-kind', b'x=3.0\n', b'x=3\n'), + ('integer-digits', b'x=9007199254740993\n', b'x=9007199254740992\n'), + ('integer-precision', b'x=9007199254740993\n', b'x=9007199254740992.0\n'), + ('int64-precision', b'x=9223372036854775807\n', b'x=9.223372036854776e+18\n'), + ('integer-spelling', b'x=3\n', b'x=03\n'), + ('signed-zero-erased', b'x=-0.0\n', b'x=0.0\n'), + ('signed-zero-added', b'x=0\n', b'x=-0.0\n'), + ('noncanonical-float', b'x=0.10000000000000001\n', b'x=0.100000000000000010\n'), + ('non-finite-decimal', b'x=1e+308\n', b'x=1e+309\n'), + ('non-numeric', b'answer=3\n', b'wrong=3.0\n'), + ('missing-token', b'x=[3, 4]\n', b'x=[3.0]\n'), + ('extra-token', b'x=3\n', b'x=[3.0, 4.0]\n'), + ('position', b'x=3 y=4\n', b'x=4.0 y=3.0\n'), + ('non-finite-text', b'x=inf\n', b'x=nan\n'), + ] + for old, new in good: + if compare_result_text(elapsed_text(old), elapsed_text(new), True) <= 0: + raise ValueError('self-test positive did not compare a changed token') + for label, old, new in bad: + try: + compare_result_text(elapsed_text(old), elapsed_text(new), True) + except ValueError: + continue + raise ValueError('self-test plant survived: ' + label) + # The default remains byte strict, including the permitted N6 changes. + try: + compare_result_text(good[0][0], good[0][1]) + except ValueError: + pass + else: + raise ValueError('self-test strict comparison accepted N6 text') + identical = b'x=3 0.10000000000000001\n' + for n6_text in (False, True): + if compare_result_text(identical, identical, n6_text) != 0: + raise ValueError('self-test unchanged text failed') + if compare_result_text(elapsed_text(b'x=3 time=1.25ms\n'), + elapsed_text(b'x=3 time=999ms\n'), n6_text) != 0: + raise ValueError('self-test elapsed milliseconds handling failed') + print('phase2-cost-self-test: positive=%d rejected=%d strict=PASS elapsed=PASS PASS' % (len(good), len(bad))) + + +root = Path.cwd() +ap = argparse.ArgumentParser(prog='tools/phase2_cost.sh') +ap.add_argument('--n6-text', action='store_true', + help='cost only: allow exact-value Python-repr float text changes; integer digits stay exact') +ap.add_argument('--self-test', action='store_true', + help='one-time output-comparator calibration; runs no workload or build') +ap.add_argument('--final', action='store_true', + help='require empty cost_expect.txt and oracle_expect.txt (the pre-merge condition)') +ap.add_argument('--declared', choices=('jit', 'off', 'all'), + help='print validated declarations (workload, method max, recover_round); all includes tier as field 2') +ap.add_argument('--method', choices=('local', 'ci'), + help='column for --declared only (default: local); cost runs always enforce max_local') +ap.add_argument('base', nargs='?') +ap.add_argument('cand', nargs='?') +args = ap.parse_args() +if args.self_test: + if args.base or args.cand or args.final or args.declared or args.n6_text or args.method: + ap.error('--self-test runs alone') + try: + output_self_test() + except (AssertionError, ValueError, OverflowError) as error: + sys.exit('phase2-cost-self-test: FAIL: ' + str(error)) + sys.exit(0) +if args.method and not args.declared: + ap.error('--method requires --declared; cost runs enforce max_local') +if args.n6_text and (not args.base or not args.cand or args.declared): + ap.error('--n6-text requires ') +expect_path = root / 'tests/phase2/cost_expect.txt' +if not expect_path.is_file(): + sys.exit('phase2-cost: FAIL: missing tests/phase2/cost_expect.txt') +expect_text = expect_path.read_text() +if args.final and expect_text: + sys.exit('phase2-cost: FAIL: --final requires an empty tests/phase2/cost_expect.txt') +oracle_path = root / 'tests/phase2/oracle_expect.txt' +if args.final and not oracle_path.is_file(): + sys.exit('phase2-cost: FAIL: missing tests/phase2/oracle_expect.txt') +if args.final and oracle_path.read_text(): + sys.exit('phase2-cost: FAIL: --final requires an empty tests/phase2/oracle_expect.txt' + ' (%d observer-oracle declaration(s) remain)' % len(oracle_path.read_text().splitlines())) +if bool(args.base) != bool(args.cand) or (not args.base and not args.final and not args.declared) \ + or (args.declared and (args.base or args.final)): + ap.error('supply , or --final alone, or --declared TIER alone') +if not args.base and not args.declared: + print('phase2-cost: final declarations=0 oracle_declarations=0 PASS') + sys.exit(0) +local_workload = re.compile(r'(?:bench/[^/]+|tests/bench_[^/]+)\.eigs') +workloads = set() +for line in (root / 'codspeed.yml').read_text().splitlines(): + m = re.match(r'\s*exec:\s*(.*)', line) + if not m: + continue + argv = shlex.split(m[1]) + local = [p for p in argv if local_workload.fullmatch(p)] + if local: + if len(local) != 1 or argv[-2:] != ['./src/eigenscript', local[0]]: + sys.exit('phase2-cost: FAIL: unsupported local CodSpeed command: ' + m[1]) + workloads.add(local[0]) +if not workloads: + sys.exit('phase2-cost: FAIL: no in-repo CodSpeed workloads') +# The execution list has one home; tracked sources anchor its coverage. +tracked = subprocess.check_output(['git', 'ls-files', '-z'], cwd=root).decode().split('\0') +expected = {p for p in tracked if local_workload.fullmatch(p)} +if workloads != expected: + sys.exit('phase2-cost: FAIL: CodSpeed workload coverage missing=%s stale=%s' % + (sorted(expected - workloads), sorted(workloads - expected))) +try: + expectations = parse_declarations(expect_text, workloads) +except ValueError as error: + sys.exit('phase2-cost: FAIL: ' + str(error)) +if args.declared: + for (workload, tier), declaration in sorted(expectations.items()): + if tier == args.declared or args.declared == 'all': + fields = [workload] + if args.declared == 'all': + fields.append(tier) + fields += [str(declaration.maximum(args.method or 'local')), declaration.recover_round] + print('\t'.join(fields)) + sys.exit(0) +if not shutil.which('valgrind'): + sys.exit('phase2-cost: FAIL: valgrind is required') +bins = [str(Path(p).resolve(strict=True)) for p in (args.base, args.cand)] +for binary in bins: + if not os.access(binary, os.X_OK): + sys.exit('phase2-cost: FAIL: not executable: ' + binary) + symbols = subprocess.run(['nm', '-D', binary], capture_output=True, text=True, check=True).stdout + if re.search(r'__(?:asan|ubsan)', symbols): + sys.exit('phase2-cost: FAIL: sanitizer binary: ' + binary) +logdir = Path(tempfile.mkdtemp(prefix='phase2-cost-')) +print('phase2-cost: source=codspeed.yml workloads=%d tiers=2 undeclared_max=+%s%% declarations=%d ' + 'stale_below_max=%s logs=%s method=local column=max_local' % + (len(workloads), THRESHOLD_PCT, len(expectations), STALE_TOLERANCE, logdir), flush=True) +if args.n6_text: + print('phase2-cost: output_compare=n6-text (exact values/bits, exact integer digits, Python repr)', flush=True) +over = stale = 0 +for workload in sorted(workloads): + for tier in ('jit', 'off'): + results = [] + for arm, binary in zip(('base', 'cand'), bins): + prefix = logdir / (workload.replace('/', '_') + '.' + tier + '.' + arm) + env = {k: v for k, v in os.environ.items() if not k.startswith('EIGS_')} + if tier == 'off': + env['EIGS_JIT_OFF'] = '1' + with prefix.with_suffix(prefix.suffix + '.stdout').open('wb') as stdout, \ + prefix.with_suffix(prefix.suffix + '.stderr').open('wb') as stderr: + run = subprocess.run(['valgrind', '--tool=cachegrind', '--cache-sim=no', + '--branch-sim=no', '--cachegrind-out-file=' + str(prefix) + '.cg', + binary, workload], cwd=root, env=env, stdin=subprocess.DEVNULL, + stdout=stdout, stderr=stderr, timeout=900) + if run.returncode: + sys.exit('phase2-cost: FAIL: %s %s %s rc=%d (see %s)' % + (workload, tier, arm, run.returncode, prefix)) + summary = re.findall(r'^summary:\s*(\d+)\s*$', Path(str(prefix) + '.cg').read_text(), re.M) + if len(summary) != 1 or int(summary[0]) <= 0: + sys.exit('phase2-cost: FAIL: missing/ambiguous Ir: ' + str(prefix)) + out = Path(str(prefix) + '.stdout').read_bytes() + # The bench prints elapsed milliseconds; compare every result field. + out = elapsed_text(out) + err = Path(str(prefix) + '.stderr').read_text() + err = re.sub(r'^==\d+==.*\n?', '', err, flags=re.M) + results.append((int(summary[0]), out, err)) + (base, out_a, err_a), (cand, out_b, err_b) = results + try: + changed = compare_result_text(out_a, out_b, args.n6_text) + changed += compare_result_text(err_a.encode(), err_b.encode(), args.n6_text) + except (ValueError, OverflowError) as error: + sys.exit('phase2-cost: FAIL: output differs: %s %s: %s (see %s)' % + (workload, tier, error, logdir)) + if args.n6_text and changed: + print('phase2-cost: n6-text workload=%s tier=%s changed_tokens=%d' % + (workload, tier, changed), flush=True) + delta = 100 * (cand - base) / base + maximum, declaration, verdict = assess_row(expectations, workload, tier, 'local', base, cand) + recover_round = declaration.recover_round if declaration else None + reason = declaration.reason if declaration else None + over += verdict == 'OVER' + stale += verdict == 'STALE' + print('%-34s %-3s base=%12d cand=%12d delta=%+.4f%% max=+%s%% %s%s' % + (workload, tier, base, cand, delta, maximum, verdict, + (' recover_round=%s reason=%s' % (recover_round, reason)) if recover_round else ''), flush=True) +print('phase2-cost: workloads=%d rows=%d over_declared_max=%d stale_declarations=%d %s' % + (len(workloads), len(workloads) * 2, over, stale, 'FAIL' if over or stale else 'PASS')) +sys.exit(bool(over or stale)) +PY diff --git a/tools/phase2_cost_declarations.py b/tools/phase2_cost_declarations.py new file mode 100644 index 000000000..7a2880cc2 --- /dev/null +++ b/tools/phase2_cost_declarations.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""One schema and two-sided predicate for Phase 2's per-method cost bounds.""" +import argparse +from dataclasses import dataclass +from decimal import Decimal, InvalidOperation +from pathlib import Path +import re +import sys + +NOISE = Decimal('0.2') +STALE_TOLERANCE = 2 * NOISE + Decimal('0.1') +METHODS = ('local', 'ci') +UNDECLARED_MAX = {'local': Decimal('0.3'), 'ci': Decimal('5')} + + +@dataclass(frozen=True) +class Declaration: + max_local: Decimal + max_ci: Decimal + recover_round: str + reason: str + + def maximum(self, method): + if method not in METHODS: + raise ValueError('unknown cost method: ' + method) + return getattr(self, 'max_' + method) + + +def parse_declarations(text, workloads=None): + expectations = {} + for n, line in enumerate(text.splitlines(), 1): + if not line.strip() or line.lstrip().startswith('#'): + continue + fields = [f.strip() for f in line.split('\t')] + where = 'cost_expect.txt:%d: ' % n + if len(fields) != 6: + raise ValueError(where + 'expected six TSV fields ' + '(workload, tier, max_local, max_ci, recover_round, reason)') + workload, tier, local, ci, recover_round, reason = fields + if (workloads is not None and workload not in workloads) or tier not in ('jit', 'off'): + raise ValueError(where + 'unknown workload/tier: %s %s' % (workload, tier)) + key = (workload, tier) + if key in expectations: + raise ValueError(where + 'duplicate workload/tier: %s %s' % key) + maxima = [] + for column, value in (('max_local', local), ('max_ci', ci)): + try: + maximum = Decimal(value) + except InvalidOperation: + maximum = Decimal('NaN') + if not maximum.is_finite() or maximum < 0: + raise ValueError(where + column + ' must be finite and >= 0') + maxima.append(maximum) + if not re.fullmatch(r'R[1-9][0-9]*(?:\.[1-9][0-9]*)?', recover_round) or not reason: + raise ValueError(where + 'recover_round and reason are required') + expectations[key] = Declaration(*maxima, recover_round, reason) + return expectations + + +def assess_row(expectations, workload, tier, method, base, cand): + """Return this method's maximum, declaration and exact two-sided verdict.""" + if method not in METHODS: + raise ValueError('unknown cost method: ' + method) + if base <= 0 or cand <= 0: + raise ValueError('instruction counts must be positive') + declaration = expectations.get((workload, tier)) + maximum = declaration.maximum(method) if declaration else UNDECLARED_MAX[method] + difference = Decimal((cand - base) * 100) + if difference > maximum * base: + verdict = 'OVER' + elif declaration and difference < (maximum - STALE_TOLERANCE) * base: + verdict = 'STALE' + else: + verdict = 'ok' + return maximum, declaration, verdict + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('--method', choices=METHODS, required=True) + ap.add_argument('--row', nargs=4, metavar=('WORKLOAD', 'TIER', 'BASE_IR', 'CAND_IR'), required=True) + args = ap.parse_args() + workload, tier, base, cand = args.row + try: + base, cand = int(base), int(cand) + root = Path(__file__).resolve().parents[1] + expectations = parse_declarations((root / 'tests/phase2/cost_expect.txt').read_text()) + if tier not in ('jit', 'off'): + raise ValueError('unknown tier: ' + tier) + maximum, declaration, verdict = assess_row(expectations, workload, tier, args.method, base, cand) + except (OSError, ValueError) as error: + print('phase2-cost: FAIL: ' + str(error), file=sys.stderr) + return 2 + delta = Decimal((cand - base) * 100) / base + label = ('REGRESSION' if verdict == 'OVER' else 'STALE DECLARATION' if verdict == 'STALE' + else 'DECLARED' if declaration else 'ok') + print(' %s: %s tier=%s Ir %d -> %d delta=%+.4f%% max_%s=+%s%%%s' % + (label, workload, tier, base, cand, delta, args.method, maximum, + (' recover_round=' + declaration.recover_round) if declaration else '')) + return int(verdict != 'ok') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/phase2_float_amend.py b/tools/phase2_float_amend.py new file mode 100644 index 000000000..a69af3cae --- /dev/null +++ b/tools/phase2_float_amend.py @@ -0,0 +1,434 @@ +#!/usr/bin/env python3 +"""R1.7-D6: derive the frozen re-render population from four live gate runs. + +Kinds come from source-bound witnesses, never from legacy text ("3" can be a +float). Numeric position means line and ordinal plus identical separating +bytes; character offsets may move when a rendering grows. The anchor is the +last pre-N6 commit, including earlier accepted frozen-bar amendments. +""" +import argparse +import hashlib +import io +import json +import math +from pathlib import Path +import re +import struct +import subprocess +import sys +import tempfile +import os + +BASE = 'b6f9db3879be5578de787ad6cf0d235b809f0e7c' +BAR = 'tests/phase2/builtins/builtins/' +PROFILES = ('release', 'server', 'server-db', 'zlib') +NUMBER = re.compile(rb'(?d', value) + + +def render(old, kinds, path): + lines = old.splitlines(keepends=True) + require(len(lines) == len(kinds), path + ': line count differs from kind metadata') + result = [] + for number, (line, line_kinds) in enumerate(zip(lines, kinds), 1): + parts, tokens = split(line) + require(len(tokens) == len(line_kinds), '%s:%d: numeric population differs from kind metadata' % (path, number)) + out = parts[0] + for token, kind, suffix in zip(tokens, line_kinds, parts[1:]): + require(kind in ('int', 'float', 'text'), path + ': unknown numeric kind ' + str(kind)) + if kind == 'text': + replacement = token + elif kind == 'int': + require(re.fullmatch(rb'-?(?:0|[1-9][0-9]*)', token) is not None, path + ': non-integral int token') + replacement = token + else: + double(token) + replacement = repr(float(token)).encode('ascii') + out += replacement + suffix + result.append(out) + return b''.join(result) + + +def check_pair(old, new, kinds, path): + expected = render(old, kinds, path) + a, b = old.splitlines(keepends=True), new.splitlines(keepends=True) + require(len(a) == len(b), path + ': line count changed') + floats = 0 + for number, (left, right, line_kinds) in enumerate(zip(a, b, kinds), 1): + lp, lt = split(left) + rp, rt = split(right) + label = '%s:%d' % (path, number) + require(len(lt) == len(rt), label + ': numeric token count changed') + require(lp == rp, label + ': non-float text or numeric position changed') + for ordinal, (x, y, kind) in enumerate(zip(lt, rt, line_kinds), 1): + where = label + ' token ' + str(ordinal) + if kind in ('int', 'text'): + require(x == y, where + ': integer spelling/value/kind changed') + else: + require(double(x) == double(y), where + ': double bits changed') + require(y.decode('ascii') == repr(float(y)), where + ': float kind/text is not Python repr') + floats += 1 + require(new == expected, path + ': amendment is not the complete N6 re-render') + return floats + + +def load(root, metadata): + data = json.loads(metadata.read_text()) + require(data.get('schema') == 2 and data.get('ruling') == 'R1.7-D6' and data.get('base') == BASE, + 'unsupported metadata schema/ruling/anchor') + files = data.get('files', {}) + population = data.get('population', {}) + require(bool(files) and set(files) == set(population), 'metadata file population differs from the derived gate population') + require(set(data.get('profiles', [])) == set(PROFILES), 'population must cover all four profiles') + retired = retired_paths(root, data) + require(not (retired & (set(files) | set(population))), 'retired path remains in the active re-render population') + for path, profiles in population.items(): + require(path.startswith(BAR) and path.endswith('.branch') and '/' not in path[len(BAR):], 'invalid population path: ' + path) + require(bool(profiles) and set(profiles) <= set(PROFILES), path + ': invalid profile population') + paths = git(root, 'ls-tree', '-r', '--name-only', BASE, '--', BAR).decode().splitlines() + require(retired <= set(paths), 'retirement path was not in the frozen pre-N6 population') + branch_paths = [path for path in paths if path.endswith('.branch') and path not in retired] + source_paths = [path.removesuffix('.branch') + '.eigs' for path in files] + anchored = anchor_files(root, branch_paths + source_paths) + frozen = {path: anchored[path] for path in branch_paths} + require(bool(frozen), 'anchor has no frozen files') + apply_semantic_amendment(root, data, frozen) + apply_diagnostic_amendment(root, frozen) + for path, entry in files.items(): + old = frozen.get(path) + require(old is not None and sha(old) == entry['old_sha256'], path + ': old bytes do not match pinned pre-N6 hash') + source = path.removesuffix('.branch') + '.eigs' + baseline = anchored[source] + require(sha(baseline) == entry['source_sha256'] and (root / source).read_bytes() == baseline, + source + ': probe source changed; kind provenance must be reviewed') + source_lines = baseline.decode().splitlines() + old_lines = old.splitlines(keepends=True) + require(old_lines[-1] == b'rc=0\n', path + ': missing fixed rc=0 trailer') + line_kinds = [[None] * len(split(line)[1]) for line in old_lines] + line_kinds[-1] = ['int'] + require(bool(entry['witnesses']), path + ': no kind witnesses') + for witness in entry['witnesses']: + require(witness['expression'] in source_lines[witness['line'] - 1], source + ': kind witness no longer comes from its source line') + require(witness['kind'] in ('int', 'float', 'text'), source + ': invalid kind witness') + require(witness.get('reader', 'num_type') == ('type' if witness['kind'] == 'text' else 'num_type'), source + ': invalid kind reader') + require(witness.get('transform') in (None, 'num'), source + ': invalid witness transform') + require(all(type(i) is int and i >= 0 for i in witness.get('index_path', [])), source + ': invalid witness index path') + require(bool(witness['positions']), source + ': kind witness explains no tokens') + for line, ordinal in witness['positions']: + require(1 <= line < len(old_lines) and 1 <= ordinal <= len(line_kinds[line - 1]), + path + ': kind witness position outside numeric population') + require(line_kinds[line - 1][ordinal - 1] is None, path + ': duplicate numeric kind witness') + line_kinds[line - 1][ordinal - 1] = witness['kind'] + require(all(kind is not None for row in line_kinds for kind in row), path + ': numeric token has no kind witness') + entry['line_kinds'] = line_kinds + render(old, entry['line_kinds'], path) + return data, frozen + + +def run_witnesses(root, binary, data, expected): + env = {key: value for key, value in os.environ.items() + if not key.startswith('EIGS_') and key not in ('LD_PRELOAD', 'LD_AUDIT')} + for path, entry in data['files'].items(): + source = (root / (path.removesuffix('.branch') + '.eigs')).read_bytes() + def expression(w): + e = '(' + w['expression'] + ')' + e += ''.join('[' + str(i) + ']' for i in w.get('index_path', [])) + return '(num of ' + e + ')' if w.get('transform') == 'num' else e + tail = '\nprint of "R17-D2-KINDS-BEGIN"\n' + ''.join( + 'print of (' + w.get('reader', 'num_type') + ' of ' + expression(w) + ')\n' for w in entry['witnesses']) + with tempfile.TemporaryDirectory(prefix='r17-d2-kind-') as temp: + probe = Path(temp) / 'witness.eigs' + probe.write_bytes(source + tail.encode()) + case = Path(path).stem + rows = [line.split('\t') for line in (root / 'tests/phase2/builtins/cases.tsv').read_text().splitlines()[1:]] + row = next(r for r in rows if r[0] == case) + require(row[2] == '-', path + ': kind witness needs an observation hook') + stdin = b'' if row[3] == '-' else (root / BAR / '_stdin' / row[3]).read_bytes() + result = subprocess.run([str(binary), str(probe)], cwd=temp, env={**env, 'EIGS_PROBE_TMP':temp}, + input=stdin, capture_output=True, timeout=10) + require(result.returncode == 0 and not result.stderr, + path + ': kind witness failed rc=%d stderr=%r' % (result.returncode, result.stderr)) + require(result.stdout.count(MARKER) == 1, path + ': kind witness marker missing/duplicated') + actual, kinds = result.stdout.split(MARKER) + require(actual + b'rc=0\n' == expected[path], path + ': runtime probe differs from the checked frozen text') + wanted = ''.join(('str' if w['kind'] == 'text' else w['kind']) + '\n' for w in entry['witnesses']).encode() + require(kinds == wanted, path + ': runtime kind witness changed: ' + repr(kinds)) + print('float-amend: runtime kind witnesses binary=%s files=%d PASS' % (binary, len(data['files']))) + + +def check(root, metadata, candidate_dir=None, baseline_binary=None, binary=None): + data, frozen = load(root, metadata) + if candidate_dir: + paths = list(candidate_dir.rglob('*')) + require({p.relative_to(candidate_dir).as_posix() for p in paths} == {Path(p).name for p in data['files']}, + 'candidate file population differs from the derived population') + actual = {path: (candidate_dir / Path(path).name).read_bytes() for path in data['files']} + else: + actual = {p.relative_to(root).as_posix(): p.read_bytes() for p in (root / BAR).rglob('*.branch')} + require(set(actual) == set(frozen), 'frozen .branch file population changed') + require(all(actual[p] == frozen[p] for p in frozen if p not in data['files']), + 'a frozen file outside the derived population changed') + changed, tokens = [], 0 + for path, entry in data['files'].items(): + tokens += check_pair(frozen[path], actual[path], entry['line_kinds'], path) + if actual[path] != frozen[path]: + changed.append(path) + print('float-amend: R1.7-D6 path=%s SHA-256=%s' % (path, sha(actual[path]))) + require(len(changed) == len(data['files']), 'derived population contains an unchanged row') + if baseline_binary: + run_witnesses(root, baseline_binary, data, frozen) + if binary: + run_witnesses(root, binary, data, actual) + print('float-amend: examined=%d amended=%d floats=%d PASS' % (len(data['files']), len(changed), tokens)) + return data, actual, changed + + +def self_test(root, metadata): + data, frozen = load(root, metadata) + with tempfile.TemporaryDirectory(prefix='r17-d2-selftest-') as temp: + directory = Path(temp) + good = {Path(p).name: render(frozen[p], e['line_kinds'], p) for p, e in data['files'].items()} + def invoke(): + return subprocess.run([sys.executable, str(Path(__file__).resolve()), '--root', str(root), + '--metadata', str(metadata), '--candidate-dir', str(directory)], capture_output=True) + def write(files): + for p in directory.glob('*'): + p.unlink() + for name, body in files.items(): + (directory / name).write_bytes(body) + write(good) + require(invoke().returncode == 0, 'self-test positive complete Python-repr amendment failed') + plants = { + 'value': ('fround.branch', lambda b: b.replace(b'0.10000000149011612', b'0.2')), + 'kind': ('str.branch', lambda b: b.replace(b'"3.0"', b'"3"')), + 'integer': ('json_encode.branch', lambda b: b.replace(b'9007199254740993', b'9007199254740992', 1)), + 'integer-kind': ('json_encode.branch', lambda b: b.replace(b'[3,', b'[3.0,', 1)), + 'non-float-text': ('json_build.branch', lambda b: b.replace(b'"a"', b'"z"')), + 'missing-token': ('min.branch', lambda b: b.replace(b'[3, "int"]', b'["int"]', 1)), + 'extra-token': ('max.branch', lambda b: b.replace(b'[3, "int"]', b'[3, 4, "int"]', 1)), + 'float-render': ('str.branch', lambda b: b.replace(b'"3.0"', b'"3.00"')), + 'position': ('json_build.branch', lambda b: b.replace(b'"d": 3.0', b'3.0: "d"')), + } + count = 0 + for label, (name, mutate) in plants.items(): + bad = dict(good) + bad[name] = mutate(bad[name]) + require(bad[name] != good[name], 'self-test plant did not take: ' + label) + write(bad) + result = invoke() + require(result.returncode == 1 and b'FAIL:' in result.stderr, 'self-test plant survived: ' + label) + print('float-amend-self-test: %s rejected PASS' % label) + count += 1 + for label, files in [('missing-file', {k: v for k, v in good.items() if k != 'str.branch'}), + ('extra-file', dict(good, extra_branch=b'0\n'))]: + if label == 'extra-file': + files['extra.branch'] = files.pop('extra_branch') + write(files) + result = invoke() + require(result.returncode == 1 and b'FAIL:' in result.stderr, 'self-test plant survived: ' + label) + print('float-amend-self-test: %s rejected PASS' % label) + count += 1 + print('float-amend-self-test: positive=1 rejected=%d PASS' % count) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--root', type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument('--metadata', type=Path) + parser.add_argument('--candidate-dir', type=Path, help='isolated directory containing exactly the derived candidate .branch files') + parser.add_argument('--observations', action='append', help='derive from PROFILE=DIR gate transcripts; repeat for all four profiles') + parser.add_argument('--population-output', type=Path, help='write the derived population JSON; never changes expectations') + parser.add_argument('--baseline-binary', type=Path, help='also execute source-bound kind witnesses on the pre-N6 binary') + parser.add_argument('--binary', type=Path, help='also execute source-bound kind witnesses on the candidate binary') + parser.add_argument('--self-test', action='store_true', help='one-time planted-fault calibration; never modifies the checkout') + args = parser.parse_args() + metadata = args.metadata or args.root / 'tests/phase2/r17_float_text.json' + try: + if args.observations: + require(args.population_output is not None, 'derive needs --population-output') + derive(args.root, args.observations, args.population_output) + elif args.self_test: + self_test(args.root, metadata) + else: + check(args.root, metadata, args.candidate_dir, args.baseline_binary, args.binary) + except (OSError, ValueError, KeyError, IndexError, TypeError, subprocess.TimeoutExpired) as error: + print('FAIL: float-amend: ' + str(error), file=sys.stderr) + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/phase2_inventory.py b/tools/phase2_inventory.py new file mode 100755 index 000000000..437e140c9 --- /dev/null +++ b/tools/phase2_inventory.py @@ -0,0 +1,1098 @@ +#!/usr/bin/env python3 +"""phase2_inventory.py -- #1758 round 0: every numeric site in src/*.c and +src/*.h, classified into exactly one category. + +POPULATION. A site is one occurrence, outside comments and string/char +literals, of a token in VOCAB: the value model's numeric vocabulary (the +number type tag, the slot predicates, the raw and checked payload accessors, +the constructors, the clamp, the formatter, the double parsers, and the AST +literal fields). The key is the TOKEN, so the blind spot is code that handles +a number without naming one of them. Two derived checks bound that blind spot: + - every function in src/ that returns or writes out a `double` AND reads a + payload (a raw or checked read appears in its body) is a numeric accessor; + it must be in VOCAB (its call sites are then sites) or in WRAPPERS with a + reason. A new accessor nobody reviewed is UNCLASSIFIED. + - every VAL_FLOAT must sit in a syntactic position the classifier recognizes + (test, case, tag store, type yield, emitter immediate); any other position + is UNCLASSIFIED. +Not in the population: src/freestanding/ (outside the src/*.c glob), and code +that computes on a double already extracted by a counted read. + +CATEGORIES (first matching rule wins; ROLE rules by file/function come first, +then the token's own kind): + a type tests b payload reads c producers + d JIT emitters/guards e number formatting + f equality, ordering and hashing g tape encode/decode + h JSON, store and db encoders i observer numeric paths + j embed API + +BUILTINS. The registration table (env_set_local_owned(env, "name", +make_builtin(fn)) lines and the ext_names.h X-lists) is the builtin +population (calibrated once against `eigenscript --api --json`: 311 = 311). +A builtin is numeric if its function, or a same-file function it calls or +names as a value (a comparator, an op table entry; closure depth 4), contains +a site, or if it calls the shared formatter or equality (SEMANTIC_LEAVES). +Every numeric builtin must have exactly one row in the classification table +of docs/design/phase2-numbers.md (class and loss columns from closed token +sets), every row must name a numeric builtin, and the LOSS names must equal +the frozen historical LOSS scope (§6). Migration +removes lossy paths; it does not change those historical classifications. + +Usage: tools/phase2_inventory.py [--src DIR] [--doc FILE] [--out FILE] + [--no-write] [--sites] +Prints `examined=N classified=N unclassified=0` and the builtin line; exits 1 +if any site or builtin is unclassified, a table row is stale, a rule or +token never fires, or nothing was examined. +""" +import argparse +import difflib +import glob +import os +import re +import subprocess +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# ---- vocabulary: token -> kind ------------------------------------------- +# Kinds decide the default category (a/b/c/e) when no role rule matches. +VOCAB = { + # (a) type tests and type derivations + 'VAL_FLOAT': 'TAG', # R1.2a: the float ValType + 'VAL_INT': 'TAG', # R1.2a: the int ValType (no producer before R1.3) + 'slot_is_f64': 'TEST', # R1.1: was slot_is_num (slot_is_numeric deleted) + 'slot_is_smi': 'TEST', # R1.1: the SMI tag test + 'NUM_REUSE': 'TEST', + 'val_is_number': 'TEST', # the Value test admits both numeric kinds + 'AST_NUM': 'TAG', + # (b) payload reads + 'VAL_F64_RAW': 'RAW', # the float payload + 'VAL_I64_RAW': 'RAW', # R1.2a: a VAL_INT's int64 + 'SLOT_F64_RAW': 'RAW', # R1.1: was SLOT_NUM_RAW + 'slot_smi_val': 'RAW', # R1.1: an SMI's int (no int Value exists before R1.3) + 'VAL_F64_OFFSET': 'RAW', # the JIT float payload offset + 'slot_f64_value': 'READ', # R1.1: replaces vm.c's slot_as_double (floats only) + 'slot_int_value': 'READ', # R1.1: an int's value (SMI; R1.2a adds the VAL_INT box) + 'eigs_num_arg': 'READ', + 'eigs_num_arg_slow': 'READ', + 'eigs_list_num': 'READ', + 'eigs_opt_num': 'READ', + 'eigs_elem_num': 'READ', + 'eigs_slot_num': 'READ', + 'eigs_slot_elem_num': 'READ', + 'eigs_slot_opt_num': 'READ', + 'list_get_num': 'READ', + 'dict_value_get_num': 'READ', + 'buffer_read_num': 'READ', + 'num_to_i64_exact': 'READ', + 'finite_num_to_byte': 'READ', + 'buffer_read_byte': 'READ', # a buffer element as a byte (Q1: N5, no wrap) + 'buf_num_arg': 'READ', # R1.6z: required absent (bridge deleted) + 'buf_float_arg': 'READ', # R1.6d: decided float-only gain/value wrapper + 'eigs_value_as_num': 'READ', # embed API accessor + 'number': 'RAW', # lexer/AST EigsNum payload (#1758 R1.3) + # (c) producers + 'make_num': 'PRODUCE', + 'make_num_permanent': 'PRODUCE', + 'make_int': 'PRODUCE', # an int-only builtin must remain numeric after R1.6 + 'slot_from_f64': 'PRODUCE', # R1.1: was slot_from_num + 'slot_from_int': 'PRODUCE', # R1.1: the one int writer (no runtime caller before R1.3) + 'list_set_num': 'PRODUCE', + 'list_append_num': 'PRODUCE', + 'dict_value_set_num': 'PRODUCE', + 'list_rewrite_num': 'PRODUCE', + 'dict_value_rewrite_num': 'PRODUCE', + 'num_guard': 'GUARD', + 'num_guard_named': 'GUARD', + 'EIGS_NUM_MAX': 'GUARD', + 'strtod': 'PARSE', + 'atof': 'PARSE', + # (e) formatting + 'eigs_num_text': 'FORMAT', +} +# #1758 R1.6a: the final checked families coexist with the bridge until +# R1.6z. Future migrations must stay visible to the same inventory. +for family in ('number', 'float', 'int', 'byte', 'int_range', 'bounded', 'magnitude'): + required = 'eigs_int_arg_range' if family == 'int_range' else 'eigs_' + family + '_arg' + for accessor in (required, 'eigs_list_' + family, 'eigs_elem_' + family, + 'eigs_opt_' + family, 'eigs_slot_' + family, + 'eigs_slot_elem_' + family, 'eigs_slot_opt_' + family): + VOCAB[accessor] = 'READ' +for accessor in ('buffer_read_f64', 'buffer_byte_at', 'buffer_index_at', 'eigs_geom_shape'): + VOCAB[accessor] = 'READ' +for family in ('f64', 'int'): + for container in ('list', 'dict_value'): + VOCAB[container + '_get_' + family] = 'READ' + for operation in ('set', 'rewrite'): + VOCAB[container + '_' + operation + '_' + family] = 'PRODUCE' + VOCAB['list_append_' + family] = 'PRODUCE' +for producer in ('make_float', 'make_float_permanent'): + VOCAB[producer] = 'PRODUCE' + +# R1.6e retires the last wrapper only after its complete caller census. +# Keep the spelling in the population: absence is required and reintroduction +# is an error, rather than silently ceasing to recognize an old byte reader. +RETIRED_VOCAB = { + 'buffer_read_byte': 'R1.6e: gfx_fb and ppu_render_frame use buffer_byte_at', +} + +# R1.6z deletes the bridge. Retaining its vocabulary as REQUIRED ABSENT +# keeps a reintroduction visible while permitting the measured zero count. +for retired in ('eigs_num_arg', 'eigs_num_arg_slow', 'eigs_list_num', 'eigs_elem_num', + 'eigs_opt_num', 'eigs_slot_num', 'eigs_slot_elem_num', 'eigs_slot_opt_num', + 'buf_num_arg', 'make_num', 'make_num_permanent', 'list_set_num', + 'list_append_num', 'list_rewrite_num', 'dict_value_set_num', + 'dict_value_rewrite_num', 'list_get_num', 'dict_value_get_num', + 'finite_num_to_byte', 'buffer_read_byte', 'buffer_read_num'): + RETIRED_VOCAB.setdefault(retired, 'R1.6z: bridge deleted; every caller migrated') + +# JIT-only vocabulary (jit.c, jit.h, jit_smoke.c): the emitted guards test +# raw tag bits and never name a VOCAB token, so a token scan alone sees almost +# none of them. These are the tag-range immediates and the emitters that +# assume an operand or result is a double. Category (d). +JIT_FILES = re.compile(r'^(jit\.c|jit\.h|jit_smoke\.c)$') +JIT_TOKEN_RE = re.compile( + r'\b(0x[Ff]{3}[89A-Fa-f](?:0{12})?(?:ULL|ull)?|max_normal_bits|' + r'emit_immediate_num_check_r[a-z]{2}|emit_load_numeric_operand|emit_overflow_check_rax|' + r'emit_(?:add|sub|mul|div)sd_xmm0_xmm1|emit_ucomisd_\w+|emit_cvttsd2si_\w+|' + r'emit_cvtsi2sd_\w+|emit_btr_63_\w+|fmod)\b') + +# `data.number` is the AST literal's EigsNum (`data.f64_` is the Value member, +# reached only through VAL_F64_RAW); matched as a member access, below. +AST_NUM_FIELD = re.compile(r'(?:->|\.)data\.number\b(?!_)') + +KIND_CATEGORY = { + 'TEST': 'a', 'CASE': 'a', 'YIELD': 'a', 'DATA': 'a', + 'READ': 'b', 'RAW': 'b', + 'TAGSET': 'c', 'WRITE': 'c', 'PRODUCE': 'c', 'GUARD': 'c', 'PARSE': 'c', + 'EMIT': 'd', + 'FORMAT': 'e', +} + +CATEGORY_NAMES = { + 'a': 'type tests', + 'b': 'payload reads', + 'c': 'producers', + 'd': 'JIT emitters and guards', + 'e': 'number formatting', + 'f': 'equality, ordering and hashing', + 'g': 'tape encode and decode', + 'h': 'JSON, store and db encoders', + 'i': 'observer numeric paths', + 'j': 'embed API', +} + +# Functions that return/write a double from a payload read, reviewed: their +# call sites are NOT sites, because the read inside them already is one and +# changes with them. (Derived check: a new such function must be added to +# VOCAB or here, with a reason.) +WRAPPERS = { + 'compute_entropy_impl': 'observer: returns an entropy, not the number; its payload read is the site', + 'ent_child_slot': 'observer: returns a child entropy, not the number; its payload read is the site', + 'observer_entropy_now': 'observer: writes an entropy, not the number; its payload read is the site', + 'obs_slot_num': 'observer: the numeric channel of a slot (R1.1); its payload read is the site', + 'numerical_loss': 'tensor: reads the loss callback result once; that read is the site', + 'gradient_perturb': 'tensor: returns the normalized float actually stored by list_set_f64; list_get_f64 is the counted payload read', + 'tensor_flatten_recursive': 'tensor: bulk element flattener into a double array; the per-element read is the site', + 'tensor_to_flat': 'tensor: bulk element flattener into a double array; the per-element read is the site', +} + +# ---- role rules: (category, file regex, scope regex), first match wins ---- +# A scope is the enclosing function, `#define NAME`, ``, or for +# the two dispatchers `vm_run_ex/CASE(X)` and `jit_compile_to_thunk/OP_X`. +ROLE_RULES = [ + ('d', r'^(jit\.c|jit\.h|jit_smoke\.c)$', r'.*'), + ('j', r'^(eigs_embed\.[ch]|embed_smoke\.c|embed_concurrent\.c|embed_roads\.c)$', r'.*'), + ('g', r'^(trace\.[ch]|tape_read\.[ch]|eigsdap\.c|step\.c)$', r'.*'), + ('h', r'^(ext_store\.c|ext_store_internal\.h|ext_db\.c|ext_db_internal\.h)$', r'.*'), + ('h', r'.*', r'(^|_)json(_|$)|^eigs_json|^builtin_json'), + ('i', r'.*', r'observer|^obs_|_obs_|^ent_|entropy|^jit_sample_num|' + r'^builtin_(observe|classify|report|get_observer_thresholds|record_history|state_at)$|' + r'CASE\((OBSERVE|PREDICATE|REPORT|INTERROGATE|LOOP_STALL)'), + ('f', r'.*', r'^(values_equal_impl|values_equal|values_equal_op|const_equal|const_hash_value|' + r'cmp_[a-z_]*|[a-z_]*_cmp|builtin_(list_contains|list_index_of|sort|sort_by|secure_equals))$|' + r'CASE\((EQ|NE)\)|^#define NUM_CMP$'), + ('e', r'.*', r'^(eigs_num_text|value_to_string|chunk_disassemble|slot_type_name|' + r'builtin_(str|print|write|hex))$'), +] + +# Functions that dispatch on an opcode, and how an arm is spelled. Sites in +# them are attributed per arm (`fn/OP_X`), and executor_arms() below derives +# the executor matrix from the same arms. +DISPATCHERS = { + 'vm_run_ex': 'CASE', # the interpreter + 'jit_supported_prefix': 'IF', # the JIT scanner + 'jit_compile_to_thunk': 'IF', # the JIT emitter + 'chunk_scan_leaf_accessor': 'LABEL', # the leaf scanner (#366) + 'vm_leaf_accessor_exec': 'LABEL', # the leaf evaluator (#366) +} + +TOKEN_RE = re.compile(r'\b(' + '|'.join(sorted(VOCAB, key=len, reverse=True)) + r')\b') +# Raw union members: the Value's numeric payloads (`f64_`/`i64_`) and the slot's (`d_`). Only +# the accessor macros may name them; anywhere else is a bypass. +RAW_MEMBER = re.compile(r'(?:\.|->)(d_|f64_|i64_)\b') +RAW_MEMBER_HOMES = {'#define SLOT_F64_RAW', '#define VAL_F64_RAW', + '#define VAL_I64_RAW', '#define VAL_F64_OFFSET'} +C_KEYWORDS = {'if', 'while', 'for', 'switch', 'return', 'sizeof', 'do', 'else', + 'case', 'defined', '__attribute__', '__typeof__', '_Generic'} + + +def source_files(src): + """The files the inventory scans: the TRACKED *.c and *.h directly in + `src`, sorted. Inside a git work tree the list comes from `git ls-files`: + an ignored, generated header (src/lsp_stdlib_index.h, which `make lsp` + writes) is not a source, and a fresh clone does not have it -- scanning + it made the committed inventory say `files=65` on a box that had run + `make lsp` and fail `--check` everywhere else (CI, the R1.1 fix round). + Outside a work tree (a `git archive` export, or a calibration copy that + holds only tracked files) every file present is tracked by construction. + A file listed but missing from disk is skipped (a deletion not yet + staged).""" + git = ['git', '-c', 'safe.directory=*', '-C', src] + inside = subprocess.run(git + ['rev-parse', '--is-inside-work-tree'], capture_output=True) + if inside.returncode == 0 and inside.stdout.strip() == b'true': + ls = subprocess.run(git + ['ls-files', '-z', '--', '*.c', '*.h'], capture_output=True) + if ls.returncode != 0: + raise SystemExit('phase2-inventory: FAIL: git ls-files in %s: %s' + % (src, ls.stderr.decode('utf-8', 'replace').strip())) + # An in-progress merge can list one path for each unmerged index + # stage. The population is source paths, not index entries. + names = {n for n in ls.stdout.decode('utf-8').split('\0') if n and '/' not in n} + return sorted(os.path.join(src, n) for n in names if os.path.isfile(os.path.join(src, n))) + return sorted(glob.glob(os.path.join(src, '*.c')) + glob.glob(os.path.join(src, '*.h'))) + + +def strip_c(text, keep_strings=False): + """Blank comments and (unless keep_strings) string/char literals, keeping + every newline (so line numbers survive) and every other column.""" + out = [] + i, n = 0, len(text) + state = 'code' + while i < n: + c = text[i] + nxt = text[i + 1] if i + 1 < n else '' + if state == 'code': + if c == '/' and nxt == '/': + state = 'line'; out.append(' '); i += 2; continue + if c == '/' and nxt == '*': + state = 'block'; out.append(' '); i += 2; continue + if c == '"': + state = 'str'; out.append('"'); i += 1; continue + if c == "'": + state = 'chr'; out.append("'"); i += 1; continue + out.append(c); i += 1; continue + if state == 'line': + if c == '\n': + state = 'code'; out.append('\n') + else: + out.append('\\' if c == '\\' and nxt == '\n' else ' ') + i += 1; continue + if state == 'block': + if c == '*' and nxt == '/': + state = 'code'; out.append(' '); i += 2; continue + # keep a line-continuation backslash: a comment inside a + # multi-line #define must not end the macro early + out.append(c if c in '\n\\' and (c == '\n' or nxt == '\n') else ' '); i += 1; continue + # string or char literal + quote = '"' if state == 'str' else "'" + if c == '\\' and i + 1 < n: + if keep_strings or text[i + 1] == '\n': + out.append(text[i:i + 2]) + else: + out.append(' ') + i += 2; continue + if c == quote: + state = 'code'; out.append(quote); i += 1; continue + out.append(c if keep_strings or c == '\n' else ' '); i += 1 + return ''.join(out) + + +def scopes_for(stripped, fname, sigs=None): + """Return a list: scope name per line (1-based index; [0] unused). + `sigs`, if given, collects each function's signature text.""" + lines = stripped.split('\n') + scope = [''] * (len(lines) + 1) + depth = 0 + sig = '' # text since the last top-level ; or } (signature) + cur = '' + sub = None # dispatcher sub-scope (CASE(X) / OP_X) + arm_depth = None # brace depth of a LABEL dispatcher's arms + prev_label = False + in_macro = None + for ln, line in enumerate(lines, 1): + # #define bodies (with continuations) are their own scope + if in_macro is not None: + scope[ln] = in_macro + if not line.rstrip().endswith('\\'): + in_macro = None + continue + m = re.match(r'\s*#\s*define\s+([A-Za-z_]\w*)', line) + if m: + scope[ln] = '#define ' + m.group(1) + if line.rstrip().endswith('\\'): + in_macro = '#define ' + m.group(1) + continue + if re.match(r'\s*#', line): + scope[ln] = cur if depth > 0 else '' + continue + if depth > 0 and cur in DISPATCHERS: + style = DISPATCHERS[cur] + if style == 'CASE': + mc = re.match(r'\s*CASE\((\w+)\)', line) + if mc: + sub = 'CASE(%s)' % mc.group(1) + elif style == 'IF': + mo = re.search(r'\b(?:if|else if)\s*\(\s*op\s*==\s*(OP_\w+)', line) + if mo and depth <= 3: + sub = mo.group(1) + else: # LABEL: the outermost `case OP_X:` labels of the dispatch switch + labels = re.findall(r'\bcase\s+(OP_\w+)\s*:', line) + if labels and arm_depth is None: + arm_depth = depth + if labels and depth == arm_depth: + # consecutive label lines name one grouped arm + sub = (sub + '|' if prev_label and sub else '') + '|'.join(labels) + # a line holding only labels continues into the next label line + prev_label = line.rstrip().endswith(':') + elif line.strip(): + prev_label = False + line_scope = cur if depth > 0 else '' + if depth > 0 and sub and cur in DISPATCHERS: + line_scope = cur + '/' + sub + for ch in line: + if ch == '{': + if depth == 0: + cur = function_name(sig) + if sigs is not None and not cur.startswith('<'): + sigs.setdefault(cur, sig) + sub = None + arm_depth = None + line_scope = cur + depth += 1 + elif ch == '}': + depth -= 1 + if depth == 0: + sig = '' + cur = '' + sub = None + elif ch == ';' and depth == 0: + sig = '' + elif depth == 0: + sig += ch + if depth == 0 and line.strip() and not line.strip().startswith('}'): + sig += '\n' + scope[ln] = line_scope + return scope + + +def function_name(sig): + """The identifier before the last top-level parenthesized group of a + definition's signature; '' for struct/enum/array bodies.""" + s = re.sub(r'__attribute__\s*\(\(.*?\)\)', ' ', sig, flags=re.S) + if '(' not in s or '=' in s.split('(')[0]: + return '' + # find the last balanced (...) group at nesting level 0 + depth, last_open = 0, -1 + for idx, ch in enumerate(s): + if ch == '(': + if depth == 0: + last_open = idx + depth += 1 + elif ch == ')': + depth -= 1 + if last_open < 0: + return '' + head = s[:last_open] + ids = re.findall(r'[A-Za-z_]\w*', head) + ids = [i for i in ids if i not in C_KEYWORDS] + return ids[-1] if ids else '' + + +def val_num_kind(line, start, end, token): + """Syntactic role of a VAL_FLOAT / AST_NUM occurrence.""" + before = line[:start].rstrip() + after = line[end:].lstrip() + if re.search(r'(==|!=)\s*$', before) or re.match(r'(==|!=)', after): + return 'TEST' + if re.search(r'\bcase\s*$', before): + return 'CASE' + if re.search(r'(?])=\s*$', before): + return 'TAGSET' + if re.search(r'\breturn\s*$', before) or re.search(r'[?:]\s*$', before): + return 'YIELD' + if re.search(r'\(\s*uint32_t\s*\)\s*$', before): + return 'EMIT' # JIT immediate operand of a type compare + if (re.search(r'[({,]\s*$', before) or not before) and re.match(r'[)},]', after): + return 'DATA' # the tag as data: enumerator, kind table, argument + return 'UNKNOWN' + + +def raw_kind(line, end): + """VAL_F64_RAW(...)/SLOT_F64_RAW(...) followed by a plain `=` is a write.""" + depth, i = 0, end + while i < len(line): + ch = line[i] + if ch == '(': + depth += 1 + elif ch == ')': + depth -= 1 + if depth == 0: + rest = line[i + 1:].lstrip() + if re.match(r'=(?!=)', rest): + return 'WRITE' + return 'RAW' + elif depth == 0 and not ch.isspace(): + return 'RAW' + i += 1 + return 'RAW' + + +def scan_file(path, rel): + with open(path, encoding='utf-8', errors='replace') as f: + text = f.read() + stripped = strip_c(text) + sigs = {} + scope = scopes_for(stripped, rel, sigs) + sites = [] + for ln, line in enumerate(stripped.split('\n'), 1): + for m in TOKEN_RE.finditer(line): + tok = m.group(1) + kind = VOCAB[tok] + if kind == 'TAG': + kind = val_num_kind(line, m.start(), m.end(), tok) + elif tok in ('VAL_F64_RAW', 'VAL_I64_RAW', 'SLOT_F64_RAW'): + kind = raw_kind(line, m.end()) + sites.append((rel, ln, scope[ln], tok, kind)) + if JIT_FILES.match(rel): + for m in JIT_TOKEN_RE.finditer(line): + tok = m.group(1) + tok = 'TAG-IMM ' + tok[:6] if tok.lower().startswith('0xfff') else tok + sites.append((rel, ln, scope[ln], tok, 'EMIT')) + for m in RAW_MEMBER.finditer(line): + kind = 'RAW' if scope[ln] in RAW_MEMBER_HOMES else 'BYPASS' + sites.append((rel, ln, scope[ln], m.group(1), kind)) + for m in AST_NUM_FIELD.finditer(line): + rest = line[m.end():].lstrip() + kind = 'WRITE' if re.match(r'=(?!=)', rest) else 'RAW' + sites.append((rel, ln, scope[ln], 'data.number', kind)) + return sites, stripped, scope, sigs + + +def classify(site): + rel, ln, scope, tok, kind = site + if kind in ('UNKNOWN', 'BYPASS'): + return None, None + base = scope.split('/')[-1] if '/' in scope else scope + for idx, (cat, frx, srx) in enumerate(ROLE_RULES): + if re.search(frx, rel) and (re.search(srx, scope) or re.search(srx, base)): + return cat, idx + return KIND_CATEGORY[kind], None + + +# ---- derived accessor check ---------------------------------------------- +# Derive the checked-read vocabulary from the same registry as the sites; +# a double-returning wrapper around a final reader must not disappear. +READ_IN_BODY = re.compile( + r'\b(' + '|'.join(sorted({name for name, kind in VOCAB.items() if kind == 'READ'} | + {'VAL_F64_RAW', 'VAL_I64_RAW', 'SLOT_F64_RAW'})) + r')\b|' + r'(?:\.|->)(?:d_|f64_|i64_)\b') + + +def derived_accessors(stripped_by_file, scope_by_file, sigs_by_file): + """Functions whose signature returns `double` (or has a `double *` out + parameter) and whose body performs a payload read.""" + found = {} + for rel, stripped in stripped_by_file.items(): + sigs = sigs_by_file[rel] + lines = stripped.split('\n') + scope = scope_by_file[rel] + bodies = {} + for ln, line in enumerate(lines, 1): + s = scope[ln] + if s.startswith('<') or s.startswith('#define') or '/' in s: + continue + bodies.setdefault(s, []).append(line) + for fn, blines in bodies.items(): + body = '\n'.join(blines) + if not READ_IN_BODY.search(body): + continue + sigtext = sigs.get(fn) + if not sigtext or fn not in sigtext: + continue + cut = sigtext.rindex(fn) + ret, params = sigtext[:cut], sigtext[cut + len(fn):] + if re.search(r'\bdouble\b', ret) or re.search(r'\bdouble\s*\*', params): + found.setdefault(fn, rel) + return found + + +# ---- builtins ------------------------------------------------------------ +REG_DIRECT = re.compile(r'env_set_local_owned\(\s*\w+\s*,\s*"([A-Za-z_]\w*)"\s*,\s*' + r'make_builtin\(\s*(\w+)\s*\)\s*\)') +REG_XLIST = re.compile(r'\bX\(\s*([A-Za-z_]\w*)\s*,\s*(\w+)\s*\)') + + +def registered_builtins(src): + regs = {} + for path in sorted(glob.glob(os.path.join(src, '*.c'))): + with open(path, encoding='utf-8', errors='replace') as f: + text = strip_c(f.read(), keep_strings=True) + for m in REG_DIRECT.finditer(text): + regs.setdefault(m.group(1), m.group(2)) + names_h = os.path.join(src, 'ext_names.h') + if os.path.exists(names_h): + with open(names_h, encoding='utf-8', errors='replace') as f: + text = strip_c(f.read()) + for m in REG_XLIST.finditer(text): + if m.group(1) != 'name': + regs.setdefault(m.group(1), m.group(2)) + return regs + + +def function_bodies(stripped_by_file, scope_by_file): + """fn -> (file, body text), for every top-level function definition.""" + out = {} + for rel, stripped in stripped_by_file.items(): + lines = stripped.split('\n') + scope = scope_by_file[rel] + for ln, line in enumerate(lines, 1): + s = scope[ln].split('/')[0] + if s.startswith('<') or s.startswith('#define'): + continue + f_rel, buf = out.setdefault(s, (rel, [])) + if f_rel == rel: + buf.append(line) + return {k: (v[0], '\n'.join(v[1])) for k, v in out.items()} + + +# Cross-file functions whose numeric semantics a builtin inherits by calling +# them: number text (N6) and equality (N2). Followed from any file. +SEMANTIC_LEAVES = {'value_to_string': 'eigs_num_text', 'eigs_num_text': 'eigs_num_text', + 'values_equal': 'values_equal', 'values_equal_op': 'values_equal'} + + +def numeric_builtins(regs, sites, bodies): + sites_by_fn = {} + for (rel, ln, scope, tok, kind) in sites: + fn = scope.split('/')[0] + sites_by_fn.setdefault(fn, []).append(tok) + # a callee is any same-file function the body names: a call, or a + # reference passed as a value (a comparator or an op table entry) + ident = re.compile(r'\b([A-Za-z_]\w*)\b') + result = {} + for name, fn in regs.items(): + seen, frontier, toks = {fn}, [fn], [] + depth = 0 + while frontier and depth <= 4: + nxt = [] + for f in frontier: + toks.extend(sites_by_fn.get(f, [])) + if f not in bodies: + continue + f_rel, body = bodies[f] + for callee in ident.findall(body): + if callee in SEMANTIC_LEAVES: + toks.append(SEMANTIC_LEAVES[callee]) + if callee in seen or callee not in bodies: + continue + if bodies[callee][0] != f_rel and not callee.startswith('builtin_'): + continue # same-file helpers only (closure) + seen.add(callee) + nxt.append(callee) + frontier = nxt + depth += 1 + if toks: + result[name] = (fn, sorted(set(toks))) + return result + + +# ---- opcode executors, derived from the DISPATCHERS' arms ------------------ +ROLE_OF = {'vm_run_ex': 'interp', 'jit_supported_prefix': 'jit_scan', + 'jit_compile_to_thunk': 'jit_emit', 'chunk_scan_leaf_accessor': 'leaf_scan', + 'vm_leaf_accessor_exec': 'leaf_exec'} +ROLES = ['interp', 'jit_scan', 'jit_emit', 'leaf_scan', 'leaf_exec'] + + +def executor_arms(stripped_by_file, scope_by_file): + """opcode -> set of roles that have an arm for it; and each dispatcher + arm's opcode chain, keyed by its scope (`fn/sub`).""" + arms, chains = {}, {} + # A named case that only returns from the void scanner is an explicit + # refusal, not an admitted opcode. Derive this narrow shape from its body: + # loops now have named refusal arms after operand widths moved to the + # shared table (#1425/#1906). Any nontrivial arm stays in the comparison, + # so an unreviewed implementation cannot disappear behind a broad filter. + refused_leaf_ops = set() + for rel, stripped in stripped_by_file.items(): + scope = scope_by_file[rel] + bodies = {} + before_default = True + for ln, line in enumerate(stripped.split('\n'), 1): + fn, _, sub = scope[ln].partition('/') + if fn != 'chunk_scan_leaf_accessor': + continue + if re.match(r'\s*default\s*:', line): + before_default = False + if sub and before_default: + bodies.setdefault(sub, []).append(line) + for sub, lines in bodies.items(): + body = re.sub(r'\bcase\s+OP_\w+\s*:', '', '\n'.join(lines)) + if body.strip() == 'return;': + refused_leaf_ops.update(sub.split('|')) + # a macro whose body spells CASE() generates an + # interpreter arm at each invocation (NUM_BINOP(SUB, ...), NUM_CMP(LT, ...)) + defs = [] + for stripped in stripped_by_file.values(): + for m in re.finditer(r'#\s*define\s+(\w+)\(\s*(\w+)[^)]*\)((?:[^\n]*\\\n)*[^\n]*)', stripped): + defs.append((m.group(1), m.group(2), m.group(3))) + case_macros, grew = set(), True + while grew: # closed over chains: INT_BINOP -> INT_BINOP_R -> CASE(NAME) + grew = False + for name, p1, body in defs: + if name in case_macros: + continue + calls = ['CASE'] + sorted(case_macros) + if re.search(r'\b(%s)\(\s*%s\b' % ('|'.join(calls), re.escape(p1)), body): + case_macros.add(name); grew = True + gen = re.compile(r'^\s*(%s)\(\s*(\w+)' % '|'.join(sorted(case_macros))) if case_macros else None + for rel, stripped in stripped_by_file.items(): + scope = scope_by_file[rel] + for ln, line in enumerate(stripped.split('\n'), 1): + fn, _, sub = scope[ln].partition('/') + if gen and fn == 'vm_run_ex': + mg = gen.match(line) + if mg: + arms.setdefault('OP_' + mg.group(2), set()).add('interp') + if fn not in DISPATCHERS: + continue + if fn == 'chunk_scan_leaf_accessor' and sub and set(sub.split('|')) <= refused_leaf_ops: + continue + if DISPATCHERS[fn] == 'IF': + ops = re.findall(r'\bop\s*==\s*(OP_\w+)', line) + elif sub.startswith('CASE('): + ops = ['OP_' + sub[5:-1]] + else: + ops = [o for o in sub.split('|') if o] + for op in ops: + arms.setdefault(op, set()).add(ROLE_OF[fn]) + if sub and ops: + chains.setdefault(fn + '/' + sub, set()).update(ops) + return arms, chains + + +# ---- slot classifiers (R1.1's worklist) ------------------------------------ +# Every function (or dispatcher arm) that tests, dereferences or types an +# EigsSlot. D1 holds a number test (R1.1 renamed or deleted every one, so the +# compiler stopped at each); D2 dereferences with no number test; D3 tests +# other tags or bits; T only reads slot_type. From R1.1 on, every D1, D2 and +# D3 entry needs a reviewed disposition in the design doc: what an int (an +# SMI) does there. +SLOT_TEST = {'slot_is_f64', 'slot_is_smi', 'slot_f64_value', 'slot_int_value', 'slot_smi_val'} +SLOT_OTHER = {'slot_is_null', 'slot_is_bool', 'slot_is_heap', 'slot_is_ptr'} +SLOT_DEREF = {'slot_as_ptr', 'slot_as_heap', 'eigs_slot_ref'} +SLOT_TYPE = {'slot_type', 'list_elem_type', 'dict_value_type'} +SLOT_BITS = {'SLOT_QNAN_MASK', 'SLOT_TAG_MASK', 'SLOT_PAYLOAD_MASK', 'TAG_HEAP', 'TAG_BOOL', 'TAG_SMI', + 'SLOT_SMI_MIN', 'SLOT_SMI_MAX', 'SLOT_SMI_SIGN', + 'TAG_SENTINEL', 'SLOT_NULL_BITS', 'SLOT_TRUE_BITS', 'SLOT_FALSE_BITS'} +SLOT_RE = re.compile(r'\b(' + '|'.join(sorted(SLOT_TEST | SLOT_OTHER | SLOT_DEREF | SLOT_TYPE | SLOT_BITS, + key=len, reverse=True)) + r')\b') +# `unwired` (R1.1): aborts on an SMI through eigs_int_unwired, whose arm needs +# the int Value kind or tape v7; R1.3 deletes eigs_int_unwired, so each one +# stops compiling in the commit that makes ints reachable. +DISPOSITIONS = {'guarded', 'smi-arm', 'refuse', 'unwired', 'not-a-classifier'} + + +def slot_classifiers(stripped_by_file, scope_by_file): + toks = {} + for rel, stripped in stripped_by_file.items(): + scope = scope_by_file[rel] + for ln, line in enumerate(stripped.split('\n'), 1): + for m in SLOT_RE.finditer(line): + k = scope[ln] + if k.split('/')[0] not in DISPATCHERS: + k = k.split('/')[0] + if k.startswith('#define ') and k[8:] in SLOT_BITS: + continue # the constant's own definition + toks.setdefault((rel, k), {}).setdefault(m.group(1), 0) + toks[(rel, k)][m.group(1)] += 1 + out = {} + for key, c in toks.items(): + t = set(c) + cls = ('D1' if t & SLOT_TEST else 'D2' if t & SLOT_DEREF else + 'D3' if t & (SLOT_OTHER | SLOT_BITS) else 'T') + out[key] = (cls, c) + return out + + +def slot_population(slots, sigs_by_file): + """The C slot test's population (design 2.5): every D1/D2/D3 FUNCTION + entry whose first parameter is an EigsSlot by value. #define entries and + dispatcher arms take no slot argument. tests/phase2/slot_layer.sh + compares this list with the entries the test examined, both ways.""" + out, bad = [], [] + for (rel, k), (cls, c) in sorted(slots.items()): + if cls not in ('D1', 'D2', 'D3') or k.startswith('#define') or '/' in k: + continue + sig = sigs_by_file[rel].get(k) + if not sig or k not in sig: + bad.append('%s:%s has no signature' % (rel, k)) + continue + params = sig[sig.rindex(k) + len(k):].strip() + if not params.startswith('('): + bad.append('%s:%s: unparsed signature %r' % (rel, k, sig.strip()[:60])) + continue + first = re.split(r'[,)]', params[1:], maxsplit=1)[0].strip() + if re.match(r'^(const\s+)?EigsSlot\s+[A-Za-z_]\w*$', first): + out.append('%s:%s' % (rel, k)) + for line in out: + print(line) + for b in bad: + print('slot-population: FAIL: ' + b) + print('slot-population: entries=%d' % len(out)) + return 1 if bad or not out else 0 + + +def read_marked_table(doc, tag, row_re): + """Rows between and ; an unparsed + line inside the markers is refused, never skipped.""" + rows, bad = {}, [] + if not os.path.exists(doc): + return None, ['%s missing' % doc] + inside = False + with open(doc, encoding='utf-8') as f: + for ln, line in enumerate(f, 1): + if line.startswith('' % tag): + inside = True; continue + if line.startswith('' % tag): + inside = False; continue + if not inside or not line.strip(): + continue + m = row_re.match(line) + if not m: + if not re.match(r'^\|\s*(name|builtin|-+)\s*\|', line.strip()): + bad.append('%s:%d: unparsed %s line: %s' % (doc, ln, tag, line.strip()[:60])) + continue + if m.group(1) in rows: + bad.append('%s:%d: `%s` has two %s rows' % (doc, ln, m.group(1), tag)) + rows[m.group(1)] = (ln,) + m.groups()[1:] + return rows, bad + + +SLOT_ROW = re.compile(r'^\|\s*`([^`]+)`\s*\|\s*([a-z-]+)\s*\|') + + +TABLE_ROW = re.compile(r'^\|\s*`([A-Za-z_]\w*)`\s*\|\s*([a-z-]+)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|(.*)\|\s*$') +CLASSES = {'int-preserving', 'float-producing', 'int-requiring', 'other'} +LOSS_MARKS = {'LOSS', '-'} +# Q7 (decided, #1758 comment 6174302316): the class of each continuous +# parameter. A row names the classes it has, in this order, or `-`. +Q7_CLASSES = ['geometry', 'bounded', 'magnitude'] +# §6 defines LOSS as the original tree's risk, not today's remaining double +# paths. Generated from the approved R1.6 bar import's §6 LOSS column: +# 0a73adb0612f8612d75393891ae2ba2e1c803663:docs/design/phase2-numbers.md +# blob b2f583afd653e845887045795077823077388442 +# R1.6b-D3 (#1758 comment 6320170738) adds http_post: the original +# header decoder rounded integers; the approved loss mark omitted it. +# Keep this fixed historical scope in the script so git-archive calibrations +# remain self-contained. Added AND removed marks require an explicit review. +HISTORICAL_LOSS = frozenset(''' +abs add ceil db_execute db_query_json db_query_value floor hex http_post json_build +json_decode json_encode json_path max min monotonic_ns multiply negative num +num_copy random_int round scan_int_tokens scan_ints seed_random shared_incr +sign_extend sort sort_by store_delete store_get store_put store_query +store_update subtract sum task_sched_seed +'''.split()) + + +def read_table(doc): + rows, bad = {}, [] + if not os.path.exists(doc): + return None, ['classification table %s missing' % doc] + with open(doc, encoding='utf-8') as f: + in_table = False + for ln, line in enumerate(f, 1): + if line.startswith(''): + in_table = True; continue + if line.startswith(''): + in_table = False; continue + if not in_table: + continue + m = TABLE_ROW.match(line) + if not m: + # inside the markers only the header and separator may fail + # to parse; anything else is refused, never skipped + if line.strip() and not re.match(r'^\|\s*(builtin|-+)\s*\|', line.strip()): + bad.append('%s:%d: unparsed table line: %s' % (doc, ln, line.strip()[:60])) + continue + name, cls, loss, q7, note = m.group(1), m.group(2), m.group(3), m.group(4), m.group(5) + if cls not in CLASSES: + bad.append('%s:%d: `%s` has unknown class %r' % (doc, ln, name, cls)) + if loss not in LOSS_MARKS: + bad.append('%s:%d: `%s` has unknown loss mark %r' % (doc, ln, name, loss)) + q7s = [] if q7 == '-' else q7.split(',') + if q7s != [c for c in Q7_CLASSES if c in q7s] or len(set(q7s)) != len(q7s): + bad.append('%s:%d: `%s` has a bad Q7 column %r (want `-` or an ordered subset of %s)' + % (doc, ln, name, q7, ','.join(Q7_CLASSES))) + # the column and the note make the same claim, both ways + if bool(q7s) != ('continuous' in note): + bad.append('%s:%d: `%s`: Q7 column %r disagrees with its note (a continuous ' + 'parameter must be named in both)' % (doc, ln, name, q7)) + if name in rows: + bad.append('%s:%d: `%s` has two rows' % (doc, ln, name)) + rows[name] = (cls, loss, q7) + return rows, bad + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('--src', default=os.path.join(ROOT, 'src')) + ap.add_argument('--doc', default=os.path.join(ROOT, 'docs/design/phase2-numbers.md')) + ap.add_argument('--out', default=os.path.join(ROOT, 'docs/design/phase2-inventory.txt')) + ap.add_argument('--no-write', action='store_true') + ap.add_argument('--check', action='store_true', + help='write nothing, and FAIL when --out differs from the generated text (#1804)') + ap.add_argument('--sites', action='store_true', help='print every site') + ap.add_argument('--slot-population', action='store_true', + help='print the C slot test population (design 2.5) and exit') + args = ap.parse_args() + + files = source_files(args.src) + sites, stripped_by_file, scope_by_file, sigs_by_file = [], {}, {}, {} + for path in files: + rel = os.path.basename(path) + s, stripped, scope, sigs = scan_file(path, rel) + sites.extend(s) + stripped_by_file[rel] = stripped + scope_by_file[rel] = scope + sigs_by_file[rel] = sigs + + examined = len(sites) + classified, unclassified = [], [] + rule_hits = [0] * len(ROLE_RULES) + token_hits = {t: 0 for t in VOCAB} + token_hits.update({'d_': 0, 'f64_': 0, 'i64_': 0}) + for site in sites: + cat, rule = classify(site) + if site[3] in token_hits: + token_hits[site[3]] += 1 + if cat is None: + why = ('raw union member outside its accessor macro' if site[4] == 'BYPASS' + else 'VAL_FLOAT/VAL_INT/AST_NUM in an unrecognized position') + unclassified.append((site, why)) + continue + if rule is not None: + rule_hits[rule] += 1 + classified.append((site, cat)) + + # derived: numeric accessors must be reviewed + acc = derived_accessors(stripped_by_file, scope_by_file, sigs_by_file) + unreviewed = sorted(fn for fn in acc if fn not in VOCAB and fn not in WRAPPERS) + for fn in unreviewed: + unclassified.append(((acc[fn], 0, fn, fn, 'ACCESSOR'), + 'reads a payload and returns/writes a double; not in VOCAB or WRAPPERS')) + stale_wrappers = sorted(w for w in WRAPPERS if w not in acc) + dead_tokens = sorted(t for t, c in token_hits.items() if c == 0 and t not in RETIRED_VOCAB) + dead_rules = [i for i, c in enumerate(rule_hits) if c == 0] + + # builtins + regs = registered_builtins(args.src) + bodies = function_bodies(stripped_by_file, scope_by_file) + nb = numeric_builtins(regs, sites, bodies) + table, table_bad = read_table(args.doc) + b_unclassified = sorted(n for n in nb if table is None or n not in table) + b_stale = sorted(n for n in (table or {}) if n not in nb) + loss_rows = sorted(n for n, (c, l, q) in (table or {}).items() if l == 'LOSS') + for n in sorted(HISTORICAL_LOSS - set(loss_rows)): + table_bad.append('`%s` is missing its historical LOSS mark' % n) + for n in sorted(set(loss_rows) - HISTORICAL_LOSS): + table_bad.append('`%s` is marked LOSS but is not in the historical LOSS scope' % n) + class_count = {} + q7_count = {} + for n, (c, l, q) in (table or {}).items(): + class_count[c] = class_count.get(c, 0) + 1 + for k in ([] if q == '-' else q.split(',')): + q7_count[k] = q7_count.get(k, 0) + 1 + + # executors: the leaf scanner and evaluator must name the same opcodes + arms, chains = executor_arms(stripped_by_file, scope_by_file) + leaf_scan = {o for o, r in arms.items() if 'leaf_scan' in r} + leaf_exec = {o for o, r in arms.items() if 'leaf_exec' in r} + exec_bad = ['leaf scanner admits %s but the leaf evaluator has no arm' % o + for o in sorted(leaf_scan - leaf_exec)] + exec_bad += ['leaf evaluator has an arm for %s the leaf scanner never admits' % o + for o in sorted(leaf_exec - leaf_scan)] + found = {r for rs in arms.values() for r in rs} + exec_bad += ['dispatcher %s (%s) attributed no opcode arm' % (f, r) + for f, r in sorted(ROLE_OF.items()) if r not in found] + + # slot classifiers: every D1/D2/D3 entry has a reviewed disposition row + slots = slot_classifiers(stripped_by_file, scope_by_file) + slot_cls = {} + for key, (cls, c) in slots.items(): + slot_cls[cls] = slot_cls.get(cls, 0) + 1 + need = {'%s:%s' % k for k, (cls, c) in slots.items() if cls in ('D1', 'D2', 'D3')} + srows, sbad = read_marked_table(args.doc, 'slot-table', SLOT_ROW) + srows = srows or {} + for name, (ln, disp) in srows.items(): + if disp not in DISPOSITIONS: + sbad.append('%s:%d: `%s` has unknown disposition %r' % (args.doc, ln, name, disp)) + slot_missing = sorted(need - set(srows)) + slot_stale = sorted(set(srows) - need) + if not any(slot_cls.get(k) for k in ('D1', 'D2', 'D3')): + sbad.append('slot classifiers: examined=0 (no D1/D2/D3 entry found)') + + if args.slot_population: + return slot_population(slots, sigs_by_file) + + # ---- report ---- + cats = 'abcdefghij' + per_cat = {c: 0 for c in cats} + per_file = {} + per_cat_file = {} + per_kind = {} + for (rel, ln, scope, tok, kind), cat in classified: + per_cat[cat] += 1 + per_file[rel] = per_file.get(rel, 0) + 1 + per_cat_file.setdefault(cat, {}).setdefault(rel, 0) + per_cat_file[cat][rel] += 1 + per_kind.setdefault(cat, {}).setdefault(kind, 0) + per_kind[cat][kind] += 1 + + out = [] + out.append('# docs/design/phase2-inventory.txt -- generated by tools/phase2_inventory.py; do not edit.') + out.append('# Population: VOCAB token occurrences in src/*.c src/*.h, comments and literals stripped.') + out.append('examined=%d classified=%d unclassified=%d' % (examined + len(unreviewed), + len(classified), len(unclassified))) + out.append('files=%d tokens=%d role_rules=%d derived_accessors=%d wrappers=%d' % ( + len(files), len(VOCAB), len(ROLE_RULES), len(acc), len(WRAPPERS))) + out.append('') + for token, reason in sorted(RETIRED_VOCAB.items()): + out.append('# Retired (required absent): %s -- %s' % (token, reason)) + out.append('## Per category') + for c in cats: + kinds = ' '.join('%s=%d' % (k, v) for k, v in sorted(per_kind.get(c, {}).items())) + out.append('(%s) %-34s %5d %s' % (c, CATEGORY_NAMES[c], per_cat[c], kinds)) + out.append('') + out.append('## Per token') + for t in sorted(token_hits, key=lambda t: (-token_hits[t], t)): + out.append('%-24s %5d' % (t, token_hits[t])) + out.append('%-24s %5d' % ('data.number (AST)', sum(1 for s in sites if s[3] == 'data.number'))) + out.append('') + out.append('## Per file (category counts)') + for rel in sorted(per_file, key=lambda r: (-per_file[r], r)): + parts = ' '.join('%s=%d' % (c, per_cat_file.get(c, {}).get(rel, 0)) for c in cats + if per_cat_file.get(c, {}).get(rel, 0)) + out.append('%-24s %5d %s' % (rel, per_file[rel], parts)) + out.append('') + out.append('## Executors (arms per opcode; roles: %s)' % ' '.join(ROLES)) + interesting = sorted(o for o, r in arms.items() if r - {'interp'}) + for o in interesting: + out.append('%-24s %s' % (o, ' '.join(r if r in arms[o] else '-' for r in ROLES))) + out.append('') + # the MAP's L/F/O rows must exercise every JIT-emitter and leaf arm that + # holds a numeric site (tests/phase2/MAP.md `ops:`; design §9.2) + num_arms = sorted({sc.split(' ')[0] for (r_, l_, sc, t_, k_), c_ in classified + if sc.split('/')[0] in ('jit_compile_to_thunk', 'vm_leaf_accessor_exec') + and '/' in sc.split(' ')[0]}) + out.append('## Numeric arms (JIT emitter and leaf evaluator arms holding a numeric site)') + for sc in num_arms: + out.append('%-9s %-24s %s' % (ROLE_OF[sc.split('/')[0]], sc.split('/', 1)[1], + ' '.join(sorted(chains.get(sc, ()))))) + out.append('') + out.append('## Slot classifiers (R1.1 worklist)') + out.append(' '.join('%s=%d' % (k, slot_cls.get(k, 0)) for k in ('D1', 'D2', 'D3', 'T')) + + ' dispositions=%d missing=%d stale=%d' % (len(srows), len(slot_missing), len(slot_stale))) + for key in sorted(slots, key=lambda k: (slots[k][0], k)): + cls, c = slots[key] + if cls in ('D1', 'D2', 'D3'): + disp = srows.get('%s:%s' % key, (0, '-'))[1] + out.append('%s %-42s %-16s %s' % (cls, '%s:%s' % key, disp, + ' '.join('%s=%d' % kv for kv in sorted(c.items())))) + out.append('') + out.append('## Builtins') + out.append('registered=%d numeric=%d classified=%d unclassified=%d stale=%d' % ( + len(regs), len(nb), len(nb) - len(b_unclassified), len(b_unclassified), len(b_stale))) + out.append('classes: ' + ' '.join('%s=%d' % (c, class_count.get(c, 0)) for c in sorted(CLASSES))) + out.append('LOSS (%d): %s' % (len(loss_rows), ' '.join(loss_rows))) + out.append('Q7: ' + ' '.join('%s=%d' % (c, q7_count.get(c, 0)) for c in Q7_CLASSES)) + for name in sorted(nb): + fn, toks = nb[name] + cls, loss, q7 = (table or {}).get(name, ('-', '-', '-')) + out.append('%-28s %-34s %-16s %-5s %-24s %s' % (name, fn, cls, loss, q7, ','.join(toks))) + out.append('') + out.append('## Sites (file:line scope token kind category)') + for (rel, ln, scope, tok, kind), cat in sorted(classified, key=lambda x: (x[0][0], x[0][1], x[0][3])): + out.append('%s:%d %s %s %s %s' % (rel, ln, scope, tok, kind, cat)) + text = '\n'.join(out) + '\n' + + stale = None + if args.check: + # the committed file must be exactly what this run generates + try: + with open(args.out, encoding='utf-8') as f: + committed = f.read() + except OSError: + committed = '' + if committed != text: + stale = sum(1 for l in difflib.unified_diff(committed.split('\n'), text.split('\n'), n=0, lineterm='') + if l[:1] in '+-' and not l.startswith(('+++', '---'))) + elif not args.no_write: + os.makedirs(os.path.dirname(args.out), exist_ok=True) + with open(args.out, 'w', encoding='utf-8') as f: + f.write(text) + + # stdout: the summary (not the site list unless --sites) + summary_end = text.index('## Builtins') + sys.stdout.write(text[:summary_end]) + if args.sites: + sys.stdout.write(text[summary_end:]) + fail = [] + for (site, why) in unclassified: + rel, ln, scope, tok, kind = site + print('UNCLASSIFIED: %s:%d %s %s (%s)' % (rel, ln, scope, tok, why)) + for w in stale_wrappers: + fail.append('stale WRAPPERS row: %s is no longer a payload-reading double accessor' % w) + for t in dead_tokens: + fail.append('VOCAB token never occurs: %s' % t) + for token, reason in RETIRED_VOCAB.items(): + if token not in VOCAB: + fail.append('retired token is absent from VOCAB: %s' % token) + elif token_hits[token]: + fail.append('retired VOCAB token occurs: %s (%s)' % (token, reason)) + for i in dead_rules: + fail.append('role rule %d (%s %s) matched no site' % (i, ROLE_RULES[i][0], ROLE_RULES[i][2][:40])) + for b in table_bad: + fail.append(b) + for n in b_unclassified: + fail.append('numeric builtin `%s` (%s) has no classification row' % (n, nb[n][0])) + for n in b_stale: + fail.append('classification row `%s` names no numeric registered builtin' % n) + fail.extend(exec_bad) + fail.extend(sbad) + for n in slot_missing: + fail.append('slot classifier `%s` has no disposition row' % n) + for n in slot_stale: + fail.append('disposition row `%s` names no D1/D2/D3 slot classifier' % n) + if stale is not None: + fail.append('%s is stale: %d line(s) differ from the generated inventory; regenerate it with ' + 'tools/phase2_inventory.py' % (os.path.relpath(args.out, ROOT), stale)) + for f_ in fail: + print('FAIL: ' + f_) + print('builtins: registered=%d numeric=%d classified=%d unclassified=%d stale=%d' % ( + len(regs), len(nb), len(nb) - len(b_unclassified), len(b_unclassified), len(b_stale))) + print('slot classifiers: %s dispositions=%d missing=%d stale=%d' % ( + ' '.join('%s=%d' % (k, slot_cls.get(k, 0)) for k in ('D1', 'D2', 'D3', 'T')), + len(srows), len(slot_missing), len(slot_stale))) + print('executors: leaf_scan=%d leaf_exec=%d mismatches=%d' % ( + len(leaf_scan), len(leaf_exec), len(exec_bad))) + print('examined=%d classified=%d unclassified=%d' % (examined + len(unreviewed), + len(classified), len(unclassified))) + if examined <= 0 or not regs or unclassified or fail: + print('phase2-inventory: FAIL') + return 1 + print('phase2-inventory: PASS') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/phase2_lib_diff.sh b/tools/phase2_lib_diff.sh new file mode 100755 index 000000000..619fa454a --- /dev/null +++ b/tools/phase2_lib_diff.sh @@ -0,0 +1,360 @@ +#!/usr/bin/env bash +# The stdlib's int differential (#1848, #1758 Phase 2): every exported function +# of every tracked lib/*.eigs module, called with INT arguments on origin/main +# and on the branch. Both binaries run the SAME lib source (a copy of this +# tree's lib/ under a scratch project root), so only the runtime differs. +# bash tools/phase2_lib_diff.sh +# The stated range: every int n in [-3, 110]; arity 1 calls f(n), arity k >= 2 +# calls the diagonal f(n, ..., n) and, per position, n there with 1 elsewhere. +# A `define f as:` takes the implicit parameter n (arity 1); arity 0 is not +# called. The population is the module's import namespace (its `fn` keys), +# checked against the module's column-0 `define` lines in both directions. +# A call LISTED is one main completes and the branch raises or never reaches. +# Each listed function must be an integer-domain function declared in +# tests/phase2/lib_int_domain.txt (decision (A): int overflow raises), and each +# declaration must be listed (a stale one fails). Every other listing fails. +# N4 also compares successful JSON values (numeric kinds compare equal) for +# the source-derived closure of modulo users. Negative-boundary and evaluator +# probes reach paths the uniform int sweep cannot. Changes require an N4 reason +# in lib_floor_mod.txt; unused declarations fail. The final summary keeps the +# original contract consumed by phase2_ci.sh and includes both kinds of change. +set -eu +cd "$(dirname "$0")/.." +exec python3 - "$@" <<'PY' +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import signal +import subprocess +import sys +import tempfile + +RANGE = list(range(-3, 111)) +TIMEOUT = 120 +root = Path.cwd() +ap = argparse.ArgumentParser(prog='tools/phase2_lib_diff.sh') +ap.add_argument('main') +ap.add_argument('branch') +args = ap.parse_args() + + +def fail(msg): + print('phase2-lib-diff: FAIL: ' + msg, flush=True) + sys.exit(1) + + +bins = {} +for arm in ('main', 'branch'): + p = Path(getattr(args, arm)) + if not p.is_file() or not os.access(p, os.X_OK): + fail('not an executable: %s' % p) + bins[arm] = str(p.resolve()) +def declarations(name, rule=None): + rows = {} + for n, line in enumerate((root / 'tests/phase2' / name).read_text().splitlines(), 1): + if not line.strip() or line.startswith('#'): + continue + f = line.split('\t') + if len(f) != 2 or not re.fullmatch(r'\w+\.\w+', f[0]) or not f[1].strip() or f[0] in rows: + fail('%s:%d: want one "module.functionreason" row per function' % (name, n)) + if rule and not f[1].startswith(rule + ': '): + fail('%s:%d: reason must name %s' % (name, n, rule)) + rows[f[0]] = f[1].strip() + return rows + + +declared = declarations('lib_int_domain.txt') +floor_declared = declarations('lib_floor_mod.txt', 'N4') +mods = sorted(m for m in subprocess.check_output(['git', 'ls-files', '-z', 'lib/*.eigs'], + cwd=root).decode().split('\0') + if re.fullmatch(r'lib/[a-z_0-9]+\.eigs', m)) +if not mods: + fail('no tracked lib/*.eigs modules') +work = Path(tempfile.mkdtemp(prefix='phase2-libdiff-')) +(work / 'lib').mkdir() +for m in mods: + shutil.copy(root / m, work / m) +(work / 'eigs.json').write_text('{}\n') +env = {k: v for k, v in os.environ.items() + if not k.startswith('EIGS_') and k not in ('DISPLAY', 'XAUTHORITY', 'WAYLAND_DISPLAY')} +env.update(SDL_VIDEODRIVER='dummy', SDL_AUDIODRIVER='dummy') + + +def run(arm, name, text): + """Run one probe in its own session and cwd; a timeout kills the group.""" + prog = work / (name + '.eigs') + prog.write_text(text) + cwd = work / ('cwd-' + arm) + cwd.mkdir(exist_ok=True) + proc = subprocess.Popen([bins[arm], str(prog)], cwd=cwd, env=dict(env, HOME=str(cwd)), + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, start_new_session=True) + try: + out, _ = proc.communicate(timeout=TIMEOUT) + rc = proc.returncode + except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) + out, _ = proc.communicate() + rc = 124 + return rc, out.decode(errors='replace').splitlines() + + +def tuples(arity): + rows = [(n,) * arity for n in RANGE] + if arity >= 2: + rows += [tuple(n if i == j else 1 for i in range(arity)) for j in range(arity) for n in RANGE] + return sorted(dict.fromkeys(rows), key=lambda t: (max(abs(x) for x in t), t)) + + +def same_value(a, b): + # Ignore only the intended numeric-kind distinction, recursively. Python's + # plain equality would also treat true as 1 and hide a semantic change. + if type(a) in (int, float) and type(b) in (int, float): + return a == b + if type(a) is not type(b): + return False + if isinstance(a, list): + return len(a) == len(b) and all(same_value(x, y) for x, y in zip(a, b)) + if isinstance(a, dict): + return a.keys() == b.keys() and all(same_value(a[k], b[k]) for k in a) + return a == b + + +def call_text(mod, fn, t): + if isinstance(t, dict): + return 'r is ' + t['call'] + a = t if isinstance(t, str) else (str(t[0]) if len(t) == 1 else '[%s]' % ', '.join(map(str, t))) + return 'r is %s.%s of %s' % (mod, fn, a) + + +define_re = re.compile(r'^define ([A-Za-z_]\w*)(?:\(([^)]*)\))? as:', re.M) +own = {m: {name: 1 if params is None else len([p for p in params.split(',') if p.strip()]) + for name, params in define_re.findall((root / m).read_text()) if not name.startswith('_')} + for m in mods} +# A conservative named-reference closure: include private helpers and callbacks, +# not only `f of` calls. Ordinary strings and comments cannot seed a modulo user; +# f-string contents remain visible so an interpolated `%` cannot be missed. +# Over-approximation can add comparisons but never waive a changed result. +def code_only(source): + pattern = r'#[^\n]*|(?:f)?"(?:\\.|[^"\\])*"|(?:f)?\'(?:\\.|[^\'\\])*\'' + def mask(match): + text = match[0] + return text if text.startswith(('f"', "f'")) else re.sub(r'[^\n]', ' ', text) + return re.sub(pattern, mask, source) + + +bodies = {} +for m in mods: + source = code_only((root / m).read_text()) + definitions = list(define_re.finditer(source)) + for match in definitions: + end = match.end() + # A function ends at the next nonblank, nonindented source line. + for line in source[end:].splitlines(keepends=True): + if line.strip() and not line[0].isspace(): + break + end += len(line) + bodies[(m, match[1])] = source[match.start():end] +by_name = {} +for key in bodies: + by_name.setdefault(key[1], set()).add(key) +refs = {key: set().union(*(by_name.get(name, set()) for name in re.findall(r'\b[A-Za-z_]\w*\b', body))) - {key} + for key, body in bodies.items()} +floor_scope = {key for key, body in bodies.items() if '%' in body} +while True: + widened = floor_scope | {key for key in bodies if refs[key] & floor_scope} + if widened == floor_scope: + break + floor_scope = widened +if not floor_scope: + fail('no modulo function found in tracked stdlib sources') +# These are public API input shapes, not exemptions. They exercise the two +# meta-interpreter entries whose numeric int arguments cannot reach a binop. +floor_inputs = { + ('lib/checksum.eigs', 'adler32'): ['buf_from_list of ([-7.0, 2.0])'], + ('lib/checksum.eigs', 'sum8'): ['buf_from_list of ([-7.0, 2.0])'], + ('lib/supervise.eigs', 'supervise_step'): [{'call': '_phase2_negative_step of []'}], + ('lib/supervise.eigs', 'supervise_run'): [{'call': '_phase2_negative_run of []'}], + ('lib/eigen.eigs', 'eigen_eval'): ['[["binop", "%", ["num", -7], ["num", 3]], {}]', + '[["binop", "%", ["num", 7], ["num", -3]], {}]', + '[["binop", "%", ["num", -7.5], ["num", 2]], {}]', + '[["binop", "%", ["num", 7.5], ["num", -2]], {}]'], + ('lib/eigen.eigs', 'eigen_run'): ['"return -7 % 3"', '"return 7 % -3"', + '"return -7.5 % 2"', '"return 7.5 % -2"'], +} +# A negative heartbeat changes an observer feed even when this tick's returned +# restart count is still zero. Observe the feed and the public result together; +# one cooperative task is alive for the tick and explicitly killed afterwards. +floor_prelude = {} +for fn, call in (('supervise_step', 'supervise_step of sup'), + ('supervise_run', 'supervise_run of [sup, 1]')): + suffix = 'step' if fn == 'supervise_step' else 'run' + floor_prelude[('lib/supervise.eigs', fn)] = ( + 'load_file of "lib/supervise.eigs"\n' + 'define _phase2_worker(slot) as:\n' + ' task_yield of null\n' + ' return null\n' + 'define _phase2_negative_%s() as:\n' + ' local sup is supervisor_new of 0\n' + ' local child is supervise of [sup, _phase2_worker, 0]\n' + ' heartbeat of [0, -7]\n' + ' local result is %s\n' + ' task_kill of child.id\n' + ' return [result, _sup_o0]\n' % (suffix, call)) +for key in floor_inputs: + if key not in floor_scope or key[1] not in own.get(key[0], {}): + fail('N4 API probe no longer belongs to an exported modulo user: %s.%s' % key) +defined_anywhere = set().union(*own.values()) +# The one module that is not a library: importing it runs a command line. +NOT_LIBRARY = {'lib/pkg.eigs': 'the `eigenscript --pkg` command; importing it runs the CLI'} +# Pass 1: each module's import namespace (its `fn` keys), on both binaries. +ns, unimportable = {}, {} +for m in mods: + mod = Path(m).stem + keys, rcs = {}, {} + for arm in bins: + rcs[arm], out = run(arm, 'keys_' + mod, 'import %s\nfor k in keys of %s:\n if (type of %s[k]) == "fn":\n' + ' print of f"K {k}"\nprint of "KEYS-DONE"\n' % (mod, mod, mod)) + keys[arm] = {line[2:] for line in out if line.startswith('K ')} if 'KEYS-DONE' in out else None + if keys['main'] is None and keys['branch'] is None: + unimportable[m] = rcs + continue + if keys['main'] != keys['branch'] or not set(own[m]) <= keys['branch'] \ + or not keys['branch'] <= defined_anywhere: + fail('%s: namespaces disagree (rc main=%d branch=%d): main-only=%s branch-only=%s undefined=%s ' + 'not-exported=%s' % (m, rcs['main'], rcs['branch'], sorted((keys['main'] or set()) - (keys['branch'] or set())), + sorted((keys['branch'] or set()) - (keys['main'] or set())), + sorted((keys['branch'] or set()) - defined_anywhere), + sorted(set(own[m]) - (keys['branch'] or set())))) + ns[m] = keys['branch'] +# Pass 2: each defined function is examined once, through its own module or, +# when that cannot be imported alone (ui.eigs loads its ui_w_* fragments), +# through a module whose namespace re-exports it. +plan, skipped = [], {} +for m in mods: + for fn, arity in sorted(own[m].items()): + via = m if m in ns else next((x for x in mods if x in ns and fn in ns[x] and fn not in own[x]), None) + if via is None: + skipped.setdefault(m, []).append(fn) + else: + plan.append((m, via, fn, arity)) +if set(skipped) != set(NOT_LIBRARY) or not set(NOT_LIBRARY) <= set(unimportable): + fail('unexamined modules %s != the declared non-library modules %s' % (sorted(skipped), sorted(NOT_LIBRARY))) +counts = dict(modules=len(mods), importable=len(ns), functions=len(plan), + via_reexport=sum(via not in own or fn not in own[via] for _, via, fn, _ in plan), + no_params=0, calls=0, examined=0, unexamined=0, both_ok=0, both_raise=0, improved=0) +listed, unexamined, floor_changed = {}, {}, {} +floor_functions, floor_calls, floor_both_ok = 0, 0, 0 +for source, via, fn, arity in plan: + mod = Path(via).stem + if arity == 0: + counts['no_params'] += 1 + continue + calls = tuples(arity) + compare_value = (source, fn) in floor_scope + if compare_value: + floor_functions += 1 + # -7 reaches weekday_from_days; -719469 crosses civil_from_days' + # epoch offset and exposes its private _fdiv's negative dividend. + for n in (-7, -719469): + calls += [(n,) * arity] + calls += [tuple(n if i == j else 1 for i in range(arity)) for j in range(arity)] + calls = list(dict.fromkeys(calls)) + floor_inputs.get((source, fn), []) + value_line = ' print of f"V %d {json_encode of r}"\n' if compare_value else '' + # load_file exposes private observer state in the targeted supervision + # probe and also defines a function named supervise. Preserve the import + # namespace before that definition so the original int sweep is unchanged. + prelude = floor_prelude.get((source, fn), '') + call_mod = '_phase2_libdiff_module' if prelude else mod + header = 'import %s\n' % mod + if prelude: + header += '_phase2_libdiff_module is %s\n' % mod + text = header + prelude + ''.join( + ('try:\n %s\n print of f"R %d OK {type of r}"\n' % (call_text(call_mod, fn, t), i)) + + (value_line % i if compare_value else '') + + ('catch e:\n print of f"R %d RAISE {e}"\n' % i) for i, t in enumerate(calls)) + res = {} + for arm in bins: + rc, out = run(arm, 'call_%s_%s' % (mod, fn), text) + got, values = {}, {} + for line in out: + r = re.match(r'R (\d+) (OK|RAISE) ?(.*)$', line) + if r and int(r[1]) < len(calls) and int(r[1]) not in got: + got[int(r[1])] = (r[2], r[3]) + v = re.match(r'V (\d+) (.*)$', line) + if v and int(v[1]) < len(calls): + if int(v[1]) in values: + fail('duplicate N4 value from %s %s.%s call %s' % (arm, mod, fn, v[1])) + try: + values[int(v[1])] = json.loads(v[2]) + except ValueError: + fail('non-JSON N4 value from %s %s.%s: %s' % (arm, mod, fn, line)) + res[arm] = (rc, got, values) + counts['calls'] += len(calls) + if compare_value: + floor_calls += len(calls) + for i, t in enumerate(calls): + a, b = res['main'][1].get(i), res['branch'][1].get(i) + if a is None or (a[0] == 'RAISE' and b is None): + counts['unexamined'] += 1 + unexamined.setdefault('%s.%s' % (mod, fn), [0, res['main'][0], res['branch'][0]])[0] += 1 + continue + counts['examined'] += 1 + if a[0] == 'OK' and (b is None or b[0] == 'RAISE'): + why = b[1] if b else 'not reached (branch rc=%d)' % res['branch'][0] + listed.setdefault('%s.%s' % (mod, fn), []).append('%s -> %s' % (call_text(mod, fn, t)[5:], why)) + elif a[0] == b[0] == 'OK': + counts['both_ok'] += 1 + if compare_value: + floor_both_ok += 1 + if i not in res['main'][2] or i not in res['branch'][2]: + fail('missing successful N4 value for %s.%s call %d' % (mod, fn, i)) + av, bv = res['main'][2][i], res['branch'][2][i] + if not same_value(av, bv): + floor_changed.setdefault('%s.%s' % (mod, fn), []).append( + '%s -> %s (main) / %s (branch)' % (call_text(mod, fn, t)[5:], + json.dumps(av), json.dumps(bv))) + elif a[0] == b[0]: + counts['both_raise'] += 1 + else: + counts['improved'] += 1 +print('phase2-lib-diff: range=[%d, %d] %s' % (RANGE[0], RANGE[-1], ' '.join('%s=%d' % kv for kv in counts.items()))) +for m in sorted(NOT_LIBRARY): + print(' NOT-LIBRARY %s: %d function(s) not called (%s)' % (m, len(skipped[m]), NOT_LIBRARY[m])) +for fn, (n, rca, rcb) in sorted(unexamined.items()): + print(' UNEXAMINED %s: %d call(s) main never completed (rc main=%d branch=%d)' % (fn, n, rca, rcb)) +bad = 0 +for fn in sorted(listed): + tag = 'INT-DOMAIN' if fn in declared else 'UNDECLARED' + bad += fn not in declared + print(' %s %s: %d call(s) raise on the branch only; first: %s%s' % ( + tag, fn, len(listed[fn]), listed[fn][0], ' (%s)' % declared[fn] if fn in declared else '')) +for fn in sorted(set(declared) - set(listed)): + bad += 1 + print(' STALE %s: declared integer-domain, but no call raises on the branch only' % fn) +print('phase2-lib-diff: N4 source_functions=%d exported_functions=%d calls=%d both_ok=%d changed_functions=%d' % ( + len(floor_scope), floor_functions, floor_calls, floor_both_ok, len(floor_changed))) +for fn in sorted(floor_changed): + tag = 'N4' if fn in floor_declared else 'UNDECLARED-N4' + bad += fn not in floor_declared + print(' %s %s: %d successful value change(s); first: %s%s' % ( + tag, fn, len(floor_changed[fn]), floor_changed[fn][0], + ' (%s)' % floor_declared[fn] if fn in floor_declared else '')) +for fn in sorted(set(floor_declared) - set(floor_changed)): + bad += 1 + print(' STALE-N4 %s: declared floor-modulo change, but no successful value changes' % fn) +if floor_functions == 0 or floor_both_ok == 0: + fail('N4 comparison examined no successful exported modulo calls') +if counts['examined'] == 0 or counts['examined'] + counts['unexamined'] != counts['calls']: + fail('examined=%d unexamined=%d calls=%d' % (counts['examined'], counts['unexamined'], counts['calls'])) +print('phase2-lib-diff: listed_functions=%d undeclared=%d stale=%d %s' % ( + len(set(listed) | set(floor_changed)), + sum(f not in declared for f in listed) + sum(f not in floor_declared for f in floor_changed), + len(set(declared) - set(listed)) + len(set(floor_declared) - set(floor_changed)), + 'FAIL' if bad else 'PASS')) +shutil.rmtree(work) +sys.exit(1 if bad else 0) +PY diff --git a/tools/phase2_plants.sh b/tools/phase2_plants.sh new file mode 100755 index 000000000..717054ab4 --- /dev/null +++ b/tools/phase2_plants.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# tools/phase2_plants.sh [--ref REF] [--selftest] -- #1758: Phase 2's planted +# faults (docs/design/phase2-numbers.md 9.3). A check proves it fires when it +# is written or changed (docs/PRINCIPLES.md 5), so this runs as a +# tools/selftests.txt row keyed on the checks the plants name, never on every +# suite or CI run; `--selftest` is that row's spelling of the same run. Each +# plant: +# 1. `git archive REF` (default HEAD) into a scratch tree with no .git; +# 2. applies one replacement, refusing unless the original text occurs +# exactly once before and the planted text after (else BROKEN); +# 3. builds what its named check needs (plain make, one at a time); +# 4. runs the named check and requires the check's OWN failure line naming +# the row (keyed on the message, not on a nonzero exit) -> KILLED. +# Then, once per plant, the transverse run (9.3, "deleting its named check's +# assertion must let the plant through"): the same planted tree with the +# check's assertion removed must NOT print that line -> the plant survives +# exactly the deletion of the assertion that names it. +# +# Plants land with their named checks: plant 1 with R1.1 (the C slot test); +# 2-7 name MAP rows that land in R1.3-R2.2 and join this table then. The R1.1 +# fix round added three instrument plants on the slot test: an integer overflow +# that wraps instead of raising (Part D), and a population scan that writes noise +# to stderr or an unparsed line to stdout (the critic's class); and #1775's +# plant, the missing return that makes the -O0 amalgamation warn. +# Prints one line per plant and `plants=N killed=N survived=0 broken=0 +# transverse=N`; exit 0 only for exactly that, with N > 0. About 3 minutes +# per plant on the 4 GB box (two -O0 compiles of the amalgamated slot test). +set -u +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +REF=HEAD +while [ $# -gt 0 ]; do + case "$1" in + --ref) REF="$2"; shift 2 ;; + --selftest) shift ;; + *) echo "phase2-plants: FAIL: unknown argument $1"; exit 2 ;; + esac +done +TMO="" +if command -v timeout >/dev/null 2>&1; then TMO="timeout 1500" +elif command -v gtimeout >/dev/null 2>&1; then TMO="gtimeout 1500"; fi +WORK=$(mktemp -d "${TMPDIR:-/tmp}/eigs-phase2-plants.XXXXXX") || { echo "phase2-plants: FAIL: mktemp"; exit 2; } +VERDICT_DONE=0 +# A run that ends without its verdict line is a FAIL, named with the command +# it was running (the #988 rule). +trap 'rc=$?; [ "$VERDICT_DONE" = 1 ] || echo "phase2-plants: FAIL: exited ($rc) before a verdict, at: $BASH_COMMAND"; rm -rf "$WORK"' EXIT + +# replace FILE OLD NEW: exactly one OLD before, NEW present after. +replace() { + python3 -W error - "$1" "$2" "$3" <<'PY' +import sys +path, old, new = sys.argv[1], sys.argv[2], sys.argv[3] +with open(path, encoding='utf-8') as f: + text = f.read() +n = text.count(old) +if n != 1: + print('replace: %s: the original occurs %d times, want exactly 1' % (path, n)) + sys.exit(1) +text = text.replace(old, new) +if new not in text: + print('replace: %s: the planted text is absent after the replacement' % path) + sys.exit(1) +with open(path, 'w', encoding='utf-8') as f: + f.write(text) +PY +} + +plants=0; killed=0; survived=0; broken=0; transverse=0 + +# run_plant ID FILE OLD NEW CHECK_FILE CHECK_OLD CHECK_NEW FAIL_RE CHECK_CMD... +run_plant() { + local id="$1" file="$2" old="$3" new="$4" cfile="$5" cold="$6" cnew="$7" re="$8" + shift 8 + plants=$((plants + 1)) + local verdict="" trans="" + for mode in plant transverse; do + local dir="$WORK/$id-$mode" + mkdir -p "$dir" + if ! git -C "$ROOT" archive "$REF" | tar -x -C "$dir"; then verdict=BROKEN; echo " $id: archive of $REF failed"; break; fi + if ! replace "$dir/$file" "$old" "$new"; then verdict=BROKEN; break; fi + if [ "$mode" = transverse ] && ! replace "$dir/$cfile" "$cold" "$cnew"; then trans=BROKEN; break; fi + local log="$WORK/$id-$mode.log" + (cd "$dir" && $TMO "$@") > "$log" 2> "$log.err" + local rc=$? + if [ -s "$log.err" ]; then # the check's own stderr: unexpected, shown, BROKEN + sed 's/^/ check stderr: /' "$log.err" + [ "$mode" = plant ] && verdict=BROKEN || trans=BROKEN + break + fi + if [ "$mode" = plant ]; then + if grep -Eq "$re" "$log"; then verdict=KILLED + elif [ "$rc" -eq 124 ] || grep -q 'build: \|make phase2-slot-test exited' "$log"; then + verdict=BROKEN; echo " $id: the check did not run (rc=$rc):"; tail -20 "$log" | sed 's/^/ | /' + else verdict=SURVIVED; echo " $id: no failure line matching /$re/ (rc=$rc):"; tail -20 "$log" | sed 's/^/ | /'; fi + grep -E "$re" "$log" | head -3 | sed 's/^/ red: /' + [ "$verdict" = KILLED ] || break + else + if grep -Eq "$re" "$log"; then trans=NOT-TRANSVERSE; echo " $id: the line still fired with its assertion deleted" + elif [ "$rc" -eq 124 ]; then trans=BROKEN + else trans=TRANSVERSE; fi + tail -2 "$log" | sed 's/^/ assertion deleted: /' + fi + done + case "$verdict" in + KILLED) killed=$((killed + 1)) ;; + SURVIVED) survived=$((survived + 1)) ;; + *) broken=$((broken + 1)); verdict=BROKEN ;; + esac + [ "$trans" = TRANSVERSE ] && transverse=$((transverse + 1)) + echo "plant $id: $verdict transverse=${trans:-not-run}" +} + +# Plant 1 (9.3): an int that fits inline stored boxed. slot_from_int, the one +# int writer, skips the small-int rule and tags the int as a heap pointer. +# Named check: the C slot test's slot_from_int rows (Part C). +run_plant 1 src/value_slot.h \ + 'EigsSlot s; s.u = TAG_SMI | ((uint64_t)i & SLOT_PAYLOAD_MASK); return s;' \ + 'EigsSlot s; s.u = TAG_HEAP | ((uint64_t)i & SLOT_PAYLOAD_MASK); return s;' \ + tests/phase2/slot_layer.c \ + ' if (s.u != smi(v).u)' \ + ' if (0)' \ + '^slot-layer-test: FAIL slot_from_int\(0\): bits 0x' \ + bash tests/phase2/slot_layer.sh release + +# R1.3 N3/(A): overflow must raise, never return the wrapped int64 result. +# The builtin computes the wrapped bits through __builtin_*_overflow; dropping +# the checked raise therefore reproduces the exact fault the row promises. +# Named check: the slot test's Part D overflow_add row, with a float control. +run_plant overflow-wrap src/vm.c \ + ' if (overflow) {' \ + ' if (overflow && 0) { /* PLANT: return the wrapped int64 result */' \ + tests/phase2/slot_layer.c \ + ' if (strcmp(got, "ran") != 0 || strcmp(pre, want) != 0) {' \ + ' if (0) {' \ + "^slot-layer-test: FAIL arm overflow_add with an int: got " \ + bash tests/phase2/slot_layer.sh release + +# The critic's class: a population scan whose diagnostics reach the parser. +# Named checks: the runner's stderr refusal, and its grammar whitelist. +run_plant stderr-noise tools/phase2_inventory.py \ + " out, bad = [], [] + for (rel, k), (cls, c) in sorted(slots.items()):" \ + " print('phase2-plant: noise on stderr', file=sys.stderr) + out, bad = [], [] + for (rel, k), (cls, c) in sorted(slots.items()):" \ + tests/phase2/slot_layer.sh \ + 'show_stderr "$TMP/pop.err" "the population scan"' \ + ':' \ + "^slot-layer: FAIL: the population scan wrote to stderr" \ + bash tests/phase2/slot_layer.sh release +run_plant stdout-noise tools/phase2_inventory.py \ + " out, bad = [], [] + for (rel, k), (cls, c) in sorted(slots.items()):" \ + " print('phase2-plant: an unparsed line') + out, bad = [], [] + for (rel, k), (cls, c) in sorted(slots.items()):" \ + tests/phase2/slot_layer.sh \ + 'if [ -s "$TMP/pop.bad" ]; then' \ + 'if false; then' \ + "^slot-layer: FAIL: the population scan printed lines outside its grammar" \ + bash tests/phase2/slot_layer.sh release + +# #1775: the missing return comes back. -O0 does not fold ARG_GUARD's +# constant test, so the amalgamation warns (-Wreturn-type). Named check: the +# runner's build-warning refusal. +run_plant return-type src/builtins_tensor.c \ + " return make_null(); /* unreachable: the guard above always returns, but + * -O0 does not fold its constant test (#1775) */ +" \ + "" \ + tests/phase2/slot_layer.sh \ + "if grep -q 'warning:' \"\$TMP/build.txt\"; then" \ + 'if false; then' \ + "^slot-layer: FAIL: the slot test build printed [0-9]+ warning" \ + bash tests/phase2/slot_layer.sh release + +echo "plants=$plants killed=$killed survived=$survived broken=$broken transverse=$transverse" +VERDICT_DONE=1 +if [ "$plants" -gt 0 ] && [ "$killed" -eq "$plants" ] && [ "$transverse" -eq "$plants" ]; then + echo "phase2-plants: PASS"; exit 0 +fi +echo "phase2-plants: FAIL"; exit 1 diff --git a/tools/phase2_readers.sh b/tools/phase2_readers.sh new file mode 100755 index 000000000..e16c4221d --- /dev/null +++ b/tools/phase2_readers.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# #1758 R1.6a: final readers through the no-SDL amalgamation, warnings fatal. +set -u +ROOT=$(cd "$(dirname "$0")/.." && pwd) +VARIANT=${1:-release} +FIXTURE=${2:-readers} +LABEL=phase2-readers +case "$FIXTURE" in + readers) ;; + num_text) LABEL=phase2-num-text ;; + const_dedup_bits) LABEL=phase2-const-dedup ;; + q7_readers) LABEL=phase2-q7-readers ;; + gfx_audio_boundary) LABEL=phase2-gfx-audio ;; + gfx_byte_boundary) LABEL=phase2-gfx-byte ;; + net_byte_boundary) LABEL=phase2-net-boundary ;; + embed_buffer_get_raw) LABEL=phase2-embed-buffer-get ;; + *) echo "phase2-readers: FAIL unknown fixture $FIXTURE"; exit 2 ;; +esac +[ -f "$ROOT/tests/phase2/$FIXTURE.c" ] || { echo "$LABEL: FAIL missing tests/phase2/$FIXTURE.c"; exit 1; } +case "$VARIANT" in release|asan) ;; *) echo "phase2-readers: FAIL unknown variant $VARIANT"; exit 2 ;; esac +TMP=$(mktemp -d "${TMPDIR:-/tmp}/eigs-readers.XXXXXX") || { echo 'phase2-readers: FAIL mktemp'; exit 2; } +trap 'rm -rf "$TMP"' EXIT +fail() { echo "$LABEL: FAIL $*"; exit 1; } +make -s -C "$ROOT" PHASE2_READERS_VARIANT="$VARIANT" PHASE2_READERS_FIXTURE="$FIXTURE" phase2-readers-test > "$TMP/build" 2>&1 +rc=$? +if [ "$rc" -ne 0 ] || grep -q 'warning:' "$TMP/build"; then + cat "$TMP/build" + rm -f "$ROOT/build/phase2/${FIXTURE}_$VARIANT" + fail "build rc=$rc (all compiler warnings fail)" +fi +BIN="$ROOT/build/phase2/${FIXTURE}_$VARIANT" +[ -x "$BIN" ] || fail "missing binary $BIN" +if [ "$VARIANT" = asan ]; then + ASAN_OPTIONS=detect_leaks=1 env -u XAUTHORITY DISPLAY=:99 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy "$BIN" > "$TMP/out" 2> "$TMP/err" + rc=$? +else + ( case "$FIXTURE" in gfx_*_boundary) ulimit -v 1500000 ;; esac; + env -u XAUTHORITY DISPLAY=:99 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy "$BIN" ) > "$TMP/out" 2> "$TMP/err" + rc=$? +fi +cat "$TMP/out" +if [ -s "$TMP/err" ]; then cat "$TMP/err"; fail 'reader fixture wrote stderr'; fi +[ "$rc" -eq 0 ] || fail "reader fixture rc=$rc" +if [ "$FIXTURE" = num_text ]; then + [ "$(grep -cE '^phase2-num-text-test: PASS cases=54 checks=1038 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete number text PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected number text output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = const_dedup_bits ]; then + [ "$(grep -cE '^phase2-const-dedup-test: PASS checks=8 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete const dedup PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected const dedup output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = embed_buffer_get_raw ]; then + [ "$(grep -cE '^phase2-embed-buffer: PASS checks=36 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete getter PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected getter output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = net_byte_boundary ]; then + [ "$(grep -cE '^phase2-net-byte: PASS checks=72 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete net-byte-boundary PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected net-byte-boundary fixture output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = gfx_byte_boundary ]; then + [ "$(grep -cE '^phase2-gfx-byte-test: PASS checks=247 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete byte-boundary PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected byte-boundary fixture output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = gfx_audio_boundary ]; then + [ "$(grep -cE '^phase2-gfx-audio-test: PASS checks=44 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete audio-boundary PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected audio-boundary fixture output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +if [ "$FIXTURE" = q7_readers ]; then + [ "$(grep -cE '^phase2-q7-test: PASS checks=46 failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete Q7 PASS' + [ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected Q7 fixture output' + echo "$LABEL: PASS variant=$VARIANT" + exit 0 +fi +[ "$(grep -c '^phase2-readers-test: PASS cases=[1-9][0-9]* forms=49 checks=[1-9][0-9]* failures=0$' "$TMP/out")" -eq 1 ] || fail 'missing unique complete PASS' +[ "$(wc -l < "$TMP/out" | tr -d ' ')" -eq 1 ] || fail 'unexpected fixture output' +checks=$(sed -n 's/^phase2-readers-test: PASS .* checks=\([0-9][0-9]*\) failures=0$/\1/p' "$TMP/out") +echo "phase2-readers: PASS variant=$VARIANT checks=$checks" diff --git a/tools/replay_diff.sh b/tools/replay_diff.sh index 017a17838..cf4375aab 100755 --- a/tools/replay_diff.sh +++ b/tools/replay_diff.sh @@ -45,12 +45,21 @@ TMO="" if command -v timeout >/dev/null 2>&1; then TMO="timeout 300" elif command -v gtimeout >/dev/null 2>&1; then TMO="gtimeout 300"; fi +# Each program runs under its run conditions from the one shared loader +# (tests/suite_program_env.sh: the suite's, jit_diff's and the Phase 2 +# differential's), so a fail-soft test is replayed past its first strict raise. +export EIGS_TEST_DIR="$ROOT/tests" +. "$ROOT/tests/suite_program_env.sh" || exit 1 corpus_dir() { printf '%s' "${REPLAY_DIFF_CORPUS:-$ROOT/tests}"; } eig_bin() { printf '%s' "${REPLAY_DIFF_EIG:-./eigenscript}"; } norm() { sed -E 's/0x[0-9a-f]+/0xADDR/g' "$1"; } +# The ledger's DATA rows. A '#' line or a blank line is a reason, never a +# row (jit_diff's ledger rule): both are stripped before the comparison and +# the count, so a row can carry the issue that explains it. +ledger_data() { awk 'NF && substr($0, 1, 1) != "#" { print }' "$1" | sort; } ledger_count() { local count - count=$(wc -l < "$1") || return 1 + count=$(ledger_data "$1" | wc -l) || return 1 count=${count//[[:space:]]/} case "$count" in ''|*[!0-9]*) return 1;; esac printf '%s' "$count" @@ -60,7 +69,10 @@ ledger_count() { # exited 3 -- a phantom row from the environment, not the tape. run() { local out="$1" rc; shift - env -u EIGS_JIT_OSR_THRESHOLD EIGS_JIT_OFF=1 "$@" $TMO "$(eig_bin)" "$(corpus_dir)/$b" > "$out" 2>&1 /dev/null || { echo "replay_diff: FAIL: run-condition lookup failed for $b" >&2; exit 1; } + ( if grep -Eq '(gfx|ui|audio)_[a-z_]+' "$(corpus_dir)/$b"; then ulimit -v 1500000; fi + suite_program_run "$b" env -u EIGS_JIT_OSR_THRESHOLD EIGS_JIT_OFF=1 "$@" $TMO "$(eig_bin)" "$(corpus_dir)/$b" + ) > "$out" 2>&1 > "$out" # Runs include self-check and second adjudication arms. Never compare, # excuse as NONDET/boundary, or ledger a possibly incomplete exit-124 run. @@ -193,17 +205,25 @@ fi # ---------------------------------------------------------------- the gate EIG="$(eig_bin)"; CORPUS="$(corpus_dir)"; BASE="${REPLAY_DIFF_BASE:-$ROOT/tests/replay_diff_expected.txt}" FLOOR="${REPLAY_DIFF_FLOOR:-100}" +(cd "$ROOT" && find tests -type f -name '*.eigs' | LC_ALL=C sort) > "$T/corpus" +python3 "$ROOT/tools/phase2_corpus_exclude.py" "$ROOT" "$T/corpus" || exit 1 +: > "$T/files" +for f in "$CORPUS"/test_*.eigs; do + [ -f "$f" ] || continue + case "$f" in "$ROOT"/*) grep -qxF "${f#"$ROOT"/}" "$T/corpus" || continue ;; esac + printf '%s\n' "$f" >> "$T/files" +done got="$T/got"; : > "$got"; n=0; boundary=0; nondet=0 # self-check on a known-deterministic program b=test_math_underflow.eigs -[ -f "$CORPUS/$b" ] || b=$(ls "$CORPUS"/test_*.eigs | head -1 | xargs basename) +[ -f "$CORPUS/$b" ] || b=$(head -n 1 "$T/files"); b=${b##*/} rm -f "$T/tape0"; run "$T/r0" EIGS_TRACE="$T/tape0" [ -s "$T/tape0" ] || { echo "replay_diff: FAIL: the record arm wrote no tape for $b -- check EIGS_TRACE"; exit 1; } run "$T/p0" EIGS_REPLAY="$T/tape0" crash_check rec "$T/r0"; crash_check rep "$T/p0" [ "$crash" -eq 0 ] || { echo "replay_diff: FAIL: the self-check program $b died by signal"; exit 1; } diff -q <(norm "$T/r0") <(norm "$T/p0") >/dev/null || { echo "replay_diff: FAIL: the self-check program $b does not replay its own tape"; diff <(norm "$T/r0") <(norm "$T/p0") | head -5; exit 1; } -for f in "$CORPUS"/test_*.eigs; do +while IFS= read -r f; do b=$(basename "$f"); n=$((n + 1)) rm -f "$T/tape" run "$T/rec" EIGS_TRACE="$T/tape" @@ -225,16 +245,16 @@ for f in "$CORPUS"/test_*.eigs; do if ! diff -q <(norm "$T/rec") <(norm "$T/rec2") >/dev/null; then nondet=$((nondet + 1)); echo " NONDET $b (record arm differs from itself)"; continue; fi if diff -q <(norm "$T/rec2") <(norm "$T/rep2") >/dev/null; then nondet=$((nondet + 1)); echo " NONDET $b (replay diverged once, then matched)"; continue; fi echo "$b" >> "$got" -done +done < "$T/files" sort -o "$got" "$got" [ "$n" -ge "$FLOOR" ] || { echo "replay_diff: only $n programs found -- the scan is vacuous (floor $FLOOR)"; exit 1; } if [ "$crash" -gt 0 ]; then echo "replay_diff: FAIL: $crash signal exit(s) -- a crash is never a boundary, whatever the arm printed (#1112); $n programs, $boundary at the documented boundary, $nondet nondeterministic" exit 1 fi -if [ "${1:-}" = "--record" ]; then cp "$got" "$BASE"; count=$(ledger_count "$BASE") || { echo "replay_diff: FAIL: invalid ledger count"; exit 1; }; echo "replay_diff: baseline recorded ($count rows, $n programs, $boundary at the documented boundary, $nondet nondeterministic)"; exit 0; fi +if [ "${1:-}" = "--record" ]; then { [ -f "$BASE" ] && awk '/^#/ { print }' "$BASE"; cat "$got"; } > "$T/ledger.new"; cp "$T/ledger.new" "$BASE"; count=$(ledger_count "$BASE") || { echo "replay_diff: FAIL: invalid ledger count"; exit 1; }; echo "replay_diff: baseline recorded ($count rows, $n programs, $boundary at the documented boundary, $nondet nondeterministic)"; exit 0; fi [ -f "$BASE" ] || { echo "replay_diff: no baseline at $BASE (run with --record)"; cat "$got"; exit 1; } -if diff <(sort "$BASE") "$got" > "$T/d"; then +if diff <(ledger_data "$BASE") "$got" > "$T/d"; then count=$(ledger_count "$BASE") || { echo "replay_diff: FAIL: invalid ledger count"; exit 1; } echo "replay_diff: OK ($n programs record+replay; $boundary at the documented boundary; $nondet nondeterministic; $count ledgered)"; exit 0 fi diff --git a/tools/road_diff.py b/tools/road_diff.py index 8b47eafaf..a9b440139 100644 --- a/tools/road_diff.py +++ b/tools/road_diff.py @@ -73,7 +73,21 @@ def jit_stats(stderr): return compiled, rest, raw def run_gate(binary, fixtures, only=None): architecture = platform.machine().lower() - paths = sorted(fixtures.glob("*.eigs")) + from phase2_corpus_exclude import filter_paths + try: + allowed, _ = filter_paths(ROOT, sorted(p.relative_to(ROOT).as_posix() + for p in (ROOT / 'tests').rglob('*.eigs') if p.is_file())) + except (OSError, ValueError) as error: + print('road_diff: FAIL: ' + str(error), flush=True) + return 1 + allowed = set(allowed) + def eligible(path): + try: + rel = path.resolve().relative_to(ROOT).as_posix() + except ValueError: # explicitly supplied external fixture directory + return True + return not rel.startswith('tests/') or rel in allowed + paths = sorted(p for p in fixtures.glob("*.eigs") if eligible(p)) if only: paths = [p for p in paths if p.stem == only] if not paths: diff --git a/tools/section_fragments_check.sh b/tools/section_fragments_check.sh index 65cb42f45..7fe56f060 100755 --- a/tools/section_fragments_check.sh +++ b/tools/section_fragments_check.sh @@ -4,7 +4,23 @@ set -u ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" || exit 2 if [ "${1:-}" = "--no-new-inline" ]; then [ "$#" -eq 2 ] || { echo "usage: $0 --no-new-inline " >&2; exit 2; } - base=$2 + requested_base=$2 + # A dispatch can compare an older branch with a newer main that moved an + # inherited section into a fragment. Only labels added since the common + # ancestor are new on this branch (the changelog gate uses the same base). + case "$requested_base" in -*) + echo "section_fragments_check: base must name a commit, not an option" >&2 + exit 2 ;; + esac + resolved_base=$(git rev-parse --verify "$requested_base^{commit}") || { + echo "section_fragments_check: cannot resolve base $requested_base as a commit" >&2 + exit 2 + } + base=$(git merge-base "$resolved_base" HEAD) || { + echo "section_fragments_check: no merge-base with $requested_base" >&2 + exit 2 + } + echo "section-fragments base: requested=$requested_base commit=$resolved_base merge_base=$base" work=$(mktemp -d "${TMPDIR:-/tmp}/eigs_inline_sections.XXXXXX") || exit 2 trap 'rm -rf "$work"' EXIT if ! git show "$base:tests/run_all_tests.sh" > "$work/base-runner.sh"; then @@ -111,5 +127,56 @@ else checks=$((checks + 1)); echo ' FAIL: mutation failed for a reason other than fragment blindness' printf '%s\n' "$out" | sed 's/^/ /'; bad=1 fi +# A section inherited from the common ancestor is not newly introduced by +# this branch when a later main has moved it out of the inline runner. +fixture="$work/ancestry" +mkdir -p "$fixture/tools" "$fixture/tests/sections" || exit 2 +cp tools/section_fragments_check.sh tools/section_plan.sh tools/runner_text.sh "$fixture/tools/" || exit 2 +printf '%s\n' 'echo "[inherited] existing section"' > "$fixture/tests/run_all_tests.sh" +git -C "$fixture" init -q || exit 2 +git -C "$fixture" config user.name 'Section gate selftest' || exit 2 +git -C "$fixture" config user.email 'selftest@example.invalid' || exit 2 +git -C "$fixture" add . || exit 2 +git -C "$fixture" commit -qm 'common ancestor has inline section' || exit 2 +ancestor=$(git -C "$fixture" rev-parse HEAD) || exit 2 +printf '%s\n' '# section moved into a fragment on later main' > "$fixture/tests/run_all_tests.sh" +git -C "$fixture" commit -qam 'main moved the inherited section' || exit 2 +main_tip=$(git -C "$fixture" rev-parse HEAD) || exit 2 +git -C "$fixture" checkout -q --detach "$ancestor" || exit 2 +printf '%s\n' '# independent branch work' >> "$fixture/tests/run_all_tests.sh" +git -C "$fixture" commit -qam 'branch retains inherited section' || exit 2 +name='ancestry: later main removal does not invent a new branch label' +check bash "$fixture/tools/section_fragments_check.sh" --no-new-inline "$main_tip" +printf '%s\n' 'echo "[newinline] genuinely new inline section"' >> "$fixture/tests/run_all_tests.sh" +inline_rejected() { + local output rc + output=$(bash "$fixture/tools/section_fragments_check.sh" --no-new-inline "$main_tip" 2>&1); rc=$? + [ "$rc" -eq 1 ] && printf '%s\n' "$output" | grep -qF 'FAIL: new inline section label [newinline]' +} +name='ancestry: a genuinely new inline label is still rejected' +check inline_rejected +bad_base_rejected() { + local output rc + output=$(bash "$fixture/tools/section_fragments_check.sh" --no-new-inline nonexistent-selftest-ref 2>&1); rc=$? + [ "$rc" -eq 2 ] && printf '%s\n' "$output" | grep -qF 'cannot resolve base nonexistent-selftest-ref as a commit' +} +name='ancestry: an unreadable base is a loud failure' +check bad_base_rejected +tree=$(git -C "$fixture" rev-parse 'HEAD^{tree}') || exit 2 +unrelated=$(git -C "$fixture" commit-tree "$tree" -m 'unrelated root') || exit 2 +unrelated_base_rejected() { + local output rc + output=$(bash "$fixture/tools/section_fragments_check.sh" --no-new-inline "$unrelated" 2>&1); rc=$? + [ "$rc" -eq 2 ] && printf '%s\n' "$output" | grep -qF 'no merge-base with' +} +name='ancestry: unrelated histories fail loudly' +check unrelated_base_rejected +option_base_rejected() { + local output rc + output=$(bash "$fixture/tools/section_fragments_check.sh" --no-new-inline --all 2>&1); rc=$? + [ "$rc" -eq 2 ] && printf '%s\n' "$output" | grep -qF 'base must name a commit, not an option' +} +name='ancestry: option-like base is rejected' +check option_base_rejected echo "section fragments selftest: checks=$checks failures=$bad" exit "$bad" diff --git a/tools/selftests.txt b/tools/selftests.txt index e6775a98b..de002bdfc 100644 --- a/tools/selftests.txt +++ b/tools/selftests.txt @@ -17,7 +17,7 @@ tests/test_child_exit.sh tests/run_all_tests.sh | timeout 30 bash tests/test_chi tests/test_changelog_fragments.sh tools/changelog_fragments.sh | timeout 30 bash tests/test_changelog_fragments.sh --selftest | [{"pattern":"^ PASS:","count":4},{"pattern":"^ FAIL:","count":0},{"pattern":"^SELFTEST: 4 case\\(s\\) run, 4 passed, 0 failed$","count":1},{"pattern":"^PLANT OK: ","count":1},{"pattern":"^PLANT FAIL:","count":0}] tests/test_dict_keys_mt.sh tests/dict_keys_mt_fixtures/* | timeout 30 bash tests/test_dict_keys_mt.sh --selftest | [{"pattern":" PASS:","count":8},{"pattern":" FAIL:","count":0}] tests/test_doc_examples.py | timeout 30 python3 tests/test_doc_examples.py --selftest | [{"pattern":" selftest ok:","count":29}] -src/fmt.c src/lexer.c tests/fmt_parent_differential.py tests/run_fmt_parent_differential.sh | timeout 60 bash tests/run_fmt_parent_differential.sh --selftest | [{"pattern":"^formatter differential selftest: live comparison passed; normalization plant caught$","count":1},{"pattern":"REGRESSION","count":0}] +src/fmt.c src/lexer.c tests/fmt_parent_differential.py tests/run_fmt_parent_differential.sh tests/phase2/fmt_parent_ref.txt | timeout 60 bash tests/run_fmt_parent_differential.sh --selftest | [{"pattern":"^formatter differential selftest: live comparison passed; normalization plant caught$","count":1},{"pattern":"REGRESSION","count":0}] tests/fmt_parent_differential.py | timeout 30 python3 tests/fmt_parent_differential.py --selftest | [{"pattern":"^classifier selftest: list-item fence is semantic-line preservation$","count":1}] tests/test_doc_examples_markers.py tests/test_doc_examples.py tests/lsan_classify.sh | timeout 30 python3 tests/test_doc_examples_markers.py --selftest tests/test_handles_mt.sh tests/handles_mt_fixtures/* | timeout 30 bash tests/test_handles_mt.sh --selftest | [{"pattern":" PASS:","count":11},{"pattern":" FAIL:","count":0}] @@ -34,9 +34,9 @@ tools/codspeed_unit_guard.py | timeout 30 python3 tools/codspeed_unit_guard.py - tools/consumer_acceptance.sh tools/_derive_variants.py tools/_extract_runcmd.py reports/consumer_acceptance/* | timeout 60 bash tools/consumer_acceptance.sh --self-test tools/core_ext_boundary_check.sh tools/core_ext_boundary_selftest.sh | timeout 120 bash tools/core_ext_boundary_selftest.sh --selftest | [{"pattern":"^PASS: core-ext boundary:","count":1}] tools/docs_claims_check.sh | timeout 30 bash tools/docs_claims_check.sh --selftest | [{"pattern":"^SELFTEST: 19 case\\(s\\) run, 19 passed, 0 failed$","count":1}] -tools/enrolment_check.sh tests/enrolment_exemptions.txt tools/selftests.txt | timeout 420 bash tools/enrolment_check.sh --selftest | [{"pattern":"^ checks=11$","count":1}] +tools/enrolment_check.sh tools/enrolment_edges.py tools/_python_execution.py tools/_derive_variants.py tests/enrolment_exemptions.txt tools/selftests.txt | timeout 420 bash tools/enrolment_check.sh --selftest | [{"pattern":"^ checks=18$","count":1}] Makefile tests/run_all_tests.sh tools/make_lock_check.sh tools/stop_gate.sh tools/tree_lock.sh tools/bounded_process.py | timeout 60 bash tools/make_lock_check.sh --selftest | [{"pattern":"^ PASS:","count":9},{"pattern":"^PLAN: [0-9]+$","count":1,"pass":"^ PASS:","fail":"^ FAIL:"},{"pattern":"FAIL:","count":0}] -tests/fixtures/jit_diff_timeout_both.eigs tools/jit_diff.sh | timeout 90 env JIT_DIFF_OBS_ONLY=tests/fixtures/jit_diff_timeout_both.eigs bash tools/jit_diff.sh --selftest | [{"pattern":"tests/fixtures/jit_diff_timeout_both\\.eigs OBS: timeout on both sides$","count":1},{"pattern":"^jit_diff: OBS examined=0 denied=0 timeout-both=1$","count":1}] +tests/fixtures/jit_diff_timeout_both.eigs tests/fixtures/jit_diff_taped_divergence.eigs tests/fixtures/jit_diff_gate_nondeterministic.eigs tests/fixtures/jit_diff_agreement.eigs tools/jit_diff.sh | timeout 190 env JIT_DIFF_SELFTEST_SUITE=1 bash tools/jit_diff.sh --selftest | [{"pattern":"tests/fixtures/jit_diff_timeout_both\\.eigs OBS: timeout on both sides$","count":1},{"pattern":"^jit_diff: OBS examined=0 denied=0 timeout-both=1$","count":1},{"pattern":"^tests/fixtures/jit_diff_taped_divergence\\.eigs OBS$","count":1},{"pattern":"^tests/fixtures/jit_diff_gate_nondeterministic\\.eigs OBS COULD_NOT_ADJUDICATE$","count":1},{"pattern":"^test_chunk_verify_stack\\.eigs OBS$","count":1},{"pattern":"^jit_diff: OBS examined=1 denied=0 timeout-both=0$","count":4},{"pattern":"^jit_diff: NOTE: ledgered COULD_NOT_ADJUDICATE row absent \\(pair agreed\\): tests/fixtures/jit_diff_agreement\\.eigs OBS COULD_NOT_ADJUDICATE$","count":1},{"pattern":"^jit_diff: PASS: absent ordinary ledger row remains stale$","count":1},{"pattern":"^jit_diff: OK \\(0 programs x \\{jit, osr\\} vs the interpreter; suite-env completed=0; OBS examined=1 denied=0; 0 arms adjudicated; 0 could not adjudicate; 1 ledgered\\)$","count":1}] tools/obs_marker_check.sh | timeout 30 bash tools/obs_marker_check.sh --selftest tools/obs_reader_sync_check.sh tools/obs_marker_check.sh | timeout 30 bash tools/obs_reader_sync_check.sh --selftest | [{"pattern":"^SELFTEST: ([0-9]+) passed.*$","count":11,"floor":true,"value":true}] tools/performance_observer_docs.py docs/PERFORMANCE.md bench/observer_callgrind.txt bench/baseline.txt bench/observed_loop.eigs bench/unobserved_loop.eigs bench/conservative_observed_loop.eigs bench/conservative_unobserved_loop.eigs | timeout 30 python3 tools/performance_observer_docs.py --selftest @@ -61,6 +61,14 @@ tools/strict_differential.sh tests/lsan_classify.sh | timeout 30 bash tools/stri tools/spec_lean_parity.py | ci-step spec-lean python3 tools/spec_lean_parity.py --selftest | - tools/selftests.sh tools/selftests_selection_selftest.sh | timeout 30 bash tools/selftests_selection_selftest.sh --selftest | [{"pattern":"^ PASS:","count":11},{"pattern":"^ FAIL:","count":0},{"pattern":"^SELFTEST_SELECTION: 11 passed, 0 failed, 11 declared$","count":1}] tools/observer_oracle/* | timeout 600 python3 tools/observer_oracle/plants.py --selftest | [{"pattern":"^RED floor-each ","count":1},{"pattern":"^plants: 1 examined, 1 red \\(controls included: green is their red\\)$","count":1}] +# #1758 Phase 2: the row checker's calibration (5 release builds: v0.46.0 and four planted binaries; 662 s on +# the 4 GB dev box) and the slot test's planted faults (two -O0 slot-test builds per plant; 446 s there). +# Keyed on the checkers, never on src/ (#1352). +tools/phase2_check.py tools/phase2_check.sh tools/phase2_corpus_diff.sh tools/phase2_inventory.py tests/phase2/calib/* tests/phase2/corpus_nonzero.txt tests/suite_program_env.sh tests/suite_program_env.txt | timeout 3600 python3 -W error tools/phase2_check.py --selftest | [{"pattern":"^calibration: cases=55 as-expected=55 unexpected=0$","count":1}] +tools/phase2_plants.sh tests/phase2/slot_layer.sh tests/phase2/slot_layer.c tools/phase2_inventory.py | timeout 1500 bash tools/phase2_plants.sh --selftest | [{"pattern":"^plants=5 killed=5 survived=0 broken=0 transverse=5$","count":1}] +tools/phase2_cost.sh | timeout 30 bash tools/phase2_cost.sh --self-test | [{"pattern":"^phase2-cost-self-test: positive=6 rejected=15 strict=PASS elapsed=PASS PASS$","count":1}] +tools/phase2_float_amend.py tests/phase2/r17_float_text.json | timeout 30 python3 tools/phase2_float_amend.py --self-test | [{"pattern":"^float-amend-self-test: positive=1 rejected=11 PASS$","count":1}] +tools/phase2_cost.sh tools/phase2_cost_declarations.py bench/check_regression.sh tests/phase2/test_cost_methods.py | timeout 30 python3 tests/phase2/test_cost_methods.py --self-test tools/verdict_propagation_calibration.sh tests/sections/99aae-verdict-propagation-2059.sh tests/test_verdict_propagation_plant.eigs lib/test.eigs | timeout 60 bash tools/verdict_propagation_calibration.sh --selftest | [{"pattern":"^PASS: verdict-propagation calibration plant was detected$","count":1},{"pattern":"^SELFTEST FAIL","count":0}] tools/child_plan_calibration.sh tests/sections/99aaf-child-plan-2051.sh tests/test_child_plan_calibration.sh tests/run_all_tests.sh | timeout 60 bash tools/child_plan_calibration.sh --selftest | [{"pattern":"^ PASS: .* plant turned its real suite section red$","count":2},{"pattern":"^PLAN: [0-9]+$","count":1,"pass":"^ PASS:","fail":"^ FAIL:"},{"pattern":"^ FAIL:","count":0}] tools/selftests.sh tools/child_plan.sh tools/selftests_plan_calibration.sh | timeout 30 bash tools/selftests_plan_calibration.sh --selftest | [{"pattern":"^ PASS: .* plant failed selftests child-plan verdict$","count":2},{"pattern":"^PLAN: [0-9]+$","count":1,"pass":"^ PASS:","fail":"^ FAIL:"},{"pattern":"^ FAIL:","count":0}] diff --git a/tools/spec_lean_parity.py b/tools/spec_lean_parity.py index 7fd402020..31f718cf8 100755 --- a/tools/spec_lean_parity.py +++ b/tools/spec_lean_parity.py @@ -68,7 +68,7 @@ def replay_refusal_cases(ref): # A V header is a hard session boundary. Neither implementation may scan # into the following session for an otherwise matching N record. sessions=td/'sessions.tape';sessions.write_text( - 'V 8 0.47.0\nB 0 1 1 0 root -\nV 8 0.47.0\nB 0 1 1 0 root -\nN 0 random_int=1\n') + 'V 9 0.47.0\nB 0 1 1 0 root -\nV 9 0.47.0\nB 0 1 1 0 root -\nN 0 random_int=1\n') c=subprocess.run([VM,source],capture_output=True,input=b'',env={**os.environ,'EIGS_JIT_OFF':'1','EIGS_REPLAY':str(sessions),'EIGS_REPLAY_STRICT':'1'}) l=subprocess.run([ref,'--replay',sessions,'--strict',source],capture_output=True,input=b'') message=b'random_int: replay stream 0 has no matching recorded N value' diff --git a/tools/strict_differential.sh b/tools/strict_differential.sh index bc8494f9b..4c0d82038 100755 --- a/tools/strict_differential.sh +++ b/tools/strict_differential.sh @@ -735,7 +735,12 @@ audio_envelope|print of (len of (audio_envelope of [([0.1, 0.2]), "0.01", 0.01, audio_gain|print of (len of (audio_gain of [([1.0]), "2.0"])) json_path|print of (json_path of ["{\"a\": 1e", "a"])|json_path: invalid JSON at position pow|print of (pow of [0 - 8, 0.5])|pow: result is not a number -num|print of (num of "nan")|num: result is not a number +min|print of (min of [])|min: expected at least one number +min|print of (min of ([]))|min: expected at least one number +max|print of (max of [])|max: expected at least one number +max|print of (max of ([]))|max: expected at least one number +num|print of (num of "nan")|num: expected complete numeric-literal text +num|print of (num of "inf")|num: expected complete numeric-literal text f64_from_bytes|print of (f64_from_bytes of ([127, 248, 0, 0, 0, 0, 0, 0]))|f64_from_bytes: result is not a number matmul|local m1 is buffer of [1, 2]\nm1[0] is 1e200\nm1[1] is 1e200\nlocal m2 is buffer of [2, 1]\nm2[0] is 1e200\nm2[1] is 0 - 1e200\nlocal r is matmul of [m1, m2]\nprint of (r[0])|matmul: result is not a number matmul|print of (matmul of [[[1, "x"]], [[1], [1]]])|matmul: expected a tensor containing only numbers @@ -797,6 +802,7 @@ EOF ) PROBES="${PROBES//@TMP@/$TMP}" +# N10 removed inf from the successful coercion pins; it is a grammar refusal above. # label|program — must exit 0 under EIGS_STRICT=1 PINS=$(cat <<'EOF' task_alive of an unknown id is 0, not an error|print of (task_alive of 999) @@ -807,7 +813,6 @@ substr starting past the end is ""|print of (substr of ["ab", 9, 1]) join of an empty list is ""|print of (join of [[], ","]) num coerces a list to 0 (documented)|print of (num of ([1, 2])) try_parse of invalid syntax is 0|print of (try_parse of "!!!") -max of an empty list is 0|print of (max of ([])) sum of an empty list is 0 (the identity, not a type mistake)|print of (sum of ([])) mean of an empty list is 0|print of (mean of ([])) gather of a 1-D tensor in the per-row form is 0 per row (shape, not index)|print of (gather of [[1, 2], [0, 0]]) @@ -823,7 +828,6 @@ json_path of a JSON null renders as ""|print of f"[{json_path of ["{\"a\": null} file_exists of a real absent path is 0 (#1008 Phase D)|print of (file_exists of "/nonexistent/eigs_971_probe") is_dir of a real absent path is 0|print of (is_dir of "/nonexistent/eigs_971_probe") index_of miss is -1 even with the flag on|print of (index_of of ["abc", "z"]) -num of "inf" saturates (overflow, not NaN)|print of (num of "inf") pow of a negative base with an INTEGER exponent is defined|print of (pow of [0 - 2, 3]) token_name of an unknown id is "?"|print of (token_name of 9999) channel_closed of a reclaimed/unknown channel is 1|print of (channel_closed of ({"_channel_id": 99999})) diff --git a/tools/strict_shape_contracts.json b/tools/strict_shape_contracts.json index 3ff43a793..038ff936b 100644 --- a/tools/strict_shape_contracts.json +++ b/tools/strict_shape_contracts.json @@ -137,20 +137,20 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum.", + "reason": "Whole sample list/buffer data has no fixed tuple maximum. R1.6e N13 reviews the changed audio_convert_samples closure: finite continuous samples clamp and round; stored NaN/infinity refuse at the data boundary, independently of payload length.", "public_names": [ "audio_play" ], "disposition": "exempt", "body_sha256": [ - "ea799d0a7d251e8cd8d5b8eaad7735e5ea03c600b026a57d4f41e40b80a4a34b" + "3ab967bbd1c8275d133bb159c9f7bc0b07f122b1b701af0f8aa143a1957fe77d" ], "source_sha256": { "audio_convert_samples": [ - "01a118dbda254e0065f13d68f234dbfa5b3673fac1a68f711cd6c6fc9ca9cb77" + "d0eb9a7c748b74dab12ca7bebbb9d68c931d3e076b0943400c7523b5ae966397" ], "builtin_audio_play": [ - "ea799d0a7d251e8cd8d5b8eaad7735e5ea03c600b026a57d4f41e40b80a4a34b" + "3ab967bbd1c8275d133bb159c9f7bc0b07f122b1b701af0f8aa143a1957fe77d" ], "gfx_bad_samples": [ "06b184f2d2c365ae61d277905810ed9d7257d7f2c81d8a13e26d90d0b9b6a23c" @@ -223,7 +223,7 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum.", + "reason": "Whole sample list/buffer data has no fixed tuple maximum. R1.6e N13 reviews the changed audio_convert_samples closure: finite continuous samples clamp and round; stored NaN/infinity refuse at the data boundary, independently of payload length.", "public_names": [ "audio_stream_push" ], @@ -233,7 +233,7 @@ ], "source_sha256": { "audio_convert_samples": [ - "01a118dbda254e0065f13d68f234dbfa5b3673fac1a68f711cd6c6fc9ca9cb77" + "d0eb9a7c748b74dab12ca7bebbb9d68c931d3e076b0943400c7523b5ae966397" ], "builtin_audio_stream_push": [ "cd6347275f699b2ee2d7079e39972b43c9c705f566185907b9831dae2978282b" @@ -381,17 +381,17 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "List is the whole value/data input, not a bounded tuple.", + "reason": "List is the whole value/data input, not a bounded tuple. R1.6d review: Q2 float conversion and error cleanup consume the complete input list; no tuple-width change.", "public_names": [ "buf_from_list" ], "disposition": "exempt", "body_sha256": [ - "c45c354c5cc6390200cb91b736b185b44af3d20728c0d57869e24e4d0f79a1d9" + "62d524fce828a136c69d911f201bf63239c7d1630d60c8500611884a5d4f816f" ], "source_sha256": { "builtin_buf_from_list": [ - "c45c354c5cc6390200cb91b736b185b44af3d20728c0d57869e24e4d0f79a1d9" + "62d524fce828a136c69d911f201bf63239c7d1630d60c8500611884a5d4f816f" ] } }, @@ -497,20 +497,23 @@ "category": "exact_strict_rejection", "minimum": 2, "maximum": 2, - "reason": "Only two numeric list elements select shaped buffer; other lists already raise via STRICT_REQUIRE, strict-off legacy empty buffer retained.", + "reason": "Only two numeric list elements select shaped buffer; other lists already raise via STRICT_REQUIRE, strict-off legacy empty buffer retained. R1.6d review: N5 exact dimensions retain the existing exactly-two numeric list recognition and strict-off other-shape behavior.", "public_names": [ "buffer" ], "disposition": "exempt", "body_sha256": [ - "57cb0537fbd5bc20702d3c01759e2fb51829e2218925a950d203725c736932db" + "2edb03734cf4aac5f813ffbfc63811134f98840f6d3a998f2d1e1e4c5f2e669c" ], "source_sha256": { "builtin_buffer": [ - "57cb0537fbd5bc20702d3c01759e2fb51829e2218925a950d203725c736932db" + "2edb03734cf4aac5f813ffbfc63811134f98840f6d3a998f2d1e1e4c5f2e669c" + ], + "eigs_int_arg_range": [ + "6c64833ddf01abc9ae34c664470f2f9bdb3d6196a4c240aea29df622cfdeb288" ], - "eigs_list_num": [ - "9f81cf3349e551156657ba9cf52b16170875dbce7bbbca4ac6f076ea5a72423d" + "eigs_list_int_range": [ + "890b2394e8acf3972d7046eb0ad5aec3b8ea161fc315818f9cab0a7f617d6ad8" ] } }, @@ -635,7 +638,7 @@ "4cf207881dd2dd1bbc61735eb3ffa36eaad8a5acd8d918cda47413a6d99ff2b6" ], "db_build_query": [ - "63ab23cab48b08750ac19498dbbca46ad729824ed205a08c20839575c352e3e7" + "5781874e0f84f785f5d7fc56a3ec858561e023357c4b860a72b6f820b4693aea" ], "db_exec_from_arg": [ "f91b6f1b3f2081c538ed33c1217ee62162003947bee6442cd51f8921d9f4805c" @@ -654,14 +657,14 @@ ], "disposition": "exempt", "body_sha256": [ - "489467a3f39ce6728624d84b7d9b20ce7d5ec8415fa6d2a9d3ae7d9270925957" + "e95fd32ee88cbcb6b32fee954dbcf9718bca4962947d7e5ec923b62ef9d6325d" ], "source_sha256": { "builtin_db_query_json": [ - "489467a3f39ce6728624d84b7d9b20ce7d5ec8415fa6d2a9d3ae7d9270925957" + "e95fd32ee88cbcb6b32fee954dbcf9718bca4962947d7e5ec923b62ef9d6325d" ], "db_build_query": [ - "63ab23cab48b08750ac19498dbbca46ad729824ed205a08c20839575c352e3e7" + "5781874e0f84f785f5d7fc56a3ec858561e023357c4b860a72b6f820b4693aea" ], "db_exec_from_arg": [ "f91b6f1b3f2081c538ed33c1217ee62162003947bee6442cd51f8921d9f4805c" @@ -680,14 +683,14 @@ ], "disposition": "exempt", "body_sha256": [ - "3908eda08837e16a96c0e0a8f8f8395d07bde686123cc8f997e4d0dadb4f4c3e" + "57429b5195fcbfed06b39ef5ba9c9a3ad42639e47162a1609c746110bf298ac4" ], "source_sha256": { "builtin_db_query_value": [ - "3908eda08837e16a96c0e0a8f8f8395d07bde686123cc8f997e4d0dadb4f4c3e" + "57429b5195fcbfed06b39ef5ba9c9a3ad42639e47162a1609c746110bf298ac4" ], "db_build_query": [ - "63ab23cab48b08750ac19498dbbca46ad729824ed205a08c20839575c352e3e7" + "5781874e0f84f785f5d7fc56a3ec858561e023357c4b860a72b6f820b4693aea" ], "db_exec_from_arg": [ "f91b6f1b3f2081c538ed33c1217ee62162003947bee6442cd51f8921d9f4805c" @@ -784,20 +787,20 @@ "category": "variadic", "minimum": null, "maximum": null, - "reason": "Whole string list is argv; exactly [argv-list, timeout] is alternate form. Rejecting all >2 breaks valid commands.", + "reason": "Whole string list is argv; exactly [argv-list, timeout] is alternate form. Rejecting all >2 breaks valid commands. Reviewed R1.6c (#1758): numeric readers changed; outer argument shape is unchanged. R1.6z reviewed repin: only the retired constructor spelling in a comment changes (make_num to make_float); executable C and the whole-argument helper closure are unchanged.", "public_names": [ "exec_capture" ], "disposition": "exempt", "body_sha256": [ - "930595270b5c7e68102042960102a80180c87d174545052d5c6e685691ad210f" + "7814f2bb43b8dce29b6bfa35c4c28337ecb1f5aea918a2881f69bb8312706f05" ], "source_sha256": { "builtin_exec_capture": [ - "930595270b5c7e68102042960102a80180c87d174545052d5c6e685691ad210f" + "7814f2bb43b8dce29b6bfa35c4c28337ecb1f5aea918a2881f69bb8312706f05" ], - "eigs_list_num": [ - "9f81cf3349e551156657ba9cf52b16170875dbce7bbbca4ac6f076ea5a72423d" + "eigs_list_float": [ + "b2a4c2d694e7b0e66e6f5b0f13b5df946ace858226f059f88a4af52bcbfc0f93" ] } }, @@ -819,17 +822,20 @@ "category": "unary_data_prefix", "minimum": null, "maximum": null, - "reason": "Documented first eight bytes of list OR buffer; prefix byte decoding is data semantics, not argument arity. Explicitly exempt from this tuple repair (BUILTINS.md:208).", + "reason": "Documented first eight bytes of list OR buffer; prefix byte decoding is data semantics, not argument arity. Explicitly exempt from this tuple repair (BUILTINS.md:208). R1.6d review: Q1 validates each consumed encoded byte; the documented first-eight prefix and ignored trailing data remain.", "public_names": [ "f64_from_bytes" ], "disposition": "exempt", "body_sha256": [ - "5f12865bdbf33058b8e42e22e6f23c4e957da26f62ee09de00b881a73cfd432d" + "86a5692f56156bb40da44b64258db1e828e59f5fa0862787480a212667861415" ], "source_sha256": { + "buffer_byte_at": [ + "954a4c009acced1938aa3c2b2aad8010e57b8d4c73372cc98d1500deaad3a9cd" + ], "builtin_f64_from_bytes": [ - "5f12865bdbf33058b8e42e22e6f23c4e957da26f62ee09de00b881a73cfd432d" + "86a5692f56156bb40da44b64258db1e828e59f5fa0862787480a212667861415" ] } }, @@ -851,17 +857,17 @@ "category": "exact_rejection", "minimum": 2, "maximum": 3, - "reason": "Exact 2/3 branches; other lengths raise.", + "reason": "Exact 2/3 branches; other lengths raise. Reviewed R1.6c (#1758): numeric readers changed; outer argument shape is unchanged.", "public_names": [ "get_at" ], "disposition": "exempt", "body_sha256": [ - "bfff841ee81514af4584b2f305e60ae405ebc7e245ff8fe724007ff16677a04d" + "aec54f2ab04e9c4f7036ee33c2c4857fc48448467bde2294c0553269f070f3e5" ], "source_sha256": { "builtin_get_at": [ - "bfff841ee81514af4584b2f305e60ae405ebc7e245ff8fe724007ff16677a04d" + "aec54f2ab04e9c4f7036ee33c2c4857fc48448467bde2294c0553269f070f3e5" ] } }, @@ -1051,20 +1057,20 @@ "category": "exact_rejection", "minimum": 2, "maximum": 2, - "reason": "List overload requires exactly port/path; numeric/null scalar form remains.", + "reason": "List overload requires exactly port/path; numeric/null scalar form remains. R1.6f: reviewed exact list rejection after numeric-reader migration; tuple arity and scalar/optional forms are unchanged.", "public_names": [ "http_early_bind" ], "disposition": "exempt", "body_sha256": [ - "75b902a6ab9ba175be7d051448b761f66da9453dcb7843d16e3e5c25d46efe4d" + "018483db44d526e5a75f95440160e90a1546d7e5e6096d7d940a78e94a9fe4c2" ], "source_sha256": { "builtin_http_early_bind": [ - "75b902a6ab9ba175be7d051448b761f66da9453dcb7843d16e3e5c25d46efe4d" + "018483db44d526e5a75f95440160e90a1546d7e5e6096d7d940a78e94a9fe4c2" ], - "eigs_opt_num": [ - "9725bffff66601bf6a82c4b5e69281c6109e3277efbdd751fd9390bdbce6727e" + "eigs_int_arg_range": [ + "6c64833ddf01abc9ae34c664470f2f9bdb3d6196a4c240aea29df622cfdeb288" ] } }, @@ -1154,7 +1160,7 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Whole list is byte data; strict element type validation does not impose a tuple arity. Text consumes through numeric NUL; codecs consume the complete list.", + "reason": "Whole list is byte data; strict element type validation does not impose a tuple arity. Text consumes through numeric NUL; codecs consume the complete list. R1.6d review: Q1 validates all compressed input bytes; the complete list remains byte data rather than a fixed tuple.", "public_names": [ "inflate" ], @@ -1164,15 +1170,18 @@ "a3fc2607ff39bc3c2f9e41a02467c751cb5b171ddf2e46c829c267143a5f2950" ], "source_sha256": { + "buffer_byte_at": [ + "954a4c009acced1938aa3c2b2aad8010e57b8d4c73372cc98d1500deaad3a9cd" + ], "builtin_inflate": [ "13a5b43ee3f28d3a0d1a79c9804eff17c926297a0bac2c784cfe1abd39d01c51", "a3fc2607ff39bc3c2f9e41a02467c751cb5b171ddf2e46c829c267143a5f2950" ], "strict_numeric_byte_list": [ - "e271c965b69981e5f8e2ddf71b4f25220dd7d491135e67d8d1ada1d680bcbccc" + "27efb919189b608ba9d9b30a0f4df0ae568d9f9ec25afa4f4ca12307a3cd8487" ], "zlib_bytes_arg": [ - "a20d6431a2a031d5f01ba74a3cbc5dd6dd28541106d0a008d174042844aa1ad9" + "8c966dfa58791ec6da82c4649e718a4ecbd4af86481d9e200fb08c69a4c2484f" ], "zlib_inflate_impl": [ "39c9ed3051f3b2bce8578f00f212463c209ce29ddb530ba531802253ef7b4c96" @@ -1203,11 +1212,11 @@ ], "disposition": "exempt", "body_sha256": [ - "895b607a44f311b0910c4111ede8af5385dd8c947a93ffac24671c2931c7c026" + "2338c9131d18a6ec7cdc2635090cf31a46bf3222e803d8f001c4fca143d0ae0f" ], "source_sha256": { "builtin_json_build": [ - "895b607a44f311b0910c4111ede8af5385dd8c947a93ffac24671c2931c7c026" + "2338c9131d18a6ec7cdc2635090cf31a46bf3222e803d8f001c4fca143d0ae0f" ] } }, @@ -1230,7 +1239,7 @@ "3d31ab37b9f8ae841e1676582d4fd67faa2920584ca29fcb51d140bfa8b00e6f" ], "eigs_json_encode_value": [ - "9d0c4bc6ee9e5e2e998168d1dc5df09020ee0acd9848b18d20edf22a02f26608" + "0b1e6bf310daa2f5866abed6569c3a3cd7e00706352cec39ecaa70d66a6f2c82" ] } }, @@ -1281,11 +1290,11 @@ ], "disposition": "exempt", "body_sha256": [ - "65efba69b67175c6be83e386f9f77ab4db6da9aeab72c4b59533eb8d81ab42f8" + "f22ffba4405a23b85d1b8c946594379016d32b09a274ed3a5bac6920d376f95b" ], "source_sha256": { "builtin_len": [ - "65efba69b67175c6be83e386f9f77ab4db6da9aeab72c4b59533eb8d81ab42f8" + "f22ffba4405a23b85d1b8c946594379016d32b09a274ed3a5bac6920d376f95b" ], "eigs_module_ns_sync": [ "89a5440e7753fd7b8b20da8c57b1e135dbb89f726777035c3f0ebee65bf59fb7" @@ -1426,8 +1435,11 @@ "builtin_max": [ "2175b6fc725553d83e745e380ecddc20ad81175878ea6d2854231e8bacdeea44" ], + "eigs_number_arg": [ + "2ae593a62bd4ef4535573650992442d5e920307ca1b2a1ff23c8306d755eb6de" + ], "minmax_reduce": [ - "fb3260dd7be09f46d9dba30ecd6f70c3a3da01b39e3f342add20836e16d221fd" + "542146c9ec341089bb259e26efddf501dd14d2345959a30b33865095a0730b95" ] } }, @@ -1449,8 +1461,11 @@ "builtin_min": [ "aa04459f66b91bc896e90d66cbef455dc88f9637cdc9c81d8841cd5f64ffdfe6" ], + "eigs_number_arg": [ + "2ae593a62bd4ef4535573650992442d5e920307ca1b2a1ff23c8306d755eb6de" + ], "minmax_reduce": [ - "fb3260dd7be09f46d9dba30ecd6f70c3a3da01b39e3f342add20836e16d221fd" + "542146c9ec341089bb259e26efddf501dd14d2345959a30b33865095a0730b95" ] } }, @@ -1528,26 +1543,26 @@ "category": "exact_rejection", "minimum": 2, "maximum": 2, - "reason": "net_require_list(2,2) on list overload; scalar handle also valid.", + "reason": "net_require_list(2,2) on list overload; scalar handle also valid. R1.6f: reviewed exact list rejection after numeric-reader migration; tuple arity and scalar/optional forms are unchanged.", "public_names": [ "net_accept" ], "disposition": "exempt", "body_sha256": [ - "25d612383126cbd0518e4a4255ff1c204b3c95545ab92469b10c361786125981" + "82ebad6080c69a224b2f84b13904f019de14e247da3996033cb2fee222227ffe" ], "source_sha256": { "builtin_net_accept": [ - "25d612383126cbd0518e4a4255ff1c204b3c95545ab92469b10c361786125981" + "82ebad6080c69a224b2f84b13904f019de14e247da3996033cb2fee222227ffe" ], - "net_lookup": [ - "b7c5760787594aad4ec34d3eb3d5339e6a0bb317e153eac4687223fdff69e618" - ], - "net_num_at": [ - "d55a8eaf22d6cc93637730627247269e559fd7b0459aac2c6cef2994361c595e" + "eigs_int_arg": [ + "4adef0d09678cfe76f30b2b9cd882e775a9d48ef7f30665102169c2e5ccae1aa" ], "net_require_list": [ "90e3f1361bcc52f17a0accbf87ff18b8f941f333a1050dc60f60e99dda9f4164" + ], + "net_timeout_at": [ + "170fc2f6a6ce6cc2fdb8823981690fa6d28e197b1e243868c7dac2ece1a18cfa" ] } }, @@ -1557,23 +1572,26 @@ "category": "exact_rejection", "minimum": 2, "maximum": 3, - "reason": "net_require_list(2,3) rejects surplus in every mode; optional timeout.", + "reason": "net_require_list(2,3) rejects surplus in every mode; optional timeout. R1.6f: reviewed exact list rejection after numeric-reader migration; tuple arity and scalar/optional forms are unchanged.", "public_names": [ "net_dial" ], "disposition": "exempt", "body_sha256": [ - "a4ae095a389638dc862a3587dd19ebc3d215dc3531440803219f1c8fdaf3ff6e" + "05a2c97b1c37c0a3b8d25288b84adb9850803b99421232cdc601662d74662833" ], "source_sha256": { "builtin_net_dial": [ - "a4ae095a389638dc862a3587dd19ebc3d215dc3531440803219f1c8fdaf3ff6e" + "05a2c97b1c37c0a3b8d25288b84adb9850803b99421232cdc601662d74662833" ], - "net_num_at": [ - "d55a8eaf22d6cc93637730627247269e559fd7b0459aac2c6cef2994361c595e" + "eigs_list_int_range": [ + "890b2394e8acf3972d7046eb0ad5aec3b8ea161fc315818f9cab0a7f617d6ad8" ], "net_require_list": [ "90e3f1361bcc52f17a0accbf87ff18b8f941f333a1050dc60f60e99dda9f4164" + ], + "net_timeout_at": [ + "170fc2f6a6ce6cc2fdb8823981690fa6d28e197b1e243868c7dac2ece1a18cfa" ] } }, @@ -1583,23 +1601,26 @@ "category": "exact_rejection", "minimum": 2, "maximum": 3, - "reason": "net_require_list(2,3) rejects surplus in every mode; optional timeout.", + "reason": "net_require_list(2,3) rejects surplus in every mode; optional timeout. R1.6f: reviewed exact list rejection after numeric-reader migration; tuple arity and scalar/optional forms are unchanged.", "public_names": [ "net_recv" ], "disposition": "exempt", "body_sha256": [ - "47b4ad9023aca053634a5cd5ee4224f218e1a7b3b41c167bcea36bf62d13db8a" + "0519f32fd0cc22b7b6fb3489de377365138d764ab94aaa6d57b0655f0999ab2f" ], "source_sha256": { "builtin_net_recv": [ - "47b4ad9023aca053634a5cd5ee4224f218e1a7b3b41c167bcea36bf62d13db8a" + "0519f32fd0cc22b7b6fb3489de377365138d764ab94aaa6d57b0655f0999ab2f" ], - "net_num_at": [ - "d55a8eaf22d6cc93637730627247269e559fd7b0459aac2c6cef2994361c595e" + "net_count_at": [ + "5b0ea0900672f5061670fd704a03b05348d6d3a7c726b778d73daa7037428baa" ], "net_require_list": [ "90e3f1361bcc52f17a0accbf87ff18b8f941f333a1050dc60f60e99dda9f4164" + ], + "net_timeout_at": [ + "170fc2f6a6ce6cc2fdb8823981690fa6d28e197b1e243868c7dac2ece1a18cfa" ] } }, @@ -1609,17 +1630,17 @@ "category": "exact_rejection", "minimum": 2, "maximum": 2, - "reason": "net_require_list(2,2) before tape/socket work.", + "reason": "net_require_list(2,2) before tape/socket work. R1.6f: reviewed exact list rejection after numeric-reader migration; tuple arity and scalar/optional forms are unchanged.", "public_names": [ "net_send" ], "disposition": "exempt", "body_sha256": [ - "adafc473adbba2ab8fbdba59d748f171efd7ad87077de9a1e8d938dc5dbd33d8" + "d6a373148f5e94eb39e1ffcb73f47d437125aa82bbdb51bd53bdad3d661eefd8" ], "source_sha256": { "builtin_net_send": [ - "adafc473adbba2ab8fbdba59d748f171efd7ad87077de9a1e8d938dc5dbd33d8" + "d6a373148f5e94eb39e1ffcb73f47d437125aa82bbdb51bd53bdad3d661eefd8" ], "net_require_list": [ "90e3f1361bcc52f17a0accbf87ff18b8f941f333a1050dc60f60e99dda9f4164" @@ -1705,7 +1726,7 @@ "f5dca9f11e0c4cee680a747e2a6af8f32baa59e2b15f8b305e546c74847e2cca" ], "value_to_string": [ - "269292118bde6226411ea5a201cc73ae9a5d81dc010f8caca57118e4b8257f70" + "541c34dfd1dbe93f2ca39b800321afb116be9c74d7d8e35b297502a157a2e1fd" ] } }, @@ -1895,17 +1916,17 @@ "category": "exact_rejection", "minimum": 3, "maximum": 4, - "reason": "Exact 3/4 branches; other lengths raise. Nested list/buffer data is not tuple width.", + "reason": "Exact 3/4 branches; other lengths raise. Nested list/buffer data is not tuple width. Reviewed R1.6c (#1758): numeric readers changed; outer argument shape is unchanged.", "public_names": [ "set_at" ], "disposition": "exempt", "body_sha256": [ - "1f465449b8e6c57f14b1d07b0cb389c090666dd78d77b40ea7baba52ec4cf521" + "ad8734cf3a49fc6f8c0623409fcaf62b3bc7249b08dfaa293b449f56519733d2" ], "source_sha256": { "builtin_set_at": [ - "1f465449b8e6c57f14b1d07b0cb389c090666dd78d77b40ea7baba52ec4cf521" + "ad8734cf3a49fc6f8c0623409fcaf62b3bc7249b08dfaa293b449f56519733d2" ] } }, @@ -1993,11 +2014,11 @@ ], "disposition": "exempt", "body_sha256": [ - "7b7068ffe4f1906946d5187541e9115ab151a7621cf6ef257f28be438eff80c4" + "c308177b6ce236122390a1956e97adf6199ea7431dd7132539dc3579652af2a4" ], "source_sha256": { "builtin_sort": [ - "7b7068ffe4f1906946d5187541e9115ab151a7621cf6ef257f28be438eff80c4" + "c308177b6ce236122390a1956e97adf6199ea7431dd7132539dc3579652af2a4" ] } }, @@ -2025,11 +2046,11 @@ ], "disposition": "exempt", "body_sha256": [ - "e85e17e5f0c2e7dd74d6f3b1499208ca01307c8ab5c444adfba8f94ba8c598f6" + "8f68ccddf114c1b379a1d36dc57ec727e740264b25dc8c1f3e30868247c88c5b" ], "source_sha256": { "builtin_spawn": [ - "e85e17e5f0c2e7dd74d6f3b1499208ca01307c8ab5c444adfba8f94ba8c598f6" + "8f68ccddf114c1b379a1d36dc57ec727e740264b25dc8c1f3e30868247c88c5b" ] } }, @@ -2160,7 +2181,7 @@ "1f942006a4fdfa43f5447114d478b219bc60142b61f4585660944fc9a933ef61" ], "value_to_string": [ - "269292118bde6226411ea5a201cc73ae9a5d81dc010f8caca57118e4b8257f70" + "541c34dfd1dbe93f2ca39b800321afb116be9c74d7d8e35b297502a157a2e1fd" ] } }, @@ -2170,20 +2191,23 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Whole list is byte data; strict element type validation does not impose a tuple arity. Text consumes through numeric NUL; codecs consume the complete list.", + "reason": "Whole list is byte data; strict element type validation does not impose a tuple arity. Text consumes through numeric NUL; codecs consume the complete list. R1.6d review: Q1 validates byte cells through the existing NUL termination boundary; no tuple limit.", "public_names": [ "str_from_bytes" ], "disposition": "exempt", "body_sha256": [ - "491909802276ad7bbf448b7fd0d1a7c4249c6dc448bf2ff435b261e7dd7c8942" + "b038f331d1de71baf8f863a07e202547e5f1a0dbd96e837508d56ceaabd93743" ], "source_sha256": { + "buffer_byte_at": [ + "954a4c009acced1938aa3c2b2aad8010e57b8d4c73372cc98d1500deaad3a9cd" + ], "builtin_str_from_bytes": [ - "491909802276ad7bbf448b7fd0d1a7c4249c6dc448bf2ff435b261e7dd7c8942" + "b038f331d1de71baf8f863a07e202547e5f1a0dbd96e837508d56ceaabd93743" ], "strict_numeric_byte_list": [ - "e271c965b69981e5f8e2ddf71b4f25220dd7d491135e67d8d1ada1d680bcbccc" + "27efb919189b608ba9d9b30a0f4df0ae568d9f9ec25afa4f4ca12307a3cd8487" ] } }, @@ -2247,11 +2271,11 @@ ], "disposition": "exempt", "body_sha256": [ - "9f0d31d8976c9edb8ea910358826f6bc006aa03b418399d1e4fba8737bb99798" + "60f439671a849649e20722a8a6e03059e01a41e2eb6c9d5921b707d8b7f242cf" ], "source_sha256": { "builtin_task_spawn": [ - "9f0d31d8976c9edb8ea910358826f6bc006aa03b418399d1e4fba8737bb99798" + "60f439671a849649e20722a8a6e03059e01a41e2eb6c9d5921b707d8b7f242cf" ] } }, @@ -2298,7 +2322,7 @@ "55f0129b970addd257aa61f5c0f87c0604d2534ad5cbf5404b2f0504678bdaf5" ], "tensor_unary": [ - "04fd54c5ccf01c248491a72c9ced77c1c5229d02584040585435fb1fe1c5c4a6" + "76ea67a37aac2aeaa33a78e2f30c5f998403752e7109e13d791e284d44f3c389" ] } }, @@ -2320,17 +2344,20 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum.", + "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum. R1.6d review: Float scalar/list/tensor conversion preserves whole-data shape recognition and flat output shape.", "public_names": [ "leaky_relu" ], "disposition": "exempt", "body_sha256": [ - "752b8737e5fd748eea05d7d5e46753ac937a51ccadefb458c18325ff24b2715e" + "438aeb6f2cd2d0d95ad84bdc5e611659da5961536899a41dd1b5dde3a424f576" ], "source_sha256": { "builtin_tensor_leaky_relu": [ - "752b8737e5fd748eea05d7d5e46753ac937a51ccadefb458c18325ff24b2715e" + "438aeb6f2cd2d0d95ad84bdc5e611659da5961536899a41dd1b5dde3a424f576" + ], + "eigs_float_arg": [ + "628eed4fed7515fe2738a26153798fcb173cb23a350d8436f78215f7d1f4c4a8" ], "flat_to_like": [ "244c127abae1a7bf7fa14097cf4c4930239ba42febae9d6d8f3264ea974d4dd1" @@ -2339,7 +2366,7 @@ "65240f647516c2717f73e1df84f89fa46da785a5ff51062d545adca210986100" ], "tensor_to_flat": [ - "f6ed317543ac1e298bce2f18706f3c8fb7d7067bbe89525f2d1c4f645b87e39d" + "c14f388b17b34c0e8b621ee59e0eb10f023a87cecf56d8a2770d6735e4fd7d5d" ] } }, @@ -2349,17 +2376,17 @@ "category": "output_only", "minimum": null, "maximum": null, - "reason": "Input is scalar string; list operations concern output or replay result, not input tuple.", + "reason": "Input is scalar string; list operations concern output or replay result, not input tuple. R1.6z reviewed repin: only the retired constructor spelling in a comment changes (make_num to make_float); executable C and the whole-argument helper closure are unchanged.", "public_names": [ "tensor_load" ], "disposition": "exempt", "body_sha256": [ - "9c5a7fcdd81ab0a65c4c4895efa71fcdd2b5388b8d2101a41bb670472e351a34" + "04d769bea1bb9bdf427695836506ccae1805e0c2c43a0360f4ee9d34fd0c5b18" ], "source_sha256": { "builtin_tensor_load": [ - "9c5a7fcdd81ab0a65c4c4895efa71fcdd2b5388b8d2101a41bb670472e351a34" + "04d769bea1bb9bdf427695836506ccae1805e0c2c43a0360f4ee9d34fd0c5b18" ] } }, @@ -2382,7 +2409,7 @@ "b4a539220becdecfac11ef2593b2da1b485db0397c1790217a22bde526ff7290" ], "tensor_unary": [ - "04fd54c5ccf01c248491a72c9ced77c1c5229d02584040585435fb1fe1c5c4a6" + "76ea67a37aac2aeaa33a78e2f30c5f998403752e7109e13d791e284d44f3c389" ] } }, @@ -2392,17 +2419,17 @@ "category": "overloaded_data", "minimum": null, "maximum": null, - "reason": "Exactly [list, number] is special tensor/dim form; otherwise entire list is tensor data. A global cap of two is incorrect; preserve documented recognition.", + "reason": "Exactly [list, number] is special tensor/dim form; otherwise entire list is tensor data. A global cap of two is incorrect; preserve documented recognition. R1.6d review: Float scalar result writer preserves exactly [list, number] overload recognition and whole-list data fallback.", "public_names": [ "log_softmax" ], "disposition": "exempt", "body_sha256": [ - "b431416fb41cf6a08ba614b21435f6f9df5386f253cb1f8e69418c4443c6d54d" + "128b48d0f155a4bcb8105da0cc97dc3f1561458505ff7489992db98022baaa68" ], "source_sha256": { "builtin_tensor_log_softmax": [ - "b431416fb41cf6a08ba614b21435f6f9df5386f253cb1f8e69418c4443c6d54d" + "128b48d0f155a4bcb8105da0cc97dc3f1561458505ff7489992db98022baaa68" ] } }, @@ -2448,23 +2475,26 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "List is the whole value/data input, not a bounded tuple.", + "reason": "List is the whole value/data input, not a bounded tuple. R1.6d review: Final float readers refuse nonfinite scalar data without imposing a list arity; the whole list is reduced.", "public_names": [ "mean" ], "disposition": "exempt", "body_sha256": [ - "ed93ef0ca494dc7b108946f17916cf59c9b014dd2ebf2d541eb2216643a3d122" + "181db3e52db3b60d3c4d6677594eec9a3da469251f3fba4d669764abc6bed160" ], "source_sha256": { + "buffer_read_f64": [ + "5487758ab896c3c4ea075c722c1c845fff991a8c424c675a729cc7f90dbc1817" + ], "builtin_tensor_mean": [ - "ed93ef0ca494dc7b108946f17916cf59c9b014dd2ebf2d541eb2216643a3d122" + "181db3e52db3b60d3c4d6677594eec9a3da469251f3fba4d669764abc6bed160" ], "tensor_flatten_recursive": [ - "fdaa19d7ba1bcf588fd8be8658a654e5ddbae696a8503715121da43d172ac8e0" + "fa55a91461d8a3791b0c4520b063faa164cd50b8c4da1c4e20cbccd2ab929c91" ], "tensor_total": [ - "399c5e89a5186315a6ed2167f43654b6a1c8bac577abd013cec76b8c2dfc7a0b" + "c1020e7360055af91e01f98dc2514242d88d79bb61ecbddaf3a098d85d4c1604" ] } }, @@ -2499,7 +2529,7 @@ "1197e210c14cfbfda2e6cb1316245d316693dadce0aee086ebd830c96c10be4b" ], "tensor_unary": [ - "04fd54c5ccf01c248491a72c9ced77c1c5229d02584040585435fb1fe1c5c4a6" + "76ea67a37aac2aeaa33a78e2f30c5f998403752e7109e13d791e284d44f3c389" ] } }, @@ -2509,23 +2539,26 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "List is the whole value/data input, not a bounded tuple.", + "reason": "List is the whole value/data input, not a bounded tuple. R1.6d review: Final float readers refuse nonfinite scalar data without imposing a list arity; the whole list is reduced.", "public_names": [ "norm" ], "disposition": "exempt", "body_sha256": [ - "d7773d3e06290a00543edb1cec8ca706ce3c10dee5a97b1b669a3eaba04a1cc8" + "1850ecba8d1024bd32221bde30be71fa974b07304a194b866399eab4977828d6" ], "source_sha256": { + "buffer_read_f64": [ + "5487758ab896c3c4ea075c722c1c845fff991a8c424c675a729cc7f90dbc1817" + ], "builtin_tensor_norm": [ - "d7773d3e06290a00543edb1cec8ca706ce3c10dee5a97b1b669a3eaba04a1cc8" + "1850ecba8d1024bd32221bde30be71fa974b07304a194b866399eab4977828d6" ], "tensor_flatten_recursive": [ - "fdaa19d7ba1bcf588fd8be8658a654e5ddbae696a8503715121da43d172ac8e0" + "fa55a91461d8a3791b0c4520b063faa164cd50b8c4da1c4e20cbccd2ab929c91" ], "tensor_total": [ - "399c5e89a5186315a6ed2167f43654b6a1c8bac577abd013cec76b8c2dfc7a0b" + "c1020e7360055af91e01f98dc2514242d88d79bb61ecbddaf3a098d85d4c1604" ] } }, @@ -2547,23 +2580,26 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum.", + "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum. R1.6d review: Float scalar/list/tensor conversion preserves whole-data shape recognition and flat output shape.", "public_names": [ "relu" ], "disposition": "exempt", "body_sha256": [ - "22ca05ce8c0e3fe3887c87bc9f74b1df24e2700c61205e7f5928a7a871fa2640" + "c82f86a53fd89c1440228e099d166d9ee1f11672dfc8c6def438182b000df3bd" ], "source_sha256": { "builtin_tensor_relu": [ - "22ca05ce8c0e3fe3887c87bc9f74b1df24e2700c61205e7f5928a7a871fa2640" + "c82f86a53fd89c1440228e099d166d9ee1f11672dfc8c6def438182b000df3bd" + ], + "eigs_float_arg": [ + "628eed4fed7515fe2738a26153798fcb173cb23a350d8436f78215f7d1f4c4a8" ], "flat_to_like": [ "244c127abae1a7bf7fa14097cf4c4930239ba42febae9d6d8f3264ea974d4dd1" ], "tensor_to_flat": [ - "f6ed317543ac1e298bce2f18706f3c8fb7d7067bbe89525f2d1c4f645b87e39d" + "c14f388b17b34c0e8b621ee59e0eb10f023a87cecf56d8a2770d6735e4fd7d5d" ] } }, @@ -2597,17 +2633,17 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Entire list is data; no argument-list maximum.", + "reason": "Entire list is data; no argument-list maximum. R1.6d review: N3 integer dimension results preserve whole-list input shape recognition.", "public_names": [ "shape" ], "disposition": "exempt", "body_sha256": [ - "c91cd906174337cc0a2d10bdb7daac932bb513e9632b97f1f9c0e96b1c4bb2a0" + "b63ea605f6ab1cac996fb74bb08680c675dc0723695a362a240391dea4337fad" ], "source_sha256": { "builtin_tensor_shape": [ - "c91cd906174337cc0a2d10bdb7daac932bb513e9632b97f1f9c0e96b1c4bb2a0" + "b63ea605f6ab1cac996fb74bb08680c675dc0723695a362a240391dea4337fad" ] } }, @@ -2617,20 +2653,20 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum.", + "reason": "Transitive helper consumes whole value/list/tensor/byte or sample data; no fixed tuple maximum. R1.6d review: Float result writers and final data readers preserve whole-data recognition and output dimensions.", "public_names": [ "softmax" ], "disposition": "exempt", "body_sha256": [ - "645c908202070b64e51e3d65f24b1ea4248b9def36909422e28bd47eead6fdf5" + "ddbdaefbe7e894385de7d03855bb97610a61b45f11ff9e34b50c228c131ba78d" ], "source_sha256": { "buf_dims": [ "eed52bb80872976c3f6c755c1ef9677c5c990de16e492d4856d23fbf3f20615b" ], "builtin_tensor_softmax": [ - "645c908202070b64e51e3d65f24b1ea4248b9def36909422e28bd47eead6fdf5" + "ddbdaefbe7e894385de7d03855bb97610a61b45f11ff9e34b50c228c131ba78d" ], "flat_to_like": [ "244c127abae1a7bf7fa14097cf4c4930239ba42febae9d6d8f3264ea974d4dd1" @@ -2639,7 +2675,7 @@ "65240f647516c2717f73e1df84f89fa46da785a5ff51062d545adca210986100" ], "tensor_to_flat": [ - "f6ed317543ac1e298bce2f18706f3c8fb7d7067bbe89525f2d1c4f645b87e39d" + "c14f388b17b34c0e8b621ee59e0eb10f023a87cecf56d8a2770d6735e4fd7d5d" ] } }, @@ -2662,7 +2698,7 @@ "62a668ee5f0d2e59177d0bcc41818a1a2c07dca2862e9dbeca2366f3d6f37f06" ], "tensor_unary": [ - "04fd54c5ccf01c248491a72c9ced77c1c5229d02584040585435fb1fe1c5c4a6" + "76ea67a37aac2aeaa33a78e2f30c5f998403752e7109e13d791e284d44f3c389" ] } }, @@ -2690,17 +2726,17 @@ ], "disposition": "exempt", "body_sha256": [ - "0f2bf6cc4757021f40f7484d25047520648ad260e34fc272ba934fac7d1ff19d" + "41b64a353f2123573793d87f6eed683726aade487c1dbf4b5b6cb66e5153c09c" ], "source_sha256": { "builtin_tensor_sum": [ - "0f2bf6cc4757021f40f7484d25047520648ad260e34fc272ba934fac7d1ff19d" + "41b64a353f2123573793d87f6eed683726aade487c1dbf4b5b6cb66e5153c09c" ], - "tensor_flatten_recursive": [ - "fdaa19d7ba1bcf588fd8be8658a654e5ddbae696a8503715121da43d172ac8e0" + "tensor_sum_has_float": [ + "9f9236fba6d64f1a3058a0860ac8f14cfa6a8d2d70b598c2892c7c216e62d653" ], - "tensor_total": [ - "399c5e89a5186315a6ed2167f43654b6a1c8bac577abd013cec76b8c2dfc7a0b" + "tensor_sum_into": [ + "9b5f0271eff4ac61fc7af2b3a97b562f5c47404512d5f3a5c5511a842539498b" ] } }, @@ -2722,17 +2758,17 @@ "category": "unary_data", "minimum": null, "maximum": null, - "reason": "List is the whole value/data input, not a bounded tuple.", + "reason": "List is the whole value/data input, not a bounded tuple. R1.6d review: Float zero results preserve whole-data input recognition and output dimensions.", "public_names": [ "zeros_like" ], "disposition": "exempt", "body_sha256": [ - "0decd89b1c7923300c9fcede1eaf79c37cbe3f8843a659e748847b8fd47554b9" + "fbd514f82a1b1119c27d53a6c7abbdbe273d88dcb90a5ba13daf8bd44c9feef9" ], "source_sha256": { "builtin_tensor_zeros_like": [ - "0decd89b1c7923300c9fcede1eaf79c37cbe3f8843a659e748847b8fd47554b9" + "fbd514f82a1b1119c27d53a6c7abbdbe273d88dcb90a5ba13daf8bd44c9feef9" ], "make_buffer_like": [ "65240f647516c2717f73e1df84f89fa46da785a5ff51062d545adca210986100" @@ -2782,7 +2818,7 @@ "01c2880d21271cbf77daa5f6fa0d35cfe5eeaa2ca51e46cb333f797dbba52581" ], "value_to_string": [ - "269292118bde6226411ea5a201cc73ae9a5d81dc010f8caca57118e4b8257f70" + "541c34dfd1dbe93f2ca39b800321afb116be9c74d7d8e35b297502a157a2e1fd" ] } }, @@ -2798,11 +2834,11 @@ ], "disposition": "exempt", "body_sha256": [ - "29b2923e563451b981e86a1938f1b8866d13e9acafd5668b5b4519efd00900ea" + "c2053861356373745d56a7459af55d70c066b89a513e06c910c2e83b33c9ff13" ], "source_sha256": { "builtin_type": [ - "29b2923e563451b981e86a1938f1b8866d13e9acafd5668b5b4519efd00900ea" + "c2053861356373745d56a7459af55d70c066b89a513e06c910c2e83b33c9ff13" ] } }, @@ -2835,23 +2871,23 @@ "category": "descriptor_data", "minimum": null, "maximum": null, - "reason": "Whole ABI-stamped descriptor is data validated by vm_desc_abi_error; it is not a fixed builtin argument tuple. No descriptor execution is needed for this classification.", + "reason": "Whole ABI-stamped descriptor is data validated by vm_desc_abi_error; it is not a fixed builtin argument tuple. No descriptor execution is needed for this classification. Reviewed R1.6c (#1758): numeric readers changed; outer argument shape is unchanged.", "public_names": [ "vm_run_bytecode" ], "disposition": "exempt", "body_sha256": [ - "503497c43ec971fb5f2f0033038cedd0948c3016967fd49daca688a8c9f56741" + "e3cf03f3b871b4206196bf676e5e5cdaf66c9bb552645f23f5e47457c3ad07e7" ], "source_sha256": { "builtin_vm_run_bytecode": [ - "503497c43ec971fb5f2f0033038cedd0948c3016967fd49daca688a8c9f56741" + "e3cf03f3b871b4206196bf676e5e5cdaf66c9bb552645f23f5e47457c3ad07e7" ], "vm_build_chunk_desc": [ "56131f5a520be3bff59da05c8cc0203b0e9841de25d8bc7b76922aa2f9a21e18" ], "vm_desc_abi_error": [ - "c01ddd713da0938f1f770c7c32675ddc3f0f2cb32c23426e3f58cce41540c3d2" + "49ef36eb548d2e0b2a779190cbf62c69777545392d39b4b6a96600fe9f6d27de" ] } }, @@ -2874,7 +2910,7 @@ "df9046f4cdc7e9431a3b4c81e7c9752d9bb7520af99660223fb3e9b6f586171b" ], "value_to_string": [ - "269292118bde6226411ea5a201cc73ae9a5d81dc010f8caca57118e4b8257f70" + "541c34dfd1dbe93f2ca39b800321afb116be9c74d7d8e35b297502a157a2e1fd" ] } }, @@ -2901,5 +2937,1998 @@ "write_text" ], "disposition": "guarded" + }, + { + "function": "builtin_i8_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "d60836356c1be9c75718289222378458f702f1b5c3311fddae3ed56c2e618668" + ], + "source_sha256": { + "builtin_i8_wrapping_add": [ + "d60836356c1be9c75718289222378458f702f1b5c3311fddae3ed56c2e618668" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "0c4fe03abb11fdf8e01e631e22230b1c9c62b11648cc85cd399daa35c591870c" + ], + "source_sha256": { + "builtin_i8_wrapping_sub": [ + "0c4fe03abb11fdf8e01e631e22230b1c9c62b11648cc85cd399daa35c591870c" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "3a99ec26a8a08d88f63ce6c88ebf85f5cf48f29f9355c028337f5cf1b9cbab1a" + ], + "source_sha256": { + "builtin_i8_wrapping_mul": [ + "3a99ec26a8a08d88f63ce6c88ebf85f5cf48f29f9355c028337f5cf1b9cbab1a" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "f5c73080d630e65ba609129c89a017003b08fcec66e21aed5a18f7a322c656e4" + ], + "source_sha256": { + "builtin_i8_checked_add": [ + "f5c73080d630e65ba609129c89a017003b08fcec66e21aed5a18f7a322c656e4" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "c33e6f3efc9893832166f24e0cb8c832e18781e359afc6e331fd2dcea1b7a6d8" + ], + "source_sha256": { + "builtin_i8_checked_sub": [ + "c33e6f3efc9893832166f24e0cb8c832e18781e359afc6e331fd2dcea1b7a6d8" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "f5a9c8f949cf9ad50c030be0b033b2e04cb2f988b3035bf9844ba78b494cc4f7" + ], + "source_sha256": { + "builtin_i8_checked_mul": [ + "f5a9c8f949cf9ad50c030be0b033b2e04cb2f988b3035bf9844ba78b494cc4f7" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "335b71aa6da345081aa17ab54aa912cd4d5ffbf8a2768f15f3e3cb2775eb00cf" + ], + "source_sha256": { + "builtin_i8_saturating_add": [ + "335b71aa6da345081aa17ab54aa912cd4d5ffbf8a2768f15f3e3cb2775eb00cf" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "49a350b53edb0530849d97b2cdb72e8586831fd4b37cdbbdb9b4d023577cf5f9" + ], + "source_sha256": { + "builtin_i8_saturating_sub": [ + "49a350b53edb0530849d97b2cdb72e8586831fd4b37cdbbdb9b4d023577cf5f9" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i8_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i8_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "4b4285eab3b7ee92f2b3b9787a99b75261969e14ef7b2893b1f58cd8b5dfbe93" + ], + "source_sha256": { + "builtin_i8_saturating_mul": [ + "4b4285eab3b7ee92f2b3b9787a99b75261969e14ef7b2893b1f58cd8b5dfbe93" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "791c3ff629d6d81368f3a9ff2081fcd8d7efc6969230549b4d2be401280acab3" + ], + "source_sha256": { + "builtin_i16_wrapping_add": [ + "791c3ff629d6d81368f3a9ff2081fcd8d7efc6969230549b4d2be401280acab3" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "c80537627672a2f45b1fee5f71cd9015a7085095778453bad3671f16ae774ac4" + ], + "source_sha256": { + "builtin_i16_wrapping_sub": [ + "c80537627672a2f45b1fee5f71cd9015a7085095778453bad3671f16ae774ac4" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "99b9a63bb0d608259bb1f85efcfe371086c110cadc82a1229364434a329a62c3" + ], + "source_sha256": { + "builtin_i16_wrapping_mul": [ + "99b9a63bb0d608259bb1f85efcfe371086c110cadc82a1229364434a329a62c3" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "a77877a011718198d72c0d0a50f11989c6d95bb3aaee912e72f458da4b374b3c" + ], + "source_sha256": { + "builtin_i16_checked_add": [ + "a77877a011718198d72c0d0a50f11989c6d95bb3aaee912e72f458da4b374b3c" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "d46336e474a7617ab7f0f6b59c4b1b8c9ab8754be110ac45f0f75cce68550e82" + ], + "source_sha256": { + "builtin_i16_checked_sub": [ + "d46336e474a7617ab7f0f6b59c4b1b8c9ab8754be110ac45f0f75cce68550e82" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "ba092b25c3e669adde7a6669e1c9619b2352f00f6fc72049570850059de3e571" + ], + "source_sha256": { + "builtin_i16_checked_mul": [ + "ba092b25c3e669adde7a6669e1c9619b2352f00f6fc72049570850059de3e571" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "42e664fb6d0ee421612fe2486d57ebcec589fe3aa209eb910f4c7e5dbc007cd4" + ], + "source_sha256": { + "builtin_i16_saturating_add": [ + "42e664fb6d0ee421612fe2486d57ebcec589fe3aa209eb910f4c7e5dbc007cd4" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "c67d10e0b8e1daaac9d446375965cc5f74aa63cf9a4c271ae95c117abb938909" + ], + "source_sha256": { + "builtin_i16_saturating_sub": [ + "c67d10e0b8e1daaac9d446375965cc5f74aa63cf9a4c271ae95c117abb938909" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i16_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i16_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "061691de49ce5d833edfce351b4bc7ac88be4c4fccb6b10052c964e6addbcb99" + ], + "source_sha256": { + "builtin_i16_saturating_mul": [ + "061691de49ce5d833edfce351b4bc7ac88be4c4fccb6b10052c964e6addbcb99" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "a5db9f259166411b5afc57a0df29e38bda398f88b2909b6dcc04c2316665ceb3" + ], + "source_sha256": { + "builtin_i32_wrapping_add": [ + "a5db9f259166411b5afc57a0df29e38bda398f88b2909b6dcc04c2316665ceb3" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "54d51fc24da1045da218f5b15d2f0982383a0b4222c207203799e468f895dde4" + ], + "source_sha256": { + "builtin_i32_wrapping_sub": [ + "54d51fc24da1045da218f5b15d2f0982383a0b4222c207203799e468f895dde4" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "13ececb51237ac99480a93ee057b50f08977f4c601d5d669ea411451abac587d" + ], + "source_sha256": { + "builtin_i32_wrapping_mul": [ + "13ececb51237ac99480a93ee057b50f08977f4c601d5d669ea411451abac587d" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "91a978bb340dc5375e280e156bd33db98d9c128e67a769176571f5a65cfe32f1" + ], + "source_sha256": { + "builtin_i32_checked_add": [ + "91a978bb340dc5375e280e156bd33db98d9c128e67a769176571f5a65cfe32f1" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "7a326f945fcb657990907c7b2231dafe534d91ad3acce84f5791169d8f3821e8" + ], + "source_sha256": { + "builtin_i32_checked_sub": [ + "7a326f945fcb657990907c7b2231dafe534d91ad3acce84f5791169d8f3821e8" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "07eb9f3bb071198a873c9112f8934526619f11c21ce389aea2e15d3e40f4389e" + ], + "source_sha256": { + "builtin_i32_checked_mul": [ + "07eb9f3bb071198a873c9112f8934526619f11c21ce389aea2e15d3e40f4389e" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "214fe4841e912759d2effad2d060a4922b1e55391fea33384730374ffb2f5c26" + ], + "source_sha256": { + "builtin_i32_saturating_add": [ + "214fe4841e912759d2effad2d060a4922b1e55391fea33384730374ffb2f5c26" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "ce231dac618bfe95e62e6ef92d24166f637a74a0fdb419b96b1a4b5266887c58" + ], + "source_sha256": { + "builtin_i32_saturating_sub": [ + "ce231dac618bfe95e62e6ef92d24166f637a74a0fdb419b96b1a4b5266887c58" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i32_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i32_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "74bafcdf32529da812e98b977108695cb002797b5183f890e7a69a3b3aa8993e" + ], + "source_sha256": { + "builtin_i32_saturating_mul": [ + "74bafcdf32529da812e98b977108695cb002797b5183f890e7a69a3b3aa8993e" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "68c7f3282f21fe998542353339b1064580d60de4d90f6c48a68eacda8847ea98" + ], + "source_sha256": { + "builtin_i64_wrapping_add": [ + "68c7f3282f21fe998542353339b1064580d60de4d90f6c48a68eacda8847ea98" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "8074da2a24ba68feef340b8454c886da2d9c1bd710eeb530f5d6f5ab21355934" + ], + "source_sha256": { + "builtin_i64_wrapping_sub": [ + "8074da2a24ba68feef340b8454c886da2d9c1bd710eeb530f5d6f5ab21355934" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "1b1fbc9b7c4378a61929453b953a76c13cdfbbbfd31c958caaf2aaa49b0e7fc8" + ], + "source_sha256": { + "builtin_i64_wrapping_mul": [ + "1b1fbc9b7c4378a61929453b953a76c13cdfbbbfd31c958caaf2aaa49b0e7fc8" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "74b3cc5a8cc56e29c10cfa38bd7f880b60919d7bcbcc90cca06bec18ec9da67a" + ], + "source_sha256": { + "builtin_i64_checked_add": [ + "74b3cc5a8cc56e29c10cfa38bd7f880b60919d7bcbcc90cca06bec18ec9da67a" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "f9028710ce537acd6d09fb3c22dbcb139b170b4cc795d6a32be48f26ece55154" + ], + "source_sha256": { + "builtin_i64_checked_sub": [ + "f9028710ce537acd6d09fb3c22dbcb139b170b4cc795d6a32be48f26ece55154" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "f1d1175ce387a65d8109966f5cd0e0f3449a820ea2846ef87a9311f7c498dbdc" + ], + "source_sha256": { + "builtin_i64_checked_mul": [ + "f1d1175ce387a65d8109966f5cd0e0f3449a820ea2846ef87a9311f7c498dbdc" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "fdc690492db8283a55d3af553705ef6113fe731a6523e9b51a87cae4dcd2eafd" + ], + "source_sha256": { + "builtin_i64_saturating_add": [ + "fdc690492db8283a55d3af553705ef6113fe731a6523e9b51a87cae4dcd2eafd" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "5b5dc454c618cd6491476ad748d2572ef5241dc9b01dfe1185196c682a6cf07d" + ], + "source_sha256": { + "builtin_i64_saturating_sub": [ + "5b5dc454c618cd6491476ad748d2572ef5241dc9b01dfe1185196c682a6cf07d" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_i64_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "i64_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "f85aece237b65d8efbc310b7db6acaf0d62c2021b69f8c7f45094f21542d3be3" + ], + "source_sha256": { + "builtin_i64_saturating_mul": [ + "f85aece237b65d8efbc310b7db6acaf0d62c2021b69f8c7f45094f21542d3be3" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "55493824adb52e1a28d254f9cb5024238914ab8463875e994b9b7824151d77d0" + ], + "source_sha256": { + "builtin_u8_wrapping_add": [ + "55493824adb52e1a28d254f9cb5024238914ab8463875e994b9b7824151d77d0" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "00de168d773519c55541d43e417f233b32184a907db396cf5ffa7b3fd34e6edb" + ], + "source_sha256": { + "builtin_u8_wrapping_sub": [ + "00de168d773519c55541d43e417f233b32184a907db396cf5ffa7b3fd34e6edb" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "eeba4cd16500af7b22d5b28eff5dccfb25c01cfc9bf51215192196bcaead106e" + ], + "source_sha256": { + "builtin_u8_wrapping_mul": [ + "eeba4cd16500af7b22d5b28eff5dccfb25c01cfc9bf51215192196bcaead106e" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "958464e39e8606805225201aceff28ea9db4a0940400795b2d3c873758d7919a" + ], + "source_sha256": { + "builtin_u8_checked_add": [ + "958464e39e8606805225201aceff28ea9db4a0940400795b2d3c873758d7919a" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "f87d1e753b89003151d4ce779e3d535f1cf9ddc2f9de08766d9921ab64254d8e" + ], + "source_sha256": { + "builtin_u8_checked_sub": [ + "f87d1e753b89003151d4ce779e3d535f1cf9ddc2f9de08766d9921ab64254d8e" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "104e43743eda70bb25a0ac9aaab9958476950261561bb699fe01197a61e96725" + ], + "source_sha256": { + "builtin_u8_checked_mul": [ + "104e43743eda70bb25a0ac9aaab9958476950261561bb699fe01197a61e96725" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "d404f7a66d2696fc7f2b7e1e3d489d72af27eb3ca90cb77c49709fb335d52f62" + ], + "source_sha256": { + "builtin_u8_saturating_add": [ + "d404f7a66d2696fc7f2b7e1e3d489d72af27eb3ca90cb77c49709fb335d52f62" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "dcf72491a2410f34e8a394848cc55d932e4cc1bd42dd16e06e1b9f4ee9b94f22" + ], + "source_sha256": { + "builtin_u8_saturating_sub": [ + "dcf72491a2410f34e8a394848cc55d932e4cc1bd42dd16e06e1b9f4ee9b94f22" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u8_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u8_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "6e7c0c474be39a69c8ac4acdf6f2ee2d4e554595916331f3680f3c5409541dc4" + ], + "source_sha256": { + "builtin_u8_saturating_mul": [ + "6e7c0c474be39a69c8ac4acdf6f2ee2d4e554595916331f3680f3c5409541dc4" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "5743ee2a2611994740f35fd705fbc94ed0d3a56f523abeeb759dec4eaac131cf" + ], + "source_sha256": { + "builtin_u16_wrapping_add": [ + "5743ee2a2611994740f35fd705fbc94ed0d3a56f523abeeb759dec4eaac131cf" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "bfc05a7037a6709d32f4f46d35e9fb255e213fec4be12cc4a43fdd91a4129dc2" + ], + "source_sha256": { + "builtin_u16_wrapping_sub": [ + "bfc05a7037a6709d32f4f46d35e9fb255e213fec4be12cc4a43fdd91a4129dc2" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "9982e0432905ddcac16da2ff49a74dbcef8ba16f5c9328445129fba2cc48fed7" + ], + "source_sha256": { + "builtin_u16_wrapping_mul": [ + "9982e0432905ddcac16da2ff49a74dbcef8ba16f5c9328445129fba2cc48fed7" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "75e047c4c477ed8073bde33b6780638291ba80fffe8164f48023fcf75810dbef" + ], + "source_sha256": { + "builtin_u16_checked_add": [ + "75e047c4c477ed8073bde33b6780638291ba80fffe8164f48023fcf75810dbef" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "5db19cf8b15cb67b3eecb734bea7dba45addfd867164c700e24009dd50fe362f" + ], + "source_sha256": { + "builtin_u16_checked_sub": [ + "5db19cf8b15cb67b3eecb734bea7dba45addfd867164c700e24009dd50fe362f" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "eec652f3ec2771845136de3666835dcbf7a95e6e3b04fafeb14af50a29287741" + ], + "source_sha256": { + "builtin_u16_checked_mul": [ + "eec652f3ec2771845136de3666835dcbf7a95e6e3b04fafeb14af50a29287741" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "12dccc30b15117ac29f5b93ab3803ba578ac5c73694f8c024842a2d846829d12" + ], + "source_sha256": { + "builtin_u16_saturating_add": [ + "12dccc30b15117ac29f5b93ab3803ba578ac5c73694f8c024842a2d846829d12" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "3b0a82dcacb4059b97b38d04bd18a859ea9f56f07afab1131235c32ec5205903" + ], + "source_sha256": { + "builtin_u16_saturating_sub": [ + "3b0a82dcacb4059b97b38d04bd18a859ea9f56f07afab1131235c32ec5205903" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u16_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u16_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "550787af46b85137de3e68c459f9e2539c3477bc004d8dc84303ac0c1f4b637a" + ], + "source_sha256": { + "builtin_u16_saturating_mul": [ + "550787af46b85137de3e68c459f9e2539c3477bc004d8dc84303ac0c1f4b637a" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "fb99fa4cea2c88728f82e5524848d7e8979779ff6fba8ce83505dbc7796ccd64" + ], + "source_sha256": { + "builtin_u32_wrapping_add": [ + "fb99fa4cea2c88728f82e5524848d7e8979779ff6fba8ce83505dbc7796ccd64" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "8a60c21abaa46893b5a7c3893c7e5e2bd9c8253d3a0c2e925dac307df73a5f35" + ], + "source_sha256": { + "builtin_u32_wrapping_sub": [ + "8a60c21abaa46893b5a7c3893c7e5e2bd9c8253d3a0c2e925dac307df73a5f35" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "3a9b63d75031e99937003bdde481093b3ac912eb8b5405f6e1a368f5ae86c89e" + ], + "source_sha256": { + "builtin_u32_wrapping_mul": [ + "3a9b63d75031e99937003bdde481093b3ac912eb8b5405f6e1a368f5ae86c89e" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "30a2152ad88422bf300ae6670091f0da22c74f134827df0328c81ee0b05b4459" + ], + "source_sha256": { + "builtin_u32_checked_add": [ + "30a2152ad88422bf300ae6670091f0da22c74f134827df0328c81ee0b05b4459" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "904cd4a05afc377eb123d09bc1899001835421dc278b1bb00f711d345c1af930" + ], + "source_sha256": { + "builtin_u32_checked_sub": [ + "904cd4a05afc377eb123d09bc1899001835421dc278b1bb00f711d345c1af930" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "e035b3ac98d8e645ab665d0f0d2a4280f9304d8ca9752ea5e4bb88905bd19852" + ], + "source_sha256": { + "builtin_u32_checked_mul": [ + "e035b3ac98d8e645ab665d0f0d2a4280f9304d8ca9752ea5e4bb88905bd19852" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "af1d6106a22cc5a9473ce752ba514bef1d9e35334355eea872344bc38c03a0ad" + ], + "source_sha256": { + "builtin_u32_saturating_add": [ + "af1d6106a22cc5a9473ce752ba514bef1d9e35334355eea872344bc38c03a0ad" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "bf0b7a32f17590390143348ca0788cf1409a643c18f7045da5c1cd13547f7473" + ], + "source_sha256": { + "builtin_u32_saturating_sub": [ + "bf0b7a32f17590390143348ca0788cf1409a643c18f7045da5c1cd13547f7473" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u32_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u32_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "35eb5250c46fe559f1bad2cd3ec26b8b682f4a7feb8c63b3d54b294b651015db" + ], + "source_sha256": { + "builtin_u32_saturating_mul": [ + "35eb5250c46fe559f1bad2cd3ec26b8b682f4a7feb8c63b3d54b294b651015db" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_wrapping_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_wrapping_add" + ], + "disposition": "exempt", + "body_sha256": [ + "986d61de96e1cd34bc48866a5943adb2c77fb68f46003bd6f42e817e8178fdc0" + ], + "source_sha256": { + "builtin_u64_wrapping_add": [ + "986d61de96e1cd34bc48866a5943adb2c77fb68f46003bd6f42e817e8178fdc0" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_wrapping_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_wrapping_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "4a021a4565f4fd3c02143158a39e7646a1af78578650a003c253d1a3fc57f5bd" + ], + "source_sha256": { + "builtin_u64_wrapping_sub": [ + "4a021a4565f4fd3c02143158a39e7646a1af78578650a003c253d1a3fc57f5bd" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_wrapping_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_wrapping_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "710eb5a6354d5294edb436db4f9d763790edc47b1c1bcb5e0ea94b96df99039d" + ], + "source_sha256": { + "builtin_u64_wrapping_mul": [ + "710eb5a6354d5294edb436db4f9d763790edc47b1c1bcb5e0ea94b96df99039d" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_checked_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_checked_add" + ], + "disposition": "exempt", + "body_sha256": [ + "e870a2fd43e35e4109a5255cb9605c2fa7377f0b7fb530d27c2f658c96bc8346" + ], + "source_sha256": { + "builtin_u64_checked_add": [ + "e870a2fd43e35e4109a5255cb9605c2fa7377f0b7fb530d27c2f658c96bc8346" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_checked_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_checked_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "accba026959827f236c091b67820dcdfd9e0e5d01518d782b9b28a99545ea110" + ], + "source_sha256": { + "builtin_u64_checked_sub": [ + "accba026959827f236c091b67820dcdfd9e0e5d01518d782b9b28a99545ea110" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_checked_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_checked_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "a6fe8fba91fc016197a1ac231f0b04d9ec29045f83e7aa8564f9a870ea634eb6" + ], + "source_sha256": { + "builtin_u64_checked_mul": [ + "a6fe8fba91fc016197a1ac231f0b04d9ec29045f83e7aa8564f9a870ea634eb6" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_saturating_add", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_saturating_add" + ], + "disposition": "exempt", + "body_sha256": [ + "63dcb9738d9759e8eeb536edcfb1a6efbc02a56a8cb26112f942704a979f63bc" + ], + "source_sha256": { + "builtin_u64_saturating_add": [ + "63dcb9738d9759e8eeb536edcfb1a6efbc02a56a8cb26112f942704a979f63bc" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_saturating_sub", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_saturating_sub" + ], + "disposition": "exempt", + "body_sha256": [ + "103af2eb58f75f99b543533cc380dde3ecc1bc55e0c34fee6f807b0b7e023e4b" + ], + "source_sha256": { + "builtin_u64_saturating_sub": [ + "103af2eb58f75f99b543533cc380dde3ecc1bc55e0c34fee6f807b0b7e023e4b" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_saturating_mul", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_saturating_mul" + ], + "disposition": "exempt", + "body_sha256": [ + "ec3604603c272e9eecb8d58838d960633ce053f3a976b29cf789e1661105c2b9" + ], + "source_sha256": { + "builtin_u64_saturating_mul": [ + "ec3604603c272e9eecb8d58838d960633ce053f3a976b29cf789e1661105c2b9" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_width": [ + "8b5c83d564ed7f22532d42d0d487d31cfe3ec9756902ae2519ae9e4c180fbe81" + ] + } + }, + { + "function": "builtin_u64_compare", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_compare" + ], + "disposition": "exempt", + "body_sha256": [ + "a983d96be765208d4397a56c0f19e9da07595c9a0ff7bbee6c67b6d32477a455" + ], + "source_sha256": { + "builtin_u64_compare": [ + "a983d96be765208d4397a56c0f19e9da07595c9a0ff7bbee6c67b6d32477a455" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ] + } + }, + { + "function": "builtin_u64_div", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_div" + ], + "disposition": "exempt", + "body_sha256": [ + "7f7c16dd1a925b74531aa5c392e7cd16a73162693eda1e87b83a38e7170224d7" + ], + "source_sha256": { + "builtin_u64_div": [ + "7f7c16dd1a925b74531aa5c392e7cd16a73162693eda1e87b83a38e7170224d7" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_unsigned_div": [ + "87fc0b63a7b4077b0aff909cf31269077d6dd376fcbbd1c5610ed5dbc4023552" + ] + } + }, + { + "function": "builtin_u64_rem", + "file": "src/builtins_int.c", + "category": "fixed", + "minimum": 2, + "maximum": 2, + "reason": "N9 R1.6g-D1: shared n9_pair unconditionally refuses surplus and missing binary operands before N5/range reads; both strict modes are covered by frozen probes and Rust differential.", + "public_names": [ + "u64_rem" + ], + "disposition": "exempt", + "body_sha256": [ + "291a646a0f0b9c41965b1ddefd353fd91d60fc47594f8c8ef9c5e66a97bde1ed" + ], + "source_sha256": { + "builtin_u64_rem": [ + "291a646a0f0b9c41965b1ddefd353fd91d60fc47594f8c8ef9c5e66a97bde1ed" + ], + "n9_pair": [ + "2af010e12576426af8fd8e1b4efe6143459215cad378f78746ba853e521aee6f" + ], + "n9_unsigned_div": [ + "87fc0b63a7b4077b0aff909cf31269077d6dd376fcbbd1c5610ed5dbc4023552" + ] + } + }, + { + "function": "builtin_u64_to_string", + "file": "src/builtins_int.c", + "category": "unary", + "minimum": 1, + "maximum": 1, + "reason": "N9 R1.6g-D1: scalar unary reader with one-element list unwrap; every other list is a non-number and raises in both strict modes.", + "public_names": [ + "u64_to_string" + ], + "disposition": "exempt", + "body_sha256": [ + "613a3341661ab5926f250beee6c32a06898872e65fc2809d88db1c655ad8d346" + ], + "source_sha256": { + "builtin_u64_to_string": [ + "613a3341661ab5926f250beee6c32a06898872e65fc2809d88db1c655ad8d346" + ], + "eigs_int_arg": [ + "4adef0d09678cfe76f30b2b9cd882e775a9d48ef7f30665102169c2e5ccae1aa" + ] + } + }, + { + "function": "builtin_fround", + "file": "src/builtins_int.c", + "category": "unary", + "minimum": 1, + "maximum": 1, + "reason": "N9 R1.6g-D1: scalar unary reader with one-element list unwrap; every other list is a non-number and raises in both strict modes.", + "public_names": [ + "fround" + ], + "disposition": "exempt", + "body_sha256": [ + "f096c2a0f03d979f1d0d4cdfe11c1a8a024f9f0c3b4fe4d8f8ae41f86722cd2c" + ], + "source_sha256": { + "builtin_fround": [ + "f096c2a0f03d979f1d0d4cdfe11c1a8a024f9f0c3b4fe4d8f8ae41f86722cd2c" + ], + "eigs_float_arg": [ + "628eed4fed7515fe2738a26153798fcb173cb23a350d8436f78215f7d1f4c4a8" + ] + } } ] diff --git a/web/build.sh b/web/build.sh index 1b60416d3..22c1ace3b 100755 --- a/web/build.sh +++ b/web/build.sh @@ -24,7 +24,7 @@ mkdir -p web/dist # units, which depend on HTTP/sockets/threads not available in WASM. SOURCES=( src/eigenscript.c src/lexer.c src/parser.c - src/builtins.c src/builtins_buf.c src/builtins_host.c src/builtins_tensor.c + src/builtins.c src/builtins_int.c src/builtins_buf.c src/builtins_host.c src/builtins_tensor.c src/fsutil.c src/hash.c src/alloc.c src/state.c src/strbuf.c src/ext_store.c src/fmt.c src/lint.c src/lint_host.c