| 1 | # Spinel AOT Compiler - Makefile
|
| 2 | #
|
| 3 | # Usage:
|
| 4 | # make Build the C compiler (runtime + spinel + tools)
|
| 5 | # make test Run the feature tests (always a fresh run)
|
| 6 | # make bench Run benchmarks vs CRuby
|
| 7 | # make optcarrot End-to-end optcarrot integration test
|
| 8 | # make legacy Build the legacy Ruby compiler (delegates to legacy/)
|
| 9 | # make bootstrap Legacy self-host fixpoint check (delegates to legacy/)
|
| 10 | # make check Fast pre-commit: rebuild + tests
|
| 11 | # make gate Full pre-push: test || bench || optcarrot
|
| 12 | # make clean Remove built binaries
|
| 13 | #
|
| 14 | # The legacy Ruby compiler (legacy/) is built ONLY by the explicit `make legacy`
|
| 15 | # / `make bootstrap` delegators or by `cd legacy && make`; the C compiler is
|
| 16 | # master and a plain `make` / `make gate` never compiles it.
|
| 17 |
|
| 18 | # Shared toolchain configuration (CC wrapping, CFLAGS, LTO, stamps, β¦).
|
| 19 | include common.mk
|
| 20 |
|
| 21 | # Prism library: prefer vendor/prism (fetched via `make deps`), then fall
|
| 22 | # back to the Prism gem if one is installed. Override with PRISM_DIR=β¦
|
| 23 | PRISM_VERSION ?= 1.9.0
|
| 24 | ifneq ($(wildcard vendor/prism/include/prism.h),)
|
| 25 | PRISM_DIR ?= vendor/prism
|
| 26 | else
|
| 27 | PRISM_DIR ?= $(shell ruby -rprism -e 'puts $$LOADED_FEATURES.grep(/prism/).first.sub(%r{/lib/.*}, "")' 2>/dev/null)
|
| 28 | endif
|
| 29 |
|
| 30 | PRISM_INC = $(PRISM_DIR)/include
|
| 31 | PRISM_SRC = $(wildcard $(PRISM_DIR)/src/*.c) $(wildcard $(PRISM_DIR)/src/util/*.c)
|
| 32 | PRISM_OBJ = $(patsubst $(PRISM_DIR)/src/%.c,build/prism/%.o,$(PRISM_SRC))
|
| 33 | PRISM_LIB = build/libprism.a
|
| 34 |
|
| 35 | # rbs C parser. Fetched via `make deps` from rubygems.org. Consumed by
|
| 36 | # spinel_rbs_extract to produce a seed file for the analyzer.
|
| 37 | RBS_VERSION ?= 4.0.1
|
| 38 | RBS_DIR = vendor/rbs
|
| 39 | RBS_INC = $(RBS_DIR)/include
|
| 40 | RBS_SRC = $(wildcard $(RBS_DIR)/src/*.c) $(wildcard $(RBS_DIR)/src/util/*.c)
|
| 41 | RBS_OBJ = $(patsubst $(RBS_DIR)/src/%.c,build/rbs/%.o,$(RBS_SRC))
|
| 42 | RBS_LIB = build/librbs.a
|
| 43 |
|
| 44 | .PHONY: all parse legacy bootstrap codegen regexp rbs_extract rbs-test rbs-seed-test \
|
| 45 | analyze-fail-test test test-run clean-test-results regen-rbs-expected \
|
| 46 | regen-expected bench optcarrot gate check gate-legs gate-test gate-bench \
|
| 47 | gate-optcarrot clean install uninstall deps tools
|
| 48 |
|
| 49 | # `make all` includes the RBS extractor when vendor/rbs has been fetched
|
| 50 | # (via `make deps`); without it the extractor is silently omitted. Built under
|
| 51 | # build/ like other intermediates; rbs-seed-test copies it beside $(SPINEL),
|
| 52 | # where main.c looks for it as a sibling at runtime.
|
| 53 | RBS_EXTRACT_BIN = build/spinel_rbs_extract
|
| 54 | ifneq ($(wildcard $(RBS_INC)/rbs/parser.h),)
|
| 55 | RBS_EXTRACT_TARGET = $(RBS_EXTRACT_BIN)
|
| 56 | else
|
| 57 | RBS_EXTRACT_TARGET =
|
| 58 | endif
|
| 59 |
|
| 60 | # The single Spinel binary (compiler + cc driver). Defined here, before the
|
| 61 | # `all` rule, because a rule's prerequisites are expanded as it is read.
|
| 62 | # Built into bin/ alongside the companion tools (spinel-doctor, ...); bin/ sits
|
| 63 | # beside lib/ so the binary resolves its runtime lib via ../lib, same as before.
|
| 64 | SPINEL = bin/spinel
|
| 65 |
|
| 66 | all: regexp $(SPINEL) $(RBS_EXTRACT_TARGET) tools
|
| 67 |
|
| 68 | # ---- Dependencies ----
|
| 69 | deps: vendor/prism/include/prism/diagnostic.h vendor/rbs/include/rbs/parser.h
|
| 70 |
|
| 71 | # Download the pre-built Prism gem from rubygems.org and extract its C
|
| 72 | # sources (the .gem ships the generated headers β no rake/bundler needed).
|
| 73 | vendor/prism/include/prism/diagnostic.h:
|
| 74 | @mkdir -p vendor/prism
|
| 75 | @echo "Fetching prism v$(PRISM_VERSION) from rubygems.org..."
|
| 76 | curl -sL -o /tmp/prism-$(PRISM_VERSION).gem https://rubygems.org/gems/prism-$(PRISM_VERSION).gem
|
| 77 | @tmpdir=$$(mktemp -d); \
|
| 78 | tar -xf /tmp/prism-$(PRISM_VERSION).gem -C $$tmpdir data.tar.gz; \
|
| 79 | tar -xzf $$tmpdir/data.tar.gz -C vendor/prism; \
|
| 80 | rm -rf $$tmpdir /tmp/prism-$(PRISM_VERSION).gem
|
| 81 | @test -f $@ && echo "prism v$(PRISM_VERSION) ready at vendor/prism"
|
| 82 |
|
| 83 | # Same shape: download the rbs gem and extract its bundled C parser.
|
| 84 | vendor/rbs/include/rbs/parser.h:
|
| 85 | @mkdir -p vendor/rbs
|
| 86 | @echo "Fetching rbs v$(RBS_VERSION) from rubygems.org..."
|
| 87 | curl -sL -o /tmp/rbs-$(RBS_VERSION).gem https://rubygems.org/gems/rbs-$(RBS_VERSION).gem
|
| 88 | @tmpdir=$$(mktemp -d); \
|
| 89 | tar -xf /tmp/rbs-$(RBS_VERSION).gem -C $$tmpdir data.tar.gz; \
|
| 90 | tar -xzf $$tmpdir/data.tar.gz -C vendor/rbs; \
|
| 91 | rm -rf $$tmpdir /tmp/rbs-$(RBS_VERSION).gem
|
| 92 | @test -f $@ && echo "rbs v$(RBS_VERSION) ready at vendor/rbs"
|
| 93 |
|
| 94 | # If PRISM_DIR ended up empty (no vendor/prism, no gem), halt with a clear
|
| 95 | # message before trying to build anything that needs it.
|
| 96 | ifeq ($(PRISM_DIR),)
|
| 97 | parse codegen regexp all: prism-missing
|
| 98 | prism-missing:
|
| 99 | @echo "Error: Prism not found."; \
|
| 100 | echo " Run 'make deps' to fetch libprism into vendor/prism,"; \
|
| 101 | echo " or install the prism gem (gem install prism),"; \
|
| 102 | echo " or set PRISM_DIR=/path/to/prism manually."; \
|
| 103 | exit 1
|
| 104 | endif
|
| 105 |
|
| 106 | # ---- Prism static library ----
|
| 107 |
|
| 108 | build/libprism.a: $(PRISM_OBJ)
|
| 109 | @mkdir -p build
|
| 110 | ar rcs $@ $^
|
| 111 |
|
| 112 | build/prism/%.o: $(PRISM_DIR)/src/%.c
|
| 113 | @mkdir -p $(dir $@)
|
| 114 | $(CC) -c -O2 -I$(PRISM_INC) -I$(PRISM_DIR)/src $< -o $@
|
| 115 |
|
| 116 | # ---- rbs static library ----
|
| 117 |
|
| 118 | build/librbs.a: $(RBS_OBJ)
|
| 119 | @mkdir -p build
|
| 120 | ar rcs $@ $^
|
| 121 |
|
| 122 | build/rbs/%.o: $(RBS_DIR)/src/%.c
|
| 123 | @mkdir -p $(dir $@)
|
| 124 | $(CC) -c -O2 -Wno-all -I$(RBS_INC) -I$(RBS_DIR)/src $< -o $@
|
| 125 |
|
| 126 | # ---- C Parser ----
|
| 127 |
|
| 128 | parse: legacy/spinel_parse
|
| 129 |
|
| 130 | legacy/spinel_parse: legacy/spinel_parse.c $(PRISM_LIB)
|
| 131 | $(CC) $(CFLAGS) -I$(PRISM_INC) legacy/spinel_parse.c $(PRISM_LIB) -lm -o $@
|
| 132 |
|
| 133 | # ---- C compiler (src/) ----
|
| 134 | # The single-binary C reimplementation of the analyzer + code generator.
|
| 135 | # Links src/spinel_parse.c, the library copy of the Prism walk (no main();
|
| 136 | # exposes sp_parse_file_to_text). The standalone-with-main copy used by the
|
| 137 | # legacy `parse` target lives in legacy/spinel_parse.c.
|
| 138 | # `spinel` is the single binary: it emits C and then drives cc to link it.
|
| 139 | # (SPINEL itself is defined above, just before the `all` target.)
|
| 140 |
|
| 141 | SPINEL_HDRS = src/node_table.h src/codegen.h src/codegen_internal.h src/types.h src/compiler.h src/analyze.h src/analyze_internal.h
|
| 142 | SPINEL_OBJ = build/csrc/node_table.o build/csrc/types.o build/csrc/compiler.o \
|
| 143 | build/csrc/analyze.o build/csrc/analyze_util.o build/csrc/analyze_infer.o \
|
| 144 | build/csrc/analyze_scope.o build/csrc/analyze_pass.o build/csrc/codegen.o build/csrc/codegen_util.o \
|
| 145 | build/csrc/codegen_fold.o build/csrc/codegen_call.o \
|
| 146 | build/csrc/codegen_expr.o build/csrc/codegen_stmt.o build/csrc/main.o
|
| 147 |
|
| 148 | build/csrc:
|
| 149 | @mkdir -p build/csrc
|
| 150 |
|
| 151 | build/csrc/%.o: src/%.c $(SPINEL_HDRS) | build/csrc
|
| 152 | $(CC) $(CFLAGS) -Isrc -c $< -o $@
|
| 153 |
|
| 154 | build/csrc/sp_parse_lib.o: src/spinel_parse.c $(PRISM_LIB) | build/csrc
|
| 155 | $(CC) $(CFLAGS) -I$(PRISM_INC) -c src/spinel_parse.c -o $@
|
| 156 |
|
| 157 | $(SPINEL): $(SPINEL_OBJ) build/csrc/sp_parse_lib.o $(PRISM_LIB)
|
| 158 | @mkdir -p bin
|
| 159 | $(CC) $(CFLAGS) $(SPINEL_OBJ) build/csrc/sp_parse_lib.o $(PRISM_LIB) -lm $(LDFLAGS) -o $@
|
| 160 | @# Dev convenience: a repo-root `./spinel` pointing at the built binary
|
| 161 | @# (the installed command is `spinel` too). Best-effort; gitignored.
|
| 162 | @ln -sf $@ spinel 2>/dev/null || cp $@ spinel 2>/dev/null || true
|
| 163 |
|
| 164 | # ---- Legacy Ruby compiler (regression oracle) ----
|
| 165 | # Lives in legacy/ with its own Makefile; these are thin delegators.
|
| 166 | legacy:
|
| 167 | +@$(MAKE) -C legacy --no-print-directory legacy
|
| 168 |
|
| 169 | bootstrap:
|
| 170 | +@$(MAKE) -C legacy --no-print-directory LTO=$(LTO) bootstrap
|
| 171 |
|
| 172 | codegen: legacy
|
| 173 |
|
| 174 | # ---- RBS extractor ----
|
| 175 | # Reads sig/**/*.rbs, emits the seed-file format spinel_analyze consumes
|
| 176 | # when invoked with `spinel --rbs DIR`.
|
| 177 |
|
| 178 | ifeq ($(wildcard $(RBS_INC)/rbs/parser.h),)
|
| 179 | rbs_extract: rbs-missing
|
| 180 | rbs-missing:
|
| 181 | @echo "Error: rbs C parser not found at $(RBS_INC)/rbs/parser.h."; \
|
| 182 | echo " Run 'make deps' to fetch it from rubygems.org into vendor/rbs."; \
|
| 183 | exit 1
|
| 184 | else
|
| 185 | rbs_extract: $(RBS_EXTRACT_BIN)
|
| 186 |
|
| 187 | $(RBS_EXTRACT_BIN): tools/spinel_rbs_extract.c $(RBS_LIB)
|
| 188 | @mkdir -p $(@D)
|
| 189 | $(CC) $(CFLAGS) -I$(RBS_INC) tools/spinel_rbs_extract.c $(RBS_LIB) -o $@
|
| 190 | endif
|
| 191 |
|
| 192 | # ---- Runtime library (regexp + bigint + β¦) ----
|
| 193 |
|
| 194 | RE_SRC = lib/regexp/re_compile.c lib/regexp/re_exec.c lib/regexp/re_utf8.c
|
| 195 | RE_OBJ = $(patsubst lib/regexp/%.c,build/regexp/%.o,$(RE_SRC))
|
| 196 |
|
| 197 | build/regexp/%.o: lib/regexp/%.c lib/regexp/re_internal.h
|
| 198 | @mkdir -p build/regexp
|
| 199 | $(CC) -c -O2 $(SEC_FLAGS) -Ilib/regexp $< -o $@
|
| 200 |
|
| 201 | build/sp_bigint.o: lib/sp_bigint.c lib/sp_bigint.h lib/mruby_shim.h
|
| 202 | @mkdir -p build
|
| 203 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_bigint.c -o build/sp_bigint.o
|
| 204 |
|
| 205 | build/sp_crypto.o: lib/sp_crypto.c lib/sp_crypto.h
|
| 206 | @mkdir -p build
|
| 207 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_crypto.c -o build/sp_crypto.o
|
| 208 |
|
| 209 | build/sp_pack.o: lib/sp_pack.c lib/mruby_shim.h
|
| 210 | @mkdir -p build
|
| 211 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_pack.c -o build/sp_pack.o
|
| 212 |
|
| 213 | build/sp_strscan.o: lib/sp_strscan.c lib/mruby_shim.h
|
| 214 | @mkdir -p build
|
| 215 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib -Ilib/regexp lib/sp_strscan.c -o build/sp_strscan.o
|
| 216 |
|
| 217 | build/sp_time.o: lib/sp_time.c lib/sp_time.h
|
| 218 | @mkdir -p build
|
| 219 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_time.c -o build/sp_time.o
|
| 220 |
|
| 221 | build/sp_core.o: lib/sp_core.c lib/sp_core.h
|
| 222 | @mkdir -p build
|
| 223 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_core.c -o build/sp_core.o
|
| 224 |
|
| 225 | build/sp_system.o: lib/sp_system.c lib/sp_system.h
|
| 226 | @mkdir -p build
|
| 227 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_system.c -o build/sp_system.o
|
| 228 |
|
| 229 | build/sp_gc.o: lib/sp_gc.c lib/sp_gc.h lib/sp_types.h
|
| 230 | @mkdir -p build
|
| 231 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_gc.c -o build/sp_gc.o
|
| 232 |
|
| 233 | build/sp_fiber.o: lib/sp_fiber.c lib/sp_fiber.h lib/sp_gc.h lib/sp_types.h
|
| 234 | @mkdir -p build
|
| 235 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_fiber.c -o build/sp_fiber.o
|
| 236 |
|
| 237 | build/sp_net.o: lib/sp_net.c lib/sp_net.h
|
| 238 | @mkdir -p build
|
| 239 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_net.c -o build/sp_net.o
|
| 240 |
|
| 241 | build/sp_io.o: lib/sp_io.c lib/sp_io.h lib/sp_gc.h lib/sp_types.h
|
| 242 | @mkdir -p build
|
| 243 | $(CC) -c -O2 -Wno-all $(SEC_FLAGS) -Ilib lib/sp_io.c -o build/sp_io.o
|
| 244 |
|
| 245 | SP_RT_LIB = lib/libspinel_rt.a
|
| 246 |
|
| 247 | $(SP_RT_LIB): $(RE_OBJ) build/sp_bigint.o build/sp_crypto.o build/sp_pack.o build/sp_strscan.o build/sp_time.o build/sp_core.o build/sp_net.o build/sp_system.o build/sp_gc.o build/sp_fiber.o build/sp_io.o
|
| 248 | ar rcs $@ $^
|
| 249 |
|
| 250 | regexp: $(SP_RT_LIB)
|
| 251 |
|
| 252 | # ---- In-tree developer tools ----
|
| 253 |
|
| 254 | # spinel-doctor / spinel-reduce / spinel-flatten: written in the spinel subset
|
| 255 | # and compiled by spinel itself (dogfood), so their only runtime dependency is
|
| 256 | # cc β the same as the compiler. Each tools/<name>.rb becomes bin/spinel-<name>,
|
| 257 | # beside the compiler, so the `spinel-<name>` command is found next to `spinel`.
|
| 258 | # A tool that no longer fits the subset breaks the build, which keeps them honest.
|
| 259 | TOOL_NAMES = doctor reduce flatten
|
| 260 | TOOL_BINS = $(addprefix bin/spinel-,$(TOOL_NAMES))
|
| 261 |
|
| 262 | tools: $(TOOL_BINS)
|
| 263 |
|
| 264 | bin/spinel-%: tools/%.rb tools/tool_common.rb $(SPINEL) $(SP_RT_LIB)
|
| 265 | @mkdir -p bin
|
| 266 | $(SPINEL) $< -o $@
|
| 267 |
|
| 268 | # ---- Test ----
|
| 269 |
|
| 270 | TESTS := $(wildcard test/*.rb)
|
| 271 | # Mode-incompatible: int_overflow_raises pins raise-mode semantics; under
|
| 272 | # --int-overflow=promote the same code auto-promotes and output diverges.
|
| 273 | ifeq ($(SPINEL_INT_OVERFLOW),promote)
|
| 274 | TESTS := $(filter-out test/int_overflow_raises.rb,$(TESTS))
|
| 275 | # Drive the spinel front-end and the C compile in promote mode so the test
|
| 276 | # rule actually exercises the auto-promotion path end to end.
|
| 277 | SP_OV_FLAG := --int-overflow=promote
|
| 278 | SP_OV_DEFINE := -DSP_INT_OVERFLOW_MODE_PROMOTE
|
| 279 | else
|
| 280 | # `promote_*` tests overflow on purpose and only have defined output under
|
| 281 | # --int-overflow=promote; in raise/wrap mode they would (correctly) raise.
|
| 282 | TESTS := $(filter-out test/promote_%.rb,$(TESTS))
|
| 283 | endif
|
| 284 | TEST_TARGETS := $(patsubst test/%.rb,build/test-results/%.ok,$(TESTS))
|
| 285 |
|
| 286 | # Warnings the generated-C -Werror check should not gate on. clang enables
|
| 287 | # -Wunused-value by default (gcc only under -Wall, which the build disables),
|
| 288 | # so a discarded value-producing statement-expression -- e.g. the
|
| 289 | # `({ ...; v; })` emitted for `Fiber[:k] = v` in statement position -- fails
|
| 290 | # CI under clang while gcc is silent. The value is intentionally discarded;
|
| 291 | # behaviour is still gated by the output diff. Keep this list minimal.
|
| 292 | TEST_WARN_SUPPRESS := -Wno-unused-value
|
| 293 |
|
| 294 | # `make test` always runs fresh: it wipes the prior `.ok` stamps first,
|
| 295 | # then runs the suite. (The old incremental `test` + `retest` split is
|
| 296 | # gone β a stale `.ok` reading PASS was a recurring foot-gun.)
|
| 297 | test:
|
| 298 | +@$(MAKE) --no-print-directory clean-test-results
|
| 299 | +@$(MAKE) --no-print-directory test-run
|
| 300 |
|
| 301 | # The actual run. rbs-test golden-checks the RBS extractor (cheap, C-only).
|
| 302 | # rbs-seed-test checks the seeds actually reach the analyzer (incl. nested
|
| 303 | # classes, #1417). analyze-fail (legacy-analyzer diagnostics) lives in legacy/
|
| 304 | # and runs only via an explicit `make analyze-fail-test` / `cd legacy && make`,
|
| 305 | # not as part of `make gate` or the hot `make test` loop.
|
| 306 | test-run: rbs-test rbs-seed-test $(TEST_TARGETS)
|
| 307 | @if [ -z "$(TIMEOUT_BIN)" ]; then echo "Note: no 'timeout' command found; running without time limits."; fi
|
| 308 | @if [ -t 1 ]; then printf '\n'; fi
|
| 309 | @pass=$$(grep -l '^PASS' build/test-results/*.ok 2>/dev/null | wc -l); \
|
| 310 | fail=$$(grep -l '^FAIL' build/test-results/*.ok 2>/dev/null | wc -l); \
|
| 311 | err=$$(grep -l '^ERR' build/test-results/*.ok 2>/dev/null | wc -l); \
|
| 312 | for f in build/test-results/*.ok; do \
|
| 313 | bn=$$(basename "$$f" .ok); \
|
| 314 | status=$$(cat "$$f"); \
|
| 315 | if [ "$$status" = FAIL ]; then \
|
| 316 | echo "FAIL: $$bn"; \
|
| 317 | head -40 "$$f.diff"; \
|
| 318 | elif [ "$$status" = ERR ]; then \
|
| 319 | echo "ERR: $$bn"; \
|
| 320 | fi; \
|
| 321 | done; \
|
| 322 | echo "Tests: $$pass pass, $$fail fail, $$err error"; \
|
| 323 | if [ $$fail -ne 0 ] || [ $$err -ne 0 ]; then exit 1; fi
|
| 324 |
|
| 325 | # ---- RBS extractor golden tests ----
|
| 326 | RBS_TEST_SRCS := $(sort $(wildcard test/rbs/*.rbs))
|
| 327 |
|
| 328 | ifeq ($(wildcard $(RBS_INC)/rbs/parser.h),)
|
| 329 | rbs-test:
|
| 330 | @echo "rbs-test: skipped (vendor/rbs not fetched; run 'make deps')"
|
| 331 | regen-rbs-expected:
|
| 332 | @echo "regen-rbs-expected: skipped (vendor/rbs not fetched; run 'make deps')"
|
| 333 | else
|
| 334 | rbs-test: $(RBS_EXTRACT_BIN)
|
| 335 | @fail=0; n=0; \
|
| 336 | for f in $(RBS_TEST_SRCS); do \
|
| 337 | n=$$((n+1)); \
|
| 338 | exp="$${f%.rbs}.seed.expected"; \
|
| 339 | if [ ! -f "$$exp" ]; then echo "rbs-test: MISSING golden $$exp"; fail=1; continue; fi; \
|
| 340 | d=$$($(RBS_EXTRACT_BIN) "$$f" 2>/dev/null | diff -u "$$exp" - 2>&1); \
|
| 341 | if [ -z "$$d" ]; then \
|
| 342 | if [ -t 1 ]; then printf .; fi; \
|
| 343 | else \
|
| 344 | echo; echo "rbs-test FAIL: $$f"; echo "$$d"; fail=1; \
|
| 345 | fi; \
|
| 346 | done; \
|
| 347 | if [ -t 1 ]; then printf '\n'; fi; \
|
| 348 | if [ $$fail -ne 0 ]; then echo "RBS extractor tests: FAIL"; exit 1; fi; \
|
| 349 | echo "RBS extractor tests: $$n pass"
|
| 350 |
|
| 351 | regen-rbs-expected: $(RBS_EXTRACT_BIN)
|
| 352 | @for f in $(RBS_TEST_SRCS); do \
|
| 353 | $(RBS_EXTRACT_BIN) "$$f" > "$${f%.rbs}.seed.expected"; \
|
| 354 | echo "regen: $${f%.rbs}.seed.expected"; \
|
| 355 | done
|
| 356 | endif
|
| 357 |
|
| 358 | # End-to-end --rbs seeding check (#1417). The extractor emits a module-nested
|
| 359 | # class under its qualified name (`Outer_Box`), but the compiler's class table
|
| 360 | # stores the leaf name (`Box`) + enclosing_class. seed_class_index must match
|
| 361 | # the two so the seed reaches the class. The fixture's `@label` is declared
|
| 362 | # `String?` but only ever assigned nil, so inference alone leaves it poly --
|
| 363 | # only an applied seed pins it to a `const char *` field. The extractor must sit
|
| 364 | # beside $(SPINEL) (main.c looks for it as a sibling), so copy it there first.
|
| 365 | ifeq ($(wildcard $(RBS_INC)/rbs/parser.h),)
|
| 366 | rbs-seed-test:
|
| 367 | @echo "rbs-seed-test: skipped (vendor/rbs not fetched; run 'make deps')"
|
| 368 | else
|
| 369 | rbs-seed-test: $(SPINEL) $(RBS_EXTRACT_BIN) $(SP_RT_LIB)
|
| 370 | @cp -f $(RBS_EXTRACT_BIN) $(dir $(SPINEL))spinel_rbs_extract
|
| 371 | @tmp=$$(mktemp -d /tmp/spinel-rbsseed.XXXXXX); ok=1; \
|
| 372 | $(SPINEL) test/rbs-seed/nested_ivar.rb --rbs test/rbs-seed/sig \
|
| 373 | -c --no-line-map -o "$$tmp/out.c" 2>/dev/null; \
|
| 374 | grep -Eq 'const char[[:space:]]+\*[[:space:]]*iv_label' "$$tmp/out.c" || { echo "rbs-seed-test: FAIL (#1417: module-nested-class seed not applied)"; ok=0; }; \
|
| 375 | if grep -Eq 'sp_RbVal[[:space:]]+iv_label' "$$tmp/out.c"; then echo "rbs-seed-test: FAIL (#1417: ivar stayed poly)"; ok=0; fi; \
|
| 376 | $(CC) -fsyntax-only -Ilib "$$tmp/out.c" 2>/dev/null || { echo "rbs-seed-test: FAIL (nested_ivar C invalid)"; ok=0; }; \
|
| 377 | $(SPINEL) test/rbs-seed/boundary.rb --rbs test/rbs-seed/sig \
|
| 378 | -c --no-line-map -o "$$tmp/b.c" 2>/dev/null; \
|
| 379 | if $(CC) -O0 -Ilib "$$tmp/b.c" $(SP_RT_LIB) -lm -o "$$tmp/b" 2>"$$tmp/b.err"; then \
|
| 380 | "$$tmp/b" > "$$tmp/b.out" 2>/dev/null; \
|
| 381 | cmp -s "$$tmp/b.out" test/rbs-seed/boundary.expected || { echo "rbs-seed-test: FAIL (#1417 boundary output mismatch)"; diff -u test/rbs-seed/boundary.expected "$$tmp/b.out" || true; ok=0; }; \
|
| 382 | else echo "rbs-seed-test: FAIL (#1417 boundary coercion C did not compile)"; ok=0; fi; \
|
| 383 | $(SPINEL) test/rbs-seed/void_block_tail.rb --rbs test/rbs-seed/sig \
|
| 384 | -c --no-line-map -o "$$tmp/v.c" 2>/dev/null; \
|
| 385 | if $(CC) -O0 -Ilib "$$tmp/v.c" $(SP_RT_LIB) -lm -o "$$tmp/v" 2>"$$tmp/v.err"; then \
|
| 386 | "$$tmp/v" > "$$tmp/v.out" 2>/dev/null; \
|
| 387 | cmp -s "$$tmp/v.out" test/rbs-seed/void_block_tail.expected || { echo "rbs-seed-test: FAIL (void block tail output mismatch)"; diff -u test/rbs-seed/void_block_tail.expected "$$tmp/v.out" || true; ok=0; }; \
|
| 388 | else echo "rbs-seed-test: FAIL (void-returning call as proc tail: C did not compile)"; ok=0; fi; \
|
| 389 | rm -rf "$$tmp"; \
|
| 390 | if [ $$ok -eq 1 ]; then echo "rbs-seed-test: pass"; else exit 1; fi
|
| 391 | endif
|
| 392 |
|
| 393 | # Analyze-fail fixtures test the legacy analyzer's rejection diagnostics;
|
| 394 | # the recipe lives in legacy/Makefile. Thin delegator for `make gate`.
|
| 395 | analyze-fail-test:
|
| 396 | +@$(MAKE) -C legacy --no-print-directory analyze-fail-test
|
| 397 |
|
| 398 | # The .ok target is the test's stamp. Order-only $(SPINEL) keeps a
|
| 399 | # compiler relink from invalidating every test.
|
| 400 | build/test-results/%.ok: test/%.rb $(SP_RT_LIB) | $(SPINEL)
|
| 401 | @mkdir -p build/test-results
|
| 402 | @tmpdir=$$(mktemp -d /tmp/spinel-test.XXXXXX); \
|
| 403 | ast=$$tmpdir/test.ast; \
|
| 404 | ir=$$tmpdir/test.ir; \
|
| 405 | cfile=$$tmpdir/test.c; \
|
| 406 | bin=$$tmpdir/test_bin; \
|
| 407 | exp=$$tmpdir/expected; \
|
| 408 | act=$$tmpdir/actual; \
|
| 409 | args=""; \
|
| 410 | if [ -f "$<.args" ]; then args=$$(cat "$<.args"); fi; \
|
| 411 | rm -f "[email protected]"; \
|
| 412 | $(SPINEL) "$<" $(SP_OV_FLAG) -c --no-line-map -o "$$cfile" 2>/dev/null && \
|
| 413 | $(CC) $(CFLAGS) $(SP_OV_DEFINE) -Werror $(TEST_WARN_SUPPRESS) $(SEC_FLAGS) -Ilib "$$cfile" $(SP_RT_LIB) $(LDFLAGS) -lm $(GC_FLAGS) -o "$$bin" 2>/dev/null; \
|
| 414 | if [ $$? -eq 0 ]; then \
|
| 415 | if [ -f "$<.expected" ]; then \
|
| 416 | LC_ALL=C sed 's/\r$$//' "$<.expected" >"$$exp.n"; \
|
| 417 | else \
|
| 418 | $(TIMEOUT10) $(REF_RUBY) "$<" $$args >"$$exp" 2>/dev/null; \
|
| 419 | ruby_rc=$$?; \
|
| 420 | if [ $$ruby_rc -ne 0 ] && [ "$(REF_RUBY)" != "ruby" ]; then \
|
| 421 | $(TIMEOUT10) ruby "$<" $$args >"$$exp" 2>/dev/null; \
|
| 422 | fi; \
|
| 423 | LC_ALL=C sed 's/\r$$//' "$$exp" >"$$exp.n"; \
|
| 424 | fi; \
|
| 425 | $(TIMEOUT10) "$$bin" $$args >"$$act" 2>/dev/null; \
|
| 426 | LC_ALL=C sed 's/\r$$//' "$$act" >"$$act.n"; \
|
| 427 | if cmp -s "$$exp.n" "$$act.n"; then \
|
| 428 | echo PASS > "$@"; \
|
| 429 | if [ -t 1 ]; then printf .; fi; \
|
| 430 | else \
|
| 431 | echo FAIL > "$@"; \
|
| 432 | diff -u "$$exp.n" "$$act.n" > "[email protected]" 2>&1 || true; \
|
| 433 | if [ -t 1 ]; then printf F; fi; \
|
| 434 | fi; \
|
| 435 | else \
|
| 436 | echo ERR > "$@"; \
|
| 437 | if [ -t 1 ]; then printf E; fi; \
|
| 438 | fi; \
|
| 439 | rm -rf "$$tmpdir"
|
| 440 |
|
| 441 | clean-test-results:
|
| 442 | @rm -rf build/test-results build/analyze-fail-results
|
| 443 |
|
| 444 | # ---- Expected-output regeneration ----
|
| 445 | # Capture each test's reference Ruby output into test/<name>.rb.expected;
|
| 446 | # once committed the test target uses it directly and skips per-test ruby.
|
| 447 | EXPECTED_FILES := $(patsubst test/%.rb,test/%.rb.expected,$(TESTS))
|
| 448 |
|
| 449 | regen-expected: $(EXPECTED_FILES)
|
| 450 |
|
| 451 | test/%.rb.expected: test/%.rb
|
| 452 | @args=""; \
|
| 453 | if [ -f "$<.args" ]; then args=$$(cat "$<.args"); fi; \
|
| 454 | $(TIMEOUT10) $(REF_RUBY) $< $$args >[email protected] 2>/dev/null; \
|
| 455 | rc=$$?; \
|
| 456 | if [ $$rc -ne 0 ] && [ "$(REF_RUBY)" != "ruby" ]; then \
|
| 457 | $(TIMEOUT10) ruby $< $$args >[email protected] 2>/dev/null; \
|
| 458 | rc=$$?; \
|
| 459 | fi; \
|
| 460 | if [ $$rc -ne 0 ]; then \
|
| 461 | echo "regen-expected: $< failed (rc=$$rc); skipping" >&2; \
|
| 462 | rm -f [email protected]; \
|
| 463 | else \
|
| 464 | LC_ALL=C sed 's/\r$$//' [email protected] > $@; \
|
| 465 | rm -f [email protected]; \
|
| 466 | fi
|
| 467 |
|
| 468 | bench: $(SPINEL) $(SP_RT_LIB)
|
| 469 | @if [ -z "$(TIMEOUT_BIN)" ]; then echo "Note: no 'timeout' command found; running without time limits."; fi
|
| 470 | @total=$$(ls benchmark/*.rb | wc -l); \
|
| 471 | if [ -t 1 ]; then tty=1; else tty=0; fi; \
|
| 472 | pass=0; fail=0; err=0; skip=0; i=0; \
|
| 473 | for f in benchmark/*.rb; do \
|
| 474 | i=$$((i+1)); \
|
| 475 | bn=$$(basename "$$f" .rb); \
|
| 476 | if [ "$$tty" = 1 ]; then printf '\r\033[K [%d/%d] %s' "$$i" "$$total" "$$bn"; fi; \
|
| 477 | $(TIMEOUT10) $(SPINEL) "$$f" -c --no-line-map -o /tmp/_sp_b.c 2>/dev/null && \
|
| 478 | $(CC) $(CFLAGS) -Werror $(TEST_WARN_SUPPRESS) $(SEC_FLAGS) -Ilib -c /tmp/_sp_b.c -o /tmp/_sp_b.c.o 2>/dev/null && \
|
| 479 | $(CC) $(CFLAGS) /tmp/_sp_b.c.o $(SP_RT_LIB) $(LDFLAGS) -lm $(GC_FLAGS) -o /tmp/_sp_b_bin 2>/dev/null; \
|
| 480 | if [ $$? -eq 0 ]; then \
|
| 481 | $(TIMEOUT60) $(REF_RUBY) "$$f" >/tmp/_sp_b_exp 2>/dev/null; \
|
| 482 | ruby_rc=$$?; \
|
| 483 | if [ $$ruby_rc -ne 0 ] && [ "$(REF_RUBY)" != "ruby" ]; then \
|
| 484 | $(TIMEOUT60) ruby "$$f" >/tmp/_sp_b_exp 2>/dev/null; \
|
| 485 | ruby_rc=$$?; \
|
| 486 | fi; \
|
| 487 | if [ $$ruby_rc -eq 124 ]; then \
|
| 488 | if [ "$$tty" = 1 ]; then printf '\r\033[K'; fi; \
|
| 489 | echo "SKIP: $$bn (ruby timeout)"; skip=$$((skip+1)); \
|
| 490 | else \
|
| 491 | $(TIMEOUT60) /tmp/_sp_b_bin >/tmp/_sp_b_act 2>/dev/null; \
|
| 492 | LC_ALL=C sed 's/\r$$//' /tmp/_sp_b_exp >/tmp/_sp_b_exp.n; \
|
| 493 | LC_ALL=C sed 's/\r$$//' /tmp/_sp_b_act >/tmp/_sp_b_act.n; \
|
| 494 | if cmp -s /tmp/_sp_b_exp.n /tmp/_sp_b_act.n; then \
|
| 495 | pass=$$((pass+1)); \
|
| 496 | else \
|
| 497 | if [ "$$tty" = 1 ]; then printf '\r\033[K'; fi; \
|
| 498 | echo "FAIL: $$bn"; \
|
| 499 | diff -u /tmp/_sp_b_exp.n /tmp/_sp_b_act.n 2>&1 | head -40; \
|
| 500 | fail=$$((fail+1)); \
|
| 501 | fi; \
|
| 502 | fi; \
|
| 503 | else \
|
| 504 | if [ "$$tty" = 1 ]; then printf '\r\033[K'; fi; \
|
| 505 | echo "ERR: $$bn"; err=$$((err+1)); \
|
| 506 | fi; \
|
| 507 | done; \
|
| 508 | if [ "$$tty" = 1 ]; then printf '\r\033[K'; fi; \
|
| 509 | rm -f /tmp/_sp_b.ast /tmp/_sp_b.ir /tmp/_sp_b.c /tmp/_sp_b.c.o /tmp/_sp_b_bin /tmp/_sp_b_exp /tmp/_sp_b_act /tmp/_sp_b_exp.n /tmp/_sp_b_act.n; \
|
| 510 | echo "Benchmarks: $$pass pass, $$fail fail, $$err error, $$skip skip"; \
|
| 511 | if [ $$fail -ne 0 ] || [ $$err -ne 0 ]; then exit 1; fi
|
| 512 |
|
| 513 | # ---- Optcarrot integration test ----
|
| 514 | OPTCARROT_DIR := build/optcarrot
|
| 515 | OPTCARROT_REPO := https://github.com/mame/optcarrot.git
|
| 516 | OPTCARROT_BRANCH := experiment/spinel
|
| 517 |
|
| 518 | optcarrot: $(SPINEL) $(SP_RT_LIB)
|
| 519 | @if [ ! -d $(OPTCARROT_DIR) ]; then \
|
| 520 | git clone --depth=1 --branch=$(OPTCARROT_BRANCH) $(OPTCARROT_REPO) $(OPTCARROT_DIR); \
|
| 521 | fi
|
| 522 | @ruby $(OPTCARROT_DIR)/tools/pack-for-spinel.rb > build/optcarrot-single.rb
|
| 523 | @$(SPINEL) build/optcarrot-single.rb -c --no-line-map -o build/optcarrot-single.c
|
| 524 | @$(CC) $(CFLAGS) -DSP_INT_OVERFLOW_MODE_WRAP -Ilib build/optcarrot-single.c $(SP_RT_LIB) $(LDFLAGS) -lm $(GC_FLAGS) -o build/optcarrot-single
|
| 525 | @out=$$($(TIMEOUT60) ./build/optcarrot-single 2>&1); \
|
| 526 | echo "$$out"; \
|
| 527 | if echo "$$out" | grep -qE "^fps: [0-9.]+$$" && echo "$$out" | grep -q "^checksum: 59662$$"; then \
|
| 528 | echo "Optcarrot: OK"; \
|
| 529 | else \
|
| 530 | echo "Optcarrot: FAIL β expected 'fps: <num>' and 'checksum: 59662'"; \
|
| 531 | exit 1; \
|
| 532 | fi
|
| 533 |
|
| 534 | # ---- Developer gates ----
|
| 535 | #
|
| 536 | # `test`, `bench` and `optcarrot` only READ the compiler binaries and
|
| 537 | # write to disjoint build/ dirs, so they run concurrently as parallel
|
| 538 | # prerequisites. Every recursive $(MAKE) is `+`-prefixed so the jobserver
|
| 539 | # fd is inherited; none pass an explicit -j (which would force a sub-make
|
| 540 | # to spawn its own pool β oversubscription).
|
| 541 |
|
| 542 | # Fast pre-commit: rebuild the compiler and run the suite. OPT=-O1 compiles
|
| 543 | # the sp_runtime.h-heavy per-test C ~3x faster than -O0 (the optimizer prunes
|
| 544 | # the 800+ unreferenced static fns before codegen). Skips bench/optcarrot β
|
| 545 | # run `make gate` before pushing for those.
|
| 546 | check:
|
| 547 | +@$(MAKE) --no-print-directory LTO=0 all
|
| 548 | +@$(MAKE) --no-print-directory test OPT=-O1
|
| 549 |
|
| 550 | # Full pre-push gate: test || bench || optcarrot in parallel. The C compiler is
|
| 551 | # master; the legacy Ruby self-host bootstrap and its analyze-fail diagnostics
|
| 552 | # are NOT part of the gate (run `make bootstrap` / `cd legacy && make` explicitly
|
| 553 | # for those).
|
| 554 | gate:
|
| 555 | +@$(MAKE) --no-print-directory LTO=0 all
|
| 556 | +@$(MAKE) --no-print-directory gate-legs
|
| 557 | @echo "gate: ALL GREEN"
|
| 558 |
|
| 559 | gate-legs: gate-test gate-bench gate-optcarrot
|
| 560 | gate-test:
|
| 561 | +@$(MAKE) --no-print-directory test OPT=-O1
|
| 562 | gate-bench:
|
| 563 | +@$(MAKE) --no-print-directory bench
|
| 564 | gate-optcarrot:
|
| 565 | +@$(MAKE) --no-print-directory optcarrot
|
| 566 |
|
| 567 | # ---- Install ----
|
| 568 |
|
| 569 | PREFIX ?= /usr/local
|
| 570 | SPNLDIR = $(PREFIX)/lib/spinel
|
| 571 |
|
| 572 | # Install the single `spinel` binary only. The legacy Ruby compiler is a
|
| 573 | # headless regression oracle (legacy/, `make legacy`/`bootstrap`/
|
| 574 | # `analyze-fail-test`) and is not shipped.
|
| 575 | install: all
|
| 576 | install -d $(SPNLDIR)/lib
|
| 577 | install -m 755 $(SPINEL) $(SPNLDIR)/spinel
|
| 578 | install -m 644 lib/libspinel_rt.a $(SPNLDIR)/lib/
|
| 579 | install -m 644 lib/sp_runtime.h $(SPNLDIR)/lib/
|
| 580 | install -m 644 lib/sp_types.h $(SPNLDIR)/lib/
|
| 581 | install -m 644 lib/sp_core.h $(SPNLDIR)/lib/
|
| 582 | install -m 644 lib/sp_system.h $(SPNLDIR)/lib/
|
| 583 | install -m 644 lib/sp_gc.h $(SPNLDIR)/lib/
|
| 584 | install -m 644 lib/sp_fiber.h $(SPNLDIR)/lib/
|
| 585 | install -m 644 lib/sp_io.h $(SPNLDIR)/lib/
|
| 586 | install -m 644 lib/sp_time.h $(SPNLDIR)/lib/
|
| 587 | install -m 644 lib/sp_net.h $(SPNLDIR)/lib/
|
| 588 | install -m 644 lib/*.rb $(SPNLDIR)/lib/
|
| 589 | install -d $(PREFIX)/bin
|
| 590 | ln -sf $(SPNLDIR)/spinel $(PREFIX)/bin/spinel
|
| 591 | for t in $(TOOL_NAMES); do \
|
| 592 | install -m 755 bin/spinel-$$t $(PREFIX)/bin/spinel-$$t; \
|
| 593 | done
|
| 594 |
|
| 595 | uninstall:
|
| 596 | rm -f $(PREFIX)/bin/spinel
|
| 597 | for t in $(TOOL_NAMES); do rm -f $(PREFIX)/bin/spinel-$$t; done
|
| 598 | rm -rf $(SPNLDIR)
|
| 599 |
|
| 600 | # ---- Clean ----
|
| 601 |
|
| 602 | # Wipe the root build tree plus the legacy subtree's own build dir
|
| 603 | # (legacy/build holds the legacy compiler's binaries + bootstrap
|
| 604 | # intermediates). Only the root-level C artifacts need an explicit rm.
|
| 605 | clean:
|
| 606 | rm -rf build/ bin/ legacy/build/
|
| 607 | rm -f legacy/spinel_parse spinel
|