Repository navigation
fix(runtime)!: runtime options go between +RTS and -RTS (#1466) - #1530
Conversation
|
@nicolas-abril is this more in line of how you'd like this fix to land? I'm working on a CLI library for bend hence the motivation for this |
…endlang#1466) A built program reads its argv as GHC's RTS does. The runtime's options are the words between +RTS and -RTS (or the end), after those in $BENDRTS; every other word is the program's, in order, so --threads, --gpu, --gpu-build and --bend-help outside a bracket reach IO.args. -- ends the options and stays the program's; --RTS ends them and goes. An unknown word inside a bracket fails. - C main: gathers BENDRTS's and the bracketed words, then parses them with the existing option code; CLI_HELP describes the bracket. - JS cli(): drops the bracket the same way; it has no options to set, so it ignores their contents and BENDRTS. `let cli_args = [];` stays for bend-lint's seam. - bend -o runs `+RTS --gpu-build`; perf.ts's FLAGS are bracketed. - ping.ts checks a built program's argv on C and JS, --RTS and BENDRTS. comp.ts: 63,067 -> 63,484 ttok.
- C main: "" closes BENDRTS and each bracket, so an option takes its value from its own span (BENDRTS="--threads" ./p +RTS 2 fails). - JS cli: checks a bracket's names and values with C's messages, so a typo fails alike on both; +RTS --gpu-build exits 0. - bend -o: the GPU program's build runs with BENDRTS cleared. - gates/ping.ts: a value across spans and three malformed options must fail with the same stderr on C and JS.
…nd JS - C main: NULL closes each span, so an empty word in a bracket is an unknown option (+RTS '' -RTS fails), as it now is in JS. - C and JS take one grammar: --threads is digits, at least 1; --gpu is on, off, or digits with at most one point and GB or MB, above zero. C's strtol and strtod also took a sign, spaces, exponents and hex. - JS cli: picks the pattern by name, not from an object, so +RTS constructor fails like any unknown option. - gates/ping.ts: +RTS --gpu 1e2GB and +RTS '' -RTS must fail alike.
main grew 428 ttok since this branch's base, which put the rebased comp.ts at 64,151, over its 64k cap. Now 63,953, with the same behavior: - main and cli lose their comments; CLI_HELP says the rule. - Comments that repeat another go: JS show_val's and io_list's (the C ones say it) and ring_rewind's (its name says it). - C main: an unknown option prints and returns 1, as --gpu-build's failure does, instead of formatting a message for err_fail. - JS cli: --gpu's size is \d*\.?\d* under the nonzero lookahead, the same set as (\d+\.?\d*|\.\d+). - The out-of-memory error's hint reads +RTS --gpu 8GB, since a bare --gpu 8GB is now the program's argument.
nicolas-abril
left a comment
There was a problem hiding this comment.
The bracket parsing looks good. The ping checks pass on C and JS for me, with the same output on both. I found three issues, inline below.
| int gpu = -1; | ||
| u64 mem = 0; | ||
| char* env = strdup(getenv("BENDRTS") != NULL ? getenv("BENDRTS") : ""); | ||
| char** rts = malloc((argc + strlen(env)) * sizeof(char*)); |
There was a problem hiding this comment.
This can overflow when argc == 0. rts holds the BENDRTS words, a NULL, and at most argc - 1 more entries from argv, so with argc >= 1 it fits. With argc == 0 (an execve with argv = {NULL}) and BENDRTS unset or one character long, malloc gets 0 or 1 pointers and rts[nr++] = NULL writes past the end. Running it that way, the binary dies with bend: memory fault.
| char** rts = malloc((argc + strlen(env)) * sizeof(char*)); | |
| char** rts = malloc((argc + strlen(env) + 1) * sizeof(char*)); |
| } else if (!strcmp(v, "on") | ||
| || (mul != 0 && n > 0 && end == v + strspn(v, "0123456789."))) { | ||
| gpu = 1; | ||
| mem = (u64)(n * (double)mul); |
There was a problem hiding this comment.
n > 0 is checked before the size is converted to bytes, so a tiny size passes the check but ends up as 0 bytes. (u64)(n * mul) truncates, and the reservation is later rounded down by & ~16383 (line 5245). On Metal with tests/run/array_atomic_fadd.bend:
+RTS --gpu 0.0000001MBmakesmem0, so it silently falls back to the default size.+RTS --gpu 0.001MBrounds to a 0-byte span and fails withbend: reservation failedinstead of the size error.
Could the check run on the byte count after rounding (or require at least one 16 KB page)? JS's size regex accepts the same inputs, so it would need the matching rule to keep the C/JS errors identical.
| "off", "-RTS", "", "space value", "--", "+RTS", "x"]; | ||
| const kept = ", <grep>, <-e>, <--threads>, <>, <space value>, <-->, <+RTS>," | ||
| + " <x>]\n"; | ||
| const c_rts = await run(argo, rts); |
There was a problem hiding this comment.
run() goes through lib.exec, which spreads process.env into the child's environment (_lib.ts:67), so the operator's BENDRTS reaches every program these checks run. With BENDRTS="--threads 0" exported on the gate host, this check exits 1. With BENDRTS=--gpu-build, every built binary run by ping or test.ts exits 0 with no output. main.ts already clears it for bend -o. Could run() default to BENDRTS: "" (before ...env, so the checks that set it still work), and test.ts do the same?
… a page, clear BENDRTS in the gates
- main's rts array held argc + strlen(env) words, one short when argc is 0
and BENDRTS is empty or one character: the closing NULL overflowed the
heap block (ASan: heap-buffer-overflow on execve with argv = {NULL}).
- --gpu checked n > 0 before converting, so a size under one 16 KB page
became 0 bytes: 0.0000001MB fell back silently to the default span and
0.001MB failed with "reservation failed". Both now fail with the size
error, on C and JS alike; ping checks 0.001MB.
- ping's run() and test.ts's shard script spread the operator's
environment, so an exported BENDRTS reached every program the gates ran
(ping went 60/65 with BENDRTS="--threads 0"). Both clear it now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
I pushed d322836 with fixes for the three issues from my review:
ping 66/66, repo 54/54, test 1635/1635. comp.ts is at 63,992 tokens. The branch conflicts with main in |
# Conflicts: # bend2/comp.ts
- main started io_argc at 1, so a program exec'd with an empty argv read the NULL argv[0] in IO.args and stopped with a memory fault. It now starts at argc > 0, and IO.args answers an empty list. - GUIDE.md and SHADERS.md still showed `./file --threads 8` and `--gpu off`, which are now the program's arguments. They show the bracket, and IO.args's paragraph names it instead of `--`. - comp.ts's cap is 66k on main, so io_list's comment, dropped to fit 64k, comes back (65,804 tokens). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
I merged main and pushed cce9d84. It's mergeable again.
ping 70/70, repo 54/54, bend-lint 134/134. The cluster test gate kept failing on shards whose working directory was deleted mid-run ("Module not found bend2/main.ts"), probably a concurrent run. The 110 tests it reported all match their expected output when run locally, apart from 12 expected-error tests that give the same output on main. |
|
Follow-up: I reran the gates on cce9d84 with the cluster quiet. test passes 1691/1691, ping 70/70 and repo 54/54. |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Inside the bracket, --help can't collide with a program's own flag, so --bend-help becomes --help, as in GHC's +RTS --help. -- isn't a runtime option, so its line leaves the help. ping checks +RTS --help on C and JS, and that a bare --help is the program's argument. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…me's options -- no longer stops +RTS parsing: it reaches the program like any other word, and a bracket after it is still the runtime's. --RTS stays the one escape. Inside a bracket, -- is an unknown runtime option, on C and JS. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Updated description (as of 8507661)The description above no longer matches the branch. This is what the branch does now. Fixes #1466. A compiled program (C or JS) reads runtime options only between The rule
Migration
Old flags outside a bracket aren't rejected. They silently become program arguments. A binary keeps its old behavior until it's rebuilt. What changes
Not in this PR
Checks at 8507661
|
Bend 2's compiled programs now read runtime options only inside the bracket (bendlang/bend#1530); a bare --gpu off is the program's argument. The pinned runtime reads it either way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bend 2's compiled programs now read runtime options only inside the bracket (bendlang/bend#1530), so the checks run their binaries with +RTS --threads 2 --gpu off. Older runtimes read the options either way, and the game ignores the extra +RTS word. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bend 2's compiled programs now read runtime options only inside the bracket (bendlang/bend#1530), so make and the validation scripts run their binaries with +RTS --gpu off. Older runtimes read --gpu off either way, and the programs ignore their arguments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bend 2's compiled programs now read runtime options only inside the bracket (bendlang/bend#1530). Run outside it, --gpu-build became the program's argument, so a Metal leg compiled its GPU program at run time and diverged from C-seq on stderr (two false leg-diverge findings in 12 seeds); --threads and --gpu went unapplied. Older runtimes read the options either way, and generated programs ignore their arguments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixes #1466. Follows #1476, which read the runtime's settings from the environment. It was closed in favor of "a solution that is more similar to what haskell does" (comment). This PR is that design: a compiled program reads its argv the way GHC's RTS does. It also finishes what #934 and #988 started for
--help, for the remaining runtime options.The rule
+RTSand-RTS. A trailing-RTScan be left off.IO.args), in order. Outside a bracket,--threads,--gpu,--gpu-buildand--bend-helpare ordinary arguments.--stops the runtime from reading options and is passed to the program. So runtime options are still taken only until the first--, and the program now sees that--.--RTSdoes the same as--but is dropped.BENDRTSholds options too. It's read before the command line, so./main +RTS --threads 4overridesBENDRTS="--threads 8".BENDRTSand each bracket are read separately, so an option's value must be in the same span.BENDRTS="--threads" ./main +RTS 2fails, and so does./main +RTS --threads -RTS x +RTS 2.bend: unknown runtime option --bogus. So does an empty word.--threadstakes digits, at least 1.--gputakeson,off, or digits with at most one point followed byGBorMB, above zero. main's C parser (strtol,strtod) also took a sign, leading spaces, an exponent or hex, so--gpu 1e2GBand--threads +4worked there; they fail now, on C and JS alike.GHC does the same in its argv loop and with
GHCRTS.Before and after
argsprintsIO.args(), #1466's program; the head is left out below. These rows are from the C binary. The JS build of this branch prints the same lists../args …grep -e --threads notes.txtbend: expected a thread count of 1 or more after --threads, exit 1[<grep>, <-e>, <--threads>, <notes.txt>]compress --threads auto[<compress>, <--threads>, <auto>]build --threads 4[<build>][<build>, <--threads>, <4>]make --gpu-buildmainnever runs[<make>, <--gpu-build>]grep -- -v[<grep>, <-v>][<grep>, <-->, <-v>]grep +RTS --threads 4 -RTS -v[<grep>, <+RTS>, <-RTS>, <-v>][<grep>, <-v>]--bend-help[<--bend-help>]Migration
./main --threads 8./main +RTS --threads 8, orBENDRTS="--threads 8" ./main./main -- -v./main -v./main --bend-help./main +RTS --bend-help./main --gpu-build./main +RTS --gpu-build(bend -opasses it itself)A binary keeps its old behavior until it's rebuilt.
What changes
bend2/comp.ts:maincollectsBENDRTS's words and the bracketed words, then parses them with the existing option code. A NULL closes each span, so a value can't come from the next one and an empty argument stays an argument.CLI_HELPdescribes the bracket.+RTS --gpu 8GB, since a bare--gpu 8GBis now the program's argument.cli()drops the bracket the same way. The JS runtime has no options to set, but it checks a bracket's option names and values with C's grammar and messages, so a typo fails on both.+RTS --gpu-buildexits 0 with no output. JS ignoresBENDRTS.let cli_args = [];is unchanged, since bend-lint's seam matches that line.main's andcli's comments, sinceCLI_HELPstates the rule. It also drops three comments that repeat another: JSshow_val's andio_list's (the C ones say the same) andring_rewind's. An unknown option prints and returns 1, as--gpu-build's failure does, and JS's size pattern is shorter but accepts the same set.BENDRTSis 90 of those tokens, so it can be dropped if you'd rather not have it.bend2/main.ts:bend -oruns+RTS --gpu-build, withBENDRTScleared so the user's options can't change or break the build.gates/perf.ts:FLAGSgets the bracket.gen_pins.tsreads the same constant.gates/ping.ts: adds two checks beside "a plain run gets its arguments":--, an empty argument and a space are kept, and a mid-line bracket is dropped.--RTSis dropped and ends the options, a validBENDRTSis accepted, andBENDRTS="--threads 0"exits 1, as does a value in the span after its option.+RTS --thread 2,+RTS --gpu of,+RTS --gpu 1e2GB,+RTS '' -RTSand a value across brackets fail with exit 1 and the same stderr on C and JS.tests/io/args.bend: only its comment changes.Not in this PR
bend file.bend …is unchanged. The driver accepts its options after the file (bend <file.bend> -o <out>), so the program's arguments still follow its--, as withcargo run -- …. The interpreter has no runtime options, so it passes the words after--through unchanged,+RTSincluded.-with-rtsoptsand-rtsoptscould come later.--threadsand--gpu, and those names stay.Suggested doc changes (not committed)
guide/GUIDE.md: it's human-edited and sits at 7,998 of its 8,000 tokens, so the two GPU lines become one. With this change it's at 7,993:
guide/SHADERS.md:24:
`--gpu off`becomes`+RTS --gpu off`.Spelling
+RTSand-RTSmatch GHC exactly, so GHC's documentation of them applies here as written. If you'd prefer a Bend-specific spelling like+bendand-bend, it's a rename in the C and JS loops.What I ran
All on an M3 Max Mac, rebased onto main at d2aeefc.
--RTS,--inside a bracket,BENDRTSwith extra whitespace, and an empty, a spaced and a Unicode argument.IO.thread_countprints 3 with+RTS --threads 3, and 5 withBENDRTS="--threads 5"../tc --threads 3prints the CPU count.bend -oontests/run/array_atomic_fadd.bendwritesfadd.gpu, and the binary prints68and2448.25, also with+RTS --gpu 512MB,--gpu .5GBand--gpu off. It still does withBENDRTS="--bend-help"orBENDRTS="--threads 0"set../fadd +RTS --gpu-buildexits 0 with no output. I didn't test a host with no GPU; that code path is unchanged.bun bend2/main.tsasbend. All 7 pass on this branch, and 6 fail on its first commit.--gpu 4GB,512MB,0.5GB,.5GB,5.GB,on,off, and--threads 3,007. Rejected:--gpu +1GB,1e2GB," 4GB",0x10GB,infGB,0GB,4gb;--threads " 3",+3,0,3x; an empty word;constructor; and--threads -RTS 2.gates/test.ts's shard script, run locally on this Mac in 4 shards, passes 1633 / 1635.gfx_clicksandgfx_window. They expect "no display", but this Mac has a desktop session, so a window opens.gates/repo.tspasses, 54 / 54.FLAGSchange), Linux and CUDA.