Every program in the repository and on the PC3's SD card was compiled by the board's own compiler, and each statement it left as a text fallback was found, classified and weighted by whether it runs inside a loop. Headless workloads were then run under OPTION PROFILING to see which fallbacks actually execute. The result is the next set of statements for the compiler.
COM16: PicoMiteHDMIWEB V7.0.00b4 (development, SDK 2.3.1), RP2350B at 315 MHz, 6 MB PSRAM, OPTION COMPILE ON. The census is of the morning's build, V7.0.00b4 (stream format RB_VERSION 33). The updates re-measured the games, the benchmarks, walkr and the workloads during the day, last on V7.0.00b5 as released (RB_VERSION 37, the same firmware, at 315 MHz); the commits are in the summary.
.bas on the SD card;PAGE 622 times, IMAGE, CONTROLLER, or a SUB from a library they don't carry;Function MIN, too large to load);Bas/chess/chessiter.bas) joined the games and the workloads during the day, and Prince of Pico the workloads once it ran again.rtmmoon = vecmag(rtmoon()), moon(jdtdb, rmoon())), then a 16-digit number in MOON, which stays text, and its console PRINTs;IF ... GOTO./* ... */ block costs nothing in a compiled loop, as comment lines don't (section 7);cmd_dim bugs its tests found: STATIC s$ LENGTH n = value always failed, and in a STATIC list an AS type carried over to the names after it on every call but the first;Bas/bench. All the Route B tests pass, and on the goldens corpus the build prints the same with the compiler off and on, but for timings and an RTC status register./* ... */ blocks in its main loop, 149,461 (fixed in c1cddda);Static Float dt_ms(499), Static Integer p = 0, first = 1, n = 0 and Local Float t = Timer, _t. All three compile from 74ed383, the STATICs on every call after the first; 10,000 calls of the timing SUB take 611 ms against 947 ms as text.LLAB$(i).vecmag(rtmoon()), and its bracketed SUB calls with whole arrays; Prince of Pico's LoadChar kRec() too.OPTION LOCAL VARIABLES n raises.Bas/chess/chessiter.bas runs the same search as a loop, at most 7 call levels deep, and is the version measured here.| Programs | Runnable | Statements | Compiled | In loops | Compiled in loops | Now |
|---|---|---|---|---|---|---|
| Named games and other repository games | 9 | 16,542 | 86.2% | 15,007 | 89.3% | 93.0% |
| Benchmarks | 30 | 6,818 | 77.8% | 4,171 | 83.7% | 91.4% |
| Other repository programs and tests | 148 | 37,152 | 74.2% | 15,551 | 80.1% | ≈ 85.6% |
| SD card programs (runnable on V7) | 362 | 56,028 | 66.9% | 40,147 | 75.2% | ≈ 84.0% |
| Goldens corpus (B:/g) | 98 | 4,385 | 30.4% | 516 | 77.3% | ≈ 81.0% |
| walkr-robot (github.com/teuler) | 3 | 1,639 | 32.5% | 1,245 | 33.7% | 42.1% |
| All | 650 | 122,564 | 70.5% | 76,637 | 78.8% | ≈ 85.8% |
Statements exclude declarations (SUB/FUNCTION headers, DIM, CONST, OPTION, DATA, TYPE blocks), which run once or never. Now: compiled in loops on V7.0.00b5, censused again for the games, the benchmarks and walkr. ≈ marks a projection from the morning census: a fallback whose every item is now done counts as compiled, with the limits of what was done (dotted names only in programs without a TYPE; TEXT and the other drawing commands only with no FUNCTION call and no built-in still text in their arguments; MM.xxx, PEEK( and MATH( only in the forms compiled; VAL( only as the whole right-hand side of a numeric assignment; CHOICE( only with branches of one kind; MM.INFO( only with the compiler’s keywords; BLIT only in its FLASH, FRAMEBUFFER, READ, WRITE, CLOSE and plain forms; a LOCAL value that names a variable the same LOCAL makes, calls a FUNCTION or a built-in still text, or is an array’s list stays text, and so does a structure type). Against the three groups censused again on V7.0.00b5, the projection was 0 to 1.0 point high: games 93.0% projected and measured, benchmarks 91.8% against 91.4%, walkr 43.1% against 42.1%. 264 of the 914 programs were left out as not runnable on this firmware: unknown command / undefined SUB 219, status nocompile 33, status run 7, status load 3, line map mismatch 2.
Items 1, 3 and 4 are done, and item 2 for programs that define no TYPE, all committed on 3 October. The counts in the table are the morning census's, so for those four they show what each item has taken. The list is ordered by three things, in this order:
Statements outside loops run once, so they are not counted towards the order.
FRAMEBUFFER COPY, CLS and Draw3D WRITE are busy lines because of the drawing they do.| # | Statement or form | In loops | Alone | Programs | Games | Effort |
|---|---|---|---|---|---|---|
| 1 | Array elements as SUB and FUNCTION arguments done | 651 | 368 | 71 | 8 (114) | Small to medium |
| 2 | Dotted names that are not structure members: R.running, srv.pCurr() half done | 400 | 259 | 32 | 0 (0) | Small (no TYPEs) to medium (with TYPEs) |
| 3 | CONST names as SUB and FUNCTION arguments done | 109 | 109 | 18 | 4 (59) | Small |
| 4 | SUB calls written with brackets: Foo(a, b) and Foo() done | 2,004 | 1,351 | 94 | 2 (3) | Small |
| 5 | TEXT, CIRCLE and the other drawing commands through the value splice mostly done | 1,267 | 1,267 | 137 | 8 (290) | Small per command |
| 6 | Strings through calls: string arguments, string parameters, string FUNCTION results | 1,288 | 585 | 126 | 8 (173) | Medium |
| 7 | LOCAL in all its forms, and STATIC mostly done | 846 | 846 | 163 | 8 (39) | Small (comment, AS type, LENGTH) to medium (value, arrays, STATIC) |
| 8 | Built-in functions: TIMER, INKEY$, MM.INFO, KEYDOWN, PEEK, PIXEL, ASIN/ACOS/ATAN2, MAX/MIN, VAL, CHOICE mostly done | 1,838 | 1,569 | 312 | 9 (148) | Small each |
| 9 | MATH (vector, matrix and quaternion forms) | 321 | 321 | 50 | 1 (71) | Medium |
| 10 | GOTO, IF ... GOTO, IF ... THEN label, GOSUB, ON n GOTO | 75 | 75 | 41 | 2 (7) | Small (GOTO, IF ... GOTO) to medium (GOSUB, ON) |
| 11 | CONTINUE DO | 13 | 13 | 9 | 3 (3) | Small |
| 12 | SELECT CASE beyond 10 values, and string SELECT CASE | 99 | 81 | 43 | 6 (16) | Medium |
| 13 | String array elements | 503 | 272 | 67 | 5 (23) | Medium |
| 14 | Structure (TYPE) members | 315 | 257 | 25 | 0 (0) | Medium to large |
| 15 | Whole arrays as arguments, and array copy a() = b() | 383 | 142 | 95 | 4 (54) | Medium |
| 16 | READ and RESTORE | 463 | 463 | 85 | 7 (112) | Medium |
| 17 | Single-line IF ... THEN call ELSE call | 81 | 76 | 50 | 7 (31) | Small to medium |
| 18 | FUNCTION calls inside SUB arguments, FOR, DO/LOOP, SELECT and the splices | 137 | 42 | 40 | 3 (45) | Medium |
| 19 | 1,506 | 1,506 | 502 | 9 (215) | Medium |
In loops: fallback statements that run inside a loop (a FOR/DO/WHILE body or a backward GOTO, or anywhere in a SUB or FUNCTION reached from a loop or an interrupt). Alone: of those, the ones this item would compile by itself. Games: games affected (statements in loops).
done Committed af67bb1. An element alone goes by its address (RP_ELEM, RC_ADEL); BYVAL stays text. Exile kernel: text statements per run 55,619 → 20,170, compiled share 97.8% → 99.2%, profiled run 18.12 → 16.91 s. In the census it compiles 476 in-loop statements in 51 programs that need nothing else. Test: Bas/bench/rbelemarg.py.
Today: RBArgs refuses an argument that is an element on its own, name(i [, j]), because DefinedSubFun passes it by reference, so the whole call stays text (Stream.c, the FindSubFun(q, 1) < 0 test in RBArgs). Typical statements are a = Add8(obPat(tAddr + yy), tYoff, 0) and Bar DR, DRY(3 + i), e, 3, cYellow, cRed.
To add: Compile the element's address with RBElement(..., RC_ADEL) and pass it to RC_CALL/RC_FCALL as a pointer argument: a new RP_ flag meaning the address is on the stack, not in a bind. RBMakeParams then makes the parameter a T_PTR to the element, as DefinedSubFun does. The bounds check is the one RC_ADEL already makes.
Evidence: It is the hottest fallback in the Exile kernel: 7,061 of the 78,723 samples on its 40 busiest lines (Add8, Sub8, SevenEighths(vel(0))), and 29,030 text LETs per run. It is also in Elite's dashboard (Bar, 1,664 samples) and gfx_frames (1,970).
Bar DR, DRY(0), dSpeed \ 2, 14, cRed, cYellow ' Elite (program + library), line 2193
Pointer DR, DRY(1), 8 + alp2 * (alp1 \ 4) ' Elite (program + library), line 2194
Pointer DR, DRY(2), 8 + bet2 * bet1 ' Elite (program + library), line 2195half done Half done, committed 418892f: in a program that defines no TYPE, dotted names compile as the plain names they are (RBDotBlocked). That compiles only 38 in-loop statements in 9 programs. The other 221 in-loop statements that need nothing but this are in programs that define a TYPE, nearly all of them walkr's, so walkr gains nothing yet (section 5). The half with TYPEs, the per-symbol flag described below, is still to do. Test: Bas/bench/rbdotted.py. walkr's author has since renamed its dotted names that are not structure members: computing a move then took 5.2 ms compiled against 8.5 ms as text (V7.0.00b4).
Today: Every name whose spelling has a dot is refused as a structure member: RBVarRefS, RBElement and RBCompileLocal test memchr(sp, '.', len). Many MMBasic programs use dots as namespaces on plain variables (Dim integer R.running, Dim Float srv.pCurr(17)). And when a program defines any TYPE, findvar does not bind a dotted symbol either (FINDVAR_DOTBLOCKS: g_structcnt > 0 and SYMC_DOT), so the text path looks it up by hashing every time.
To add: When the program defines no TYPE, findvar already binds dotted symbols, so the compiler only has to stop refusing them. When it does, a dotted name is a structure member only if the part before a dot names a TYPE instance. That can be settled once per name at PrepareProgram, where the TYPEs and their instances are known (a per-symbol flag beside SYMC_DOT), and then both findvar's binding and the compiler can treat the other dotted names as the plain names they are.
Evidence: It is the main reason walkr-robot gains under 5% (section 5): 306 of its 788 in-loop text statements need it, 216 need nothing else, and fixing it alone takes walkr's compiled share in loops from 33% to 51%. It is also the style of Petrobot, superclock and the solar_eclipse INPUT lines.
Loop Until (Abs(Val(obslat.deg$)) <= 90.0 And (obslat.min >= 0.0 And obslat.min <= 60.0) And (ob ' solar_eclipse.bas, line 1913
Loop Until (Abs(Val(obslong.deg$)) >= 0.0 And Abs(Val(obslong.deg$)) <= 360.0) And (obslong.min ' solar_eclipse.bas, line 1938
we.wait_for_key$ = k$ ' crash.BAS, line 4done Committed 1649bf0. The survey marks each CONST; a CONST argument compiles as a value (RC_LDG), and its bind carries RB_BCONST, so if the name is not a CONST at run time the call falls back. Elite's demo: text statements per run 42,533 → 33,676. Test: Bas/bench/rbconstarg.py.
Today: A variable argument binds as a target, so that a CONST, which DefinedSubFun passes by value, goes to the fallback. The record compiles, but every call with a CONST argument falls back at run time. The static census counts such calls as compiled; the measured runs show them.
To add: The survey already reads every CONST (RBSurveyConst). An argument that is a known CONST compiles as a value (RC_LDG, bound without RC_TARGET), which is what DefinedSubFun does with it; any other name stays a by-reference bind, as now.
Evidence: Elite's Bar DR, DRY(3 + i), e, 3, cYellow, cRed, where CONST DR = 260: its calls stayed text after item 1 for this reason alone. Elite's Sfx SFX_BEEP, KillShip SLOT_STAR and Crosshair SRCX, SRCY, 8 are the same. 139 compiled SUB calls in 18 programs pass a CONST name, 109 of them in loops, in Elite, Chuckie Egg, Prince of Pico and Thrust (FUNCTION calls not counted).
done Committed 1ff19e2. RBCompileCall reads the list with RBArgs(..., ')') when nothing but a comment follows the closing bracket; any other form stays text, as makeargs would read it differently. 200 bracketed calls ran 204 statements as text before and 4 now. In the census it compiles 1,459 in-loop statements in 53 programs that need nothing else, the largest gain of the four. Test: Bas/bench/rbbracket.py.
Today: RBCompileCall returns 0 when the name is followed by ( (if (*q == '(' || !RBArgs(...))), so every bracketed call goes to DefinedSubFun.
To add: Do what DefinedSubFun does. A caller's list that starts with ( is split by makeargs with the delimiters "(," (MMBasic.c, the makeargs call in DefinedSubFun), so take the outer brackets off and call RBArgs(..., close = ')') as RBFcall already does. Foo() is a call with no arguments.
Evidence: This is the most common in-loop fallback in the whole corpus: 93 programs, about 1,350 statements that need nothing else (Petrobot sprite_item(&h53,h2-8,v2-8), the solar_eclipse variants, turtle and regex tests, draw()). The repository's games rarely write calls this way, so none of the measured workloads exercise it.
MarketScreen(-1) ' Elite (program + library), line 5403
LoadLevel((mission - 1) MOD NLEVEL) ' Thrust, line 715
LoadLevel((level + NLEVEL - 1) MOD NLEVEL) ' Thrust, line 290mostly done Committed f9ffb87 for TEXT, CIRCLE, TRIANGLE, ARC and RBOX: TEXT with string values, a bare-word justification (GetJustificationArg at compile time, as the handler would run it) and a #font; CIRCLE behind OPTION LEGACY's guard (RC_GUARD, as LINE). Still text: BLIT (its in-loop uses are nearly all BLIT FLASH, a keyword form), POLYGON (arrays), TRIANGLE SAVE/RESTORE, array forms, and a FUNCTION call in the arguments. Measured: 2,000 TEXTs 424 → 411 ms and 2,000 CIRCLEs 52 → 44 ms, OFF → ON: the drawing is most of their time. Test: Bas/bench/rbdraw.py (a pixel checksum of each drawing, the same OFF, ON and SHADOW). Left as text across the corpus (projected, in loops), 4 October: 278 statements. BLIT FLASH 163 (18 programs, among them Exile and Prince of Pico); the plain BLIT 20; BLIT READ, WRITE and CLOSE #n, FRAMEBUFFER and MEMORY 28; POLYGON 20 (its arrays need the splice's array kind, as MATH does); an MM.INFO( keyword in the arguments 25; a user FUNCTION call in the arguments 18 (Elite's TEXT 30, y, ViewWord$(v) + ...). gfx_frames' TEXT, its largest fallback, is held by its string array element LLAB$(i) (item 13), not by this item. Since, in 716345a: BLIT FLASH, BLIT FRAMEBUFFER, BLIT READ, WRITE and CLOSE, and the plain BLIT compile through the command splice (cmd_blit reads symbols, so the keyword and a buffer's letter stay in the text as they are). Of the 278 above, that leaves POLYGON, the MM.INFO( keywords and FUNCTION calls in the arguments, and BLIT LOAD, MERGE, RESIZE and COMPRESSED. Test: Bas/bench/rbbig4.py.
Today: RBCompileSplice takes only BOX, LINE, PIXEL and COLOUR, and only numeric values (RBEvaluate).
To add: Add TEXT, CIRCLE, TRIANGLE, ARC, RBOX and the plain BLIT form to the list, after checking that each handler reads its arguments with getargs and getint/getnumber/getstring. Let the command splice carry string values too: RBSpliceValue already returns strings for the function splice ('0'+i), so RBEvaluateS can replace RBEvaluate there. TEXT's justification may be a bare word (LT), which stays in the spliced text as its symbol; GetJustificationArg reads a symbol's spelling.
Evidence: TEXT is gfx_frames' largest fallback (9,999 of 82,490 samples on its busiest lines). In Elite, CIRCLE accounts for 2,048 text executions and 2,787 samples and TEXT for 973 and 1,731. TEXT also stays text in 82 programs and all 8 games. How much of each command's time is argument parsing rather than drawing is not measured here.
TEXT MSGX, MSGY, msgText$, "LT", 7, 1, cWhite ' Elite (program + library), line 2160
TEXT 17, DLY(i) - 1, LLAB$(i), "RT", 7, 1, cWhite ' Elite (program + library), line 2177
TEXT 303, DRY(i) - 1, RLAB$(i), "LT", 7, 1, cWhite ' Elite (program + library), line 2181
CIRCLE cx, cy, r, 1, 1, cGreen, cGreen ' Elite (program + library), line 1936
CIRCLE cx, cy, r, 1, 1, cGreen, -1 ' Elite (program + library), line 1938
CIRCLE CPX, CPY, CPR + 2, 1, 1.25, cDim, -1 ' Elite (program + library), line 2184Today: RBParams refuses a $ or AS STRING parameter, RBArgs evaluates arguments with RBEvaluate (numbers only), and RBFcall refuses a string result.
To add: A string variable argument binds as a target and goes by reference (a T_PTR to its string). A string expression is evaluated with RBEvaluateS and copied into a new local of the parameter's length, as DefinedSubFun does. A string result comes back as the FUNCTION's local string, copied to temporary memory for RC_LDS.
Evidence: Every game except the Exile physics kernel. Elite's chart and market code (DataLine y, "Economy", EcoName$(sysEco), IF SysName$() = "LAVE") and the regex and turtle tests are the biggest users.
Message "ENERGY LOW" ' Elite (program + library), line 2165
nm$ = UCASE$(AskText$("Find system: ", 10)) ' Elite (program + library), line 2848
DataLine y, "Distance", STR$(SysDist(homeX, homeY, sysX, sysY * 2) / 10) + " Light Years" ' Elite (program + library), line 2939
IF SysName$() = "LAVE" THEN EXIT FOR ' Elite (program + library), line 2315
IF SysName$() = nm$ THEN found = i ' Elite (program + library), line 2854
nm$ = SysName$() ' Elite (program + library), line 2890mostly done Committed 74ed383. LOCAL with a trailing comment, an AS type after a name, LENGTH n, arrays (any number of dimensions) and mixes of them; LOCAL name = value, each name made and set before the next, as cmd_dim does (RC_LOCALV, the value's code, RC_LOCALSET with SetValue's conversions and evaluate's errors); STATIC on every call after the first, once each name's global exists (RBStatic finds them all before making anything, then links each local as cmd_dim does, through its own code split out as StaticVarName and StaticLink). The tests found two cmd_dim bugs, fixed in the same commit: STATIC s$ LENGTH n = value always failed, and in a STATIC list an AS type carried over to the names after it on every call but the first. Still text: a LOCAL value that names a variable the same LOCAL makes (Local a = 1, b = a), calls a user FUNCTION, or is an array's list; a structure type; the first call's STATIC. Measured, 10,000 calls OFF → ON: a SUB opening with LOCAL i As integer, f As Float, a(3) 396 → 288 ms; walkr's timing SUB (two STATICs and a LOCAL with a value) 947 → 611 ms. Test: Bas/bench/rblocal.py (29 cases, each the same OFF, ON and SHADOW). Left as text across the corpus (projected, in loops), 4 October: 62 statements. A user FUNCTION call in a value 30 (Local i=findslot()); an array with a list of values 16; a built-in that is still text in a value (CHOICE, BOUND, VAL, MM.INFO(, MATH forms) 14; a value naming the LOCAL's own variable 1. None of the measured workloads runs a LOCAL or STATIC as text.
Today: RBCompileLocal takes only LOCAL [AS] [type] name {, name} with nothing after the last name. A trailing comment, name AS type, LENGTH n, = value or brackets send the statement to cmd_dim, on every call. STATIC is not compiled.
To add: Ordered by yield for effort: allow a trailing comment (one test); read name AS type and LENGTH n as cmd_dim reads them; then LOCAL x = value (compile the value and store it after RBLocal makes the variable) and LOCAL arrays. STATIC only has to find its existing variable after the first call: a compiled check that it exists, with cmd_dim only the first time.
Evidence: Every in-loop statement in this group needs nothing else. solar_eclipse ran 9,564 LOCALs as text (Local i As integer, Local rsun(3), rmoon(3), Local factor = 0.25 ' comment); Elite ran its STATIC 632 times, once a frame, 523 samples. walkr-robot's author measured 100,347 text LOCALs and 100,037 text STATICs in one run: its timing and gait SUBs, called about 50,000 times each, open with Static Float dt_ms(499), Static Integer p = 0, first = 1, n = 0 and Local Float t = Timer, _t. TSCP chess opens its search with Local alpha=a, beta=b, depth=d and Local i, j, x, c, f, from, too, exitF=0 at every node, and its evaluation with Local r=0.
LOCAL kb$ LENGTH 2 ' Elite (program + library), line 1654
LOCAL INTEGER i, k, b(5) ' Elite (program + library), line 2719
LOCAL nm$ LENGTH 10 ' Elite (program + library), line 2764
STATIC INTEGER addr = 0, fadd = 0 ' Elite (program + library), line 2169
Static count% ' StructParamTest.bas, line 490
Static float y_move(64), x_move(64) ' brownian_bg_demo.bas, line 168mostly done Committed 88f85a7, through the function splice: every function with no argument (TIMER, INKEY$, POS, DATE$, TIME$, CWD$); MM.HRES, MM.VRES, MM.ERRNO and the other integer ~(letter) forms; PEEK's INT8 (BYTE), WORD, SHORT, INTEGER, FLOAT and VAR forms; MATH(RAND); ASIN, ACOS, ATAN2, CINT, PIXEL(, KEYDOWN(, MAX and MIN. TIMER, INKEY$, KEYDOWN( and MATH(RAND) give a different answer each call, so SHADOW does not check their statements (as RND). Still text: VAL( (its result is an integer or a float depending on the string, which compiled code cannot follow), MM.INFO( (many keywords, results of several types), CHOICE( (it evaluates only the branch it picks: a compiled form needs jumps, not the splice), and PEEK's VARADDR, BP/WP/SP and other forms. Measured: 10,000 PIXEL( 156 → 60 ms and 10,000 t = TIMER 59 → 23 ms, OFF → ON. Test: Bas/bench/rbfuncs.py. Left as text across the corpus (projected, in loops), 4 October: 574 statements that use one of these functions, about 270 for the function's own forms: VAL( 189 (72 programs); CHOICE( 123 (17, among them Prince of Pico); MM.INFO( keywords 74 (90 programs: MM.INFO(WRITEBUFF), MM.INFO(FLASH ADDRESS n), MM.Info(HEAP)); PEEK(BP 24, PEEK(VARADDR 11, PEEK(VAR element(i), n) 8; MM.VER, MM.ERRMSG$, MM.DEVICE$ and the other MM.xxx that are not integers about 5; other MATH( forms 2. The other 300 or so are held by another item in the same statement: dotted names with TYPEs 50 (walkr), structures 48, string array elements 42, whole arrays 35, string arguments 25, other built-ins about 40. Since, in 716345a: VAL( as the whole of LET's right-hand side (its answer converted to the target's type in the splice, as evaluate converts it for cmd_let; anywhere else its type would matter, so it stays text); CHOICE( (the condition made an int by fun_ternary's C conversion, then only the branch it picks; both branches of one type); MM.INFO( for 30 keywords whose type the compiler knows (table RBInfoKw, taken from fun_info; the text spliced as getvalue gives it). Of the forms above that leaves VAL( elsewhere, CHOICE( with branches of two types, MM.INFO('s other keywords, PEEK(BP, VARADDR and VAR with an element, and the MM.xxx strings. Statements run as text in rbbig4.py's programs, b4 -> 716345a: VAL 1,237 -> 421, CHOICE 1,246 -> 16, BLIT 111 -> 9.
Today: RBFunction compiles only SIN to SGN, PI and RND. RBFnSplice compiles the string functions, RGB and MAP, and only bracketed ones whose arguments are all values. A function without brackets (TIMER, INKEY$), or one with a keyword argument (PEEK(BYTE a), MM.INFO(HRES), MATH(RAND)), sends the whole statement to text.
To add: TIMER becomes an RF_TIMER like RF_RND. INKEY$ goes through the function splice with no arguments. ASIN, ACOS, ATAN2, MAX, MIN and VAL go through the splice as it stands, MAX and MIN with any number of values. Keyword forms (PEEK, MM.INFO, KEYDOWN, MATH) go through the splice with the keyword copied into the spliced text and values for the expressions. CHOICE needs care: it evaluates only the branch it picks.
Evidence: TIMER stays text in 147 programs (frame timing: tFrame = TIMER), MM.INFO/MM.xxx in 177, INKEY$ in 101. Measured: PIXEL( is julia_interp's only fallback (10,428 samples, its checksum), PEEK(BYTE ...) takes 2,135 samples in the Exile kernel, MATH(RAND) 1,011 in Elite's bench copy and ASIN 951 in solar_eclipse.
tStage = TIMER ' Elite (program + library), line 1265
prof(5) = prof(5) + TIMER - tStage ' Elite (program + library), line 1278
t = TIMER ' Elite (program + library), line 1846
kb$ = INKEY$ ' Elite (program + library), line 1659
k = INKEY$ <> "" ' Elite (program + library), line 4541
DO WHILE INKEY$ <> "" ' Elite (program + library), line 4554Today: Not compiled. Its arguments are mostly whole arrays (MATH Q_MULT qB(), qA(), qC(), MATH SLICE sQ(), , n, qA()), which the splice cannot carry.
To add: Give the splice an array kind: T_VALUE plus a letter that carries a bound array, which findvar/getargaddress accept in place of the name. MATH and every other array-taking command (BLIT MEMORY, ARRAY SET, MEMORY COPY, the vector forms of PIXEL/LINE) can then use the splice.
Evidence: It is Elite's largest fallback: 11,713 text MATHs in 600 frames and 4,196 samples, on 5-element quaternions where reading the arguments costs more than the arithmetic. It appears in 49 programs.
MATH INSERT sQ(), , n, q() ' Elite (program + library), line 1438
MATH Q_EULER RAD(hdg), 0, 0, qA() ' Elite (program + library), line 1459
MATH SLICE sQ(), , s, qA() ' Elite (program + library), line 1482Today: GOTO, GOSUB and ON are not compiled. An IF whose THEN or ELSE is a GOTO or a line number is left to cmd_if.
To add: RC_GOTO already exists (ELSE uses it). GOTO label becomes a key resolved at compile time, and IF cond GOTO label / THEN label is the compiled condition plus RC_GOTO. GOSUB pushes the return as cmd_gosub does. RETURN already compiles (RC_RETURN), yet after the benchmark's text GOSUBs its RETURN still ran as text, 30,000 times, so the pair needs looking at together.
Evidence: The Micromite benchmark (B:/benchmark.BAS) ran only 76.7% of its statements compiled: every one of its timing loops is If j < 10000 GoTo BMn (9,175 samples), and 5-7 use GOSUB 4000 (1,159). Older programs in the corpus use the same style.
GoTo dobump ' Prince of Persia (Prince of Pi, line 891
GoTo airbump ' Prince of Persia (Prince of Pi, line 912
GoTo ResetBall ' Breakout, line 310
GoSub gsb ' bench_micro.bas, line 226
GoSub NearLbl ' bench_micro2.bas, line 485
GoSub FarLbl ' bench_micro2.bas, line 491Today: RBCompileExit compiles CONTINUE FOR (RC_CONTFOR) but not CONTINUE DO. In the run, each text CONTINUE DO was followed by a LOOP counted as text too.
To add: An RC_CONTDO that finds the loop's DO entry as cmd_continue does and goes to its LOOP record, the counterpart of RC_CONTFOR.
Evidence: The Exile kernel's If yy < 8 Then Continue Do ran 7,053 times, with 7,053 text LOOPs after it (1,540 samples). Only a few programs use it.
Continue Do ' Exile physics kernel (interpre, line 461
Continue Do ' restart the track ' Road.bas, line 46
continue do ' daytest.BAS, line 15Today: The SELECT record holds every CASE test, and one record is at most RB_MAXCODE = 160 code words. Across the corpus, compiled SELECTs reach 10 CASE values and every SELECT with 11 or more stays text. A string selector is refused by RBEvaluate.
To add: Chain the tests: each CASE record compiles its own values and jumps to the next CASE on no match, while the SELECT record only evaluates the selector into a cell the CASE records read. Size then no longer depends on the number of CASEs. Alternatively, use a jump table when every value is an integer constant. String selectors use the RC_OPS comparisons IF already compiles.
Evidence: Key dispatch and state machines in 6 games: Elite's key handler (35 values), the Exile kernel's Select Case i (12, 1,056 text runs), Prince of Pico's pstep and op machines, and Chuckie Egg's string SELECT CASE cel$. Few statements, but each runs every frame, and as text it tests its CASEs one by one.
SELECT CASE b ' Elite (program + library), line 1359
SELECT CASE k ' Elite (program + library), line 1664
SELECT CASE n ' Elite (program + library), line 3198
SELECT CASE cel$ ' Chuckie Egg, line 540
Select Case c ' Prince of Persia (Prince of Pi, line 687
Select Case k ' Prince of Persia (Prince of Pi, line 3995Today: RBElement refuses T_STR arrays, and RBBindK refuses a string array bind.
To add: Add an RC_LDEL/RC_ADEL for string arrays (element = base + index × (LENGTH + 1)), and an RC_STS into an element with the array's LENGTH check, as cmd_let makes it.
Evidence: 67 programs, 5 games (Prince of Pico's musName(i) tables, Elite's dashboard labels LLAB$(0) = "FS").
IF part < ltCnt(i) THEN TokPart$ = ltPart$(ltFirst(i) + part) ELSE TokPart$ = "" ' Elite (program + library), line 3100
IF part = 0 THEN TokPart$ = tk$(tok) ELSE TokPart$ = "" ' Elite (program + library), line 3104
PutStr dg$(k) ' Elite (program + library), line 3193Today: Refused as above, and a SUB with a parameter AS a TYPE cannot be surveyed, so none of its locals compile.
To add: Bind the TYPE instance and compile a member as an offset into its data (findvar's ResolveStructMember at compile time, with the instance's type checked at bind), and read AS typename in RBUnitHeader.
Evidence: Mostly the structure tests, and walkr's inp and ST.srv(i).ID (113 of its in-loop text statements).
pa(i%).x = i% * 10 ' ArrayCopyStructTest.bas, line 37
pa(i%).y = i% * 100 ' ArrayCopyStructTest.bas, line 37
points(i).x = i * (MM.HRES / numPoints) ' LinePlotTest.bas, line 52Today: RBParams refuses an a() parameter, RBArgs an a() argument, and RBLetInto a whole-array target.
To add: Bind the array (RB_BARRAY) and let RBMakeParams make the parameter a T_PTR to it, as DefinedSubFun does. Array copy can call the helper cmd_let uses.
Evidence: 94 programs (the Exile kernel's harness, NewFacing = NewShip(t, x, y, z, qA()) in Elite).
NewFacing = NewShip(t, x, y, z, qA()) ' Elite (program + library), line 1460
qC() = qA() ' Elite (program + library), line 1541
qA() = qC() ' Elite (program + library), line 1798Today: Not compiled.
To add: READ binds its targets and calls a helper that steps the DATA pointer exactly as cmd_read does, storing through the binds. RESTORE label is a position resolved at compile time.
Evidence: Data-driven loaders and tables in 83 programs and 7 games. Measured: 761 text READs in Elite's 600 frames and 738 in the Exile kernel.
READ bName$(b), bNv(b), bNf(b), bNfv(b), bNf0(b), bNv0(b) ' Elite (program + library), line 1390
READ bCan(b), bArea(b), bBty(b), bVis(b), bEne(b), bSpd(b) ' Elite (program + library), line 1391
READ bLas(b), bMis(b), bGun(b), bExp(b) ' Elite (program + library), line 1392
RESTORE dat_sidewinder ' Elite (program + library), line 1360
RESTORE dat_viper ' Elite (program + library), line 1361
RESTORE dat_mamba ' Elite (program + library), line 1362Today: With an ELSE, a single-line IF compiles only if both parts are assignments, because cmd_if runs a THEN part that has an ELSE itself.
To add: Give the THEN part a record of its own when there is an ELSE (an RB_PART, as the ELSE part and the no-ELSE THEN part already have), so the IF compiles to a two-way jump whatever the parts are.
Evidence: 50 programs, 7 games: IF buying THEN BuyOne i ELSE SellOne i, If immob Then RotatingPlayer Else AngleAndFacing.
IF DEMOSCENE = 2 THEN DockInput frames ELSE DemoInput frames ' Elite (program + library), line 1252
IF eqOwned(EQ_DOCK) THEN dockComp = 1 - dockComp ELSE Sfx SFX_BOOP ' Elite (program + library), line 1260
IF dtSink THEN BriefBreak ELSE PutCh " " ' Elite (program + library), line 3207Today: FUNCTION calls compile only in LET, IF and INC (x.calls), because once a call has run the statement cannot fall back.
To add: Allow them wherever every other check comes before the first call: SUB arguments (the binds and the target SUB are checked first) and the splices. FOR and DO do their stack work first (RBFcall's note), so those need the call moved ahead of it.
Evidence: 38 programs, 3 games (PutName NameCap$(), DataLine y, "Government", GovName$(sysGov)). Mostly in combination with string arguments (item 4).
nm$ = UCASE$(AskText$("Find system: ", 10)) ' Elite (program + library), line 2848
DataLine y, "Distance", STR$(SysDist(homeX, homeY, sysX, sysY * 2) / 10) + " Light Years" ' Elite (program + library), line 2939
DataLine y, "Economy", EcoName$(sysEco) ' Elite (program + library), line 2940Today: Not compiled.
To add: Through the value splice with string values (item 3), keeping the ; and , separators in the spliced text.
Evidence: It is the most frequent fallback by statement count (499 programs). Most in-loop PRINTs, though, are console or file output where the output costs more than the interpreting: in solar_eclipse its 4,899 samples are the UART. It ranks low for speed.
PRINT " "; n; " typ"; sTyp(n); " bp"; sBp(n); " obj"; sObj(n); ' Elite (program + library), line 2618
PRINT " x"; STR$(sX(n), 0, 0); " y"; STR$(sY(n), 0, 0); " z"; STR$(sZ(n), 0, 0); ' Elite (program + library), line 2619
PRINT " area"; bArea(sBp(n)); " ene"; sEne(n); " ai"; sAI(n); ' Elite (program + library), line 2621The static census counts statements. These runs count executions. Each workload that ends by itself was run to END under OPTION COMPILE ON with OPTION PROFILING ON, SAMPLE, 16384:
Bas/chess/chessiter.bas, from 74ed383): its bench, the depth-5 search of a fixed position, with the bench's time limit set to 3 minutes in the copy run (the full bench took 48 minutes at 378 MHz). That covers depths 1 to 3 and part of 4. Then bye, and an END added after the program's last line, since the profile is printed by END and the program otherwise runs off its end.A:/gb/gbpopp.bas loaded with ,C: 274 checks over 2,690 frames, nothing drawn. Its file reading (OPEN, LINE INPUT and VAL on the scenario file) is the harness's, not the game's.Each workload ran in the morning on V7.0.00b4 as released, and again after the day's commits, last on V7.0.00b5 as released (4 October). On that run the Micromite benchmark was sent from the corpus's copy of B:/benchmark.BAS, the SD card being out of the board; its counts are the same as before. The statements run are the same both times; what changed is how many of them ran as text.
The board stayed at 315 MHz throughout. These runs measure counts, not speed, so the clock does not matter here.
The repository's knivd.bas and julia.bas never reach END (an endless DO and a wait for a key), so their fixed-work twins stand in for them.
| Workload | Statements run | Compiled, morning | Compiled now | Text, morning | Text now | Text statements by command now (top) |
|---|---|---|---|---|---|---|
| solar_eclipse | 714,382 | 96.3% | 99.1% | 26,256 | 6,115 | Let 2,888, Read 221, Print 88, Dim 7, Const 7, Input 5 |
| knivd_fixed | 2,207,971 | 99.999% | 99.999% | 21 | 19 | Print 6, Dim 5, Const 2, End 1, Let 1, Option 1 |
| julia_interp | 3,215,211 | 98.7% | 99.999% | 43,243 | 41 | Let 18, Print 7, For 4, Const 4, Dim 1, End 1 |
| gfx_frames | 1,896,391 | 97.4% | 99.1% | 49,342 | 17,197 | Text 13,000, FRAMEBUFFER 1,004, Pixel 1,000, CLS 1,000, Array Set 1,000, Read 128 |
| benchmark | 430,053 | 76.7% | 76.7% | 100,051 | 100,044 | If 70,000, GoSub 30,000, Return 30,000, Print 25, Timer 8, Let 4 |
| jbench | 606 | 90.4% | 92.2% | 58 | 47 | Print 17, Timer 11, Dim 3, Option 2, End 1, Const 1 |
| elite | 967,001 | 95.6% | 97.7% | 42,533 | 22,132 | Math 11,713, Let 1,010, Draw3D 796, Array Set 785, Read 761, Pixel 710 |
| exile_phys | 2,555,847 | 97.8% | 99.4% | 55,619 | 15,931 | Continue 7,053, Loop 7,053, If 2,537, Select Case 1,056, Read 738, Restore 18 |
| pop | 2,924,118 | - | 96.7% | - | 97,099 | If 28,799, Let 24,849, Select Case 10,292, Line Input 1,816, Loop 1,816, Print 1,094 |
| chess | 60,438,074 | - | 99.954% | - | 28,041 | Let 26,664, Read 1,064, Sprite 104, Dim 44, Const 43, Sub 21 |
Statements run = records the stream executed (MM.INFO(COMPILE) RAN); compiled = CODE. The text column of OPTION PROFILING counts only fallbacks by command; a fallback SUB call is not a command and shows only in the total.
From each run's 40 busiest lines ([PCSLINE]) on V7.0.00b5, the ones holding a fallback statement. Samples are 97 µs apart.
| Samples | Line | Statement | Item |
|---|---|---|---|
| 1,375 | 547 | If yy < 8 Then Continue Do | CONTINUE DO |
| 796 | 877 | If immob Then RotatingPlayer Else AngleAndFacing | Single-line IF ... THEN x ELSE y with a call/command part |
| Samples | Line | Statement | Item |
|---|---|---|---|
| 1,252 | 732 | Draw3D WRITE sObj(n), tx, ty, tz, 0, solidMode | Command DRAW3D |
| 1,102 | 701 | CLS | Command CLS |
| 845 | 123 | FRAMEBUFFER COPY F, N | Command FRAMEBUFFER |
| 738 | 797 | MATH Q_VECTOR 0, 0, 1, qB() : MATH Q_ROTATE qA(), qB(), qV() | Command MATH |
| 623 | 638 | MATH SLICE sQ(), , n, qA() | Command MATH |
| 619 | 1031 | MEMORY COPY INTEGER addr, fadd, DASHWORDS | Command MEMORY |
| 557 | 639 | MATH Q_MULT qP(), qA(), qC() | Command MATH |
| 550 | 611 | MATH Q_CREATE -aT, 0, 0, 1, qA() | Command MATH |
| Samples | Line | Statement | Item |
|---|---|---|---|
| 3,786 | 367 | Text 17, DLY(i) - 1, LLAB$(i), "RT", 7, 1, cWhite | Command TEXT |
| 3,438 | 371 | Text 303, DRY(i) - 1, RLAB$(i), "LT", 7, 1, cWhite | Command TEXT |
| 1,768 | 90 | Cls | Command CLS |
| 1,445 | 105 | If FBUF% Then Framebuffer Copy F, N | Command FRAMEBUFFER |
| 824 | 279 | Pixel spx(), spy(), spc() | BOX/LINE/PIXEL vector forms (array arguments), LINE PLOT/GRAPH/AA |
| Samples | Line | Statement | Item |
|---|
| Samples | Line | Statement | Item |
|---|---|---|---|
| 963 | 1936 | Input obslong.deg$, obslong.min, obslong.sec | Command INPUT |
| 963 | 1884 | Input month, day, year | Command INPUT |
| 826 | 1911 | Input obslat.deg$, obslat.min, obslat.sec | Command INPUT |
| 825 | 223 | Input ndays | Command INPUT |
| 825 | 1959 | Input obsalt | Command INPUT |
| 426 | 750 | Print " " | Command PRINT |
| 414 | 724 | Print "topocentric coordinates" | Command PRINT |
| 413 | 712 | Print "UTC julian day ", Str$(jdutc, 0, 8) | Command PRINT |
| Samples | Line | Statement | Item |
|---|---|---|---|
| 10,309 | 2 | Pause 1000 | Command PAUSE |
| 1,361 | 96 | If j < 10000 GoTo BM8 | GOTO, IF ... GOTO, THEN linenumber |
| 1,351 | 67 | If j < 10000 GoTo BM6 | GOTO, IF ... GOTO, THEN linenumber |
| 1,338 | 40 | If j < 10000 GoTo BM4 | GOTO, IF ... GOTO, THEN linenumber |
| 1,336 | 29 | If j < 10000 GoTo BM3 | GOTO, IF ... GOTO, THEN linenumber |
| 1,308 | 83 | If j < 10000 GoTo BM7 | GOTO, IF ... GOTO, THEN linenumber |
| 1,304 | 18 | If j < 10000 GoTo BM2 | GOTO, IF ... GOTO, THEN linenumber |
| 1,250 | 52 | If j < 10000 GoTo BM5 | GOTO, IF ... GOTO, THEN linenumber |
| Samples | Line | Statement | Item |
|---|---|---|---|
| 65,879 | 1086 | Input "",s$ | Command INPUT |
| Samples | Line | Statement | Item |
|---|---|---|---|
| 3,006 | 691 | Select Case c | String SELECT CASE |
| 2,764 | 4219 | ApplyCode Mid$(codes, i, 1) | SUB/FUNCTION string arguments and parameters |
| 2,561 | 691 | Select Case c | String SELECT CASE |
| 2,231 | 4158 | Line Input #3, ln | Command LINE INPUT |
| 1,916 | 3974 | Open home + "levels.dat" For Input As #1 | Command OPEN |
| 1,805 | 1141 | LoadChar kRec() : SetOp gRec() | Whole arrays passed to SUBs/FUNCTIONs, array copy |
| 1,728 | 2361 | Select Case op | SELECT CASE with more than ~10 values |
| 1,563 | 4160 | w = UCase$(ScWord$(ln, 1)) | FUNCTION calls inside SUB/FOR/DO/SELECT/BOX... arguments; SUB/FUNCTION string arguments and parameters; String FUNCTION results |
| Program | Statements | Compiled | In loops | Compiled in loops | Now |
|---|---|---|---|---|---|
| Elite (program + library) | 4,878 | 85.7% | 4,260 | 88.0% | 91.5% |
| Chuckie Egg | 1,046 | 87.9% | 987 | 90.0% | 93.5% |
| Exile (program + CSUB library) | 505 | 73.3% | 398 | 81.9% | 90.7% |
| Exile physics kernel (interpreted) | 1,015 | 88.8% | 957 | 93.2% | 97.4% |
| Prince of Persia (Prince of Pico engine) | 4,793 | 90.4% | 4,440 | 93.5% | 95.4% |
| Thrust | 1,000 | 83.8% | 943 | 85.9% | 91.3% |
| Picanoid | 1,536 | 83.0% | 1,436 | 86.1% | 91.5% |
| Arkanoid | 1,508 | 83.0% | 1,420 | 85.8% | 91.4% |
| Breakout | 261 | 71.3% | 166 | 80.7% | 84.3% |
| TSCP chess (Bas/chess/chessiter.bas) | 1,207 | - | 1,154 | - | 92.0% |
| Item | In loops | Alone | Games |
|---|---|---|---|
| Command PRINT | 216 | 216 | 10 |
| SUB/FUNCTION string arguments and parameters | 199 | 143 | 8 |
| Command READ | 83 | 83 | 8 |
| Command FRAMEBUFFER | 71 | 71 | 9 |
| Command MATH | 71 | 71 | 1 |
| String FUNCTION results | 68 | 15 | 6 |
| Command RESTORE | 47 | 47 | 8 |
| Whole arrays passed to SUBs/FUNCTIONs, array copy | 46 | 43 | 3 |
| FUNCTION calls inside SUB/FOR/DO/SELECT/BOX... arguments | 46 | 15 | 4 |
| Command CLS | 34 | 34 | 9 |
| Single-line IF ... THEN x ELSE y with a call/command part | 31 | 30 | 7 |
| Command PLAY | 29 | 29 | 7 |
| String array elements | 23 | 11 | 5 |
| Command SAVE | 20 | 20 | 7 |
| Command TILEMAP | 20 | 20 | 4 |
| Command SPRITE | 19 | 19 | 3 |
| Array elements passed to SUBs/FUNCTIONs (BYREF element) | 18 | 0 | 3 |
| Command INPUT | 17 | 17 | 4 |
| Command MAP( | 16 | 16 | 2 |
| Command TEXT | 15 | 15 | 4 |
| Command PAUSE | 15 | 15 | 6 |
| SELECT CASE with more than ~10 values | 11 | 11 | 6 |
| Command ON ERROR | 11 | 11 | 9 |
| Command DRAW3D | 8 | 8 | 1 |
| Command CLOSE | 8 | 8 | 6 |
| Built-in function DEVICE( | 8 | 8 | 2 |
| Command OPEN | 7 | 7 | 6 |
| GOTO, IF ... GOTO, THEN linenumber | 7 | 7 | 2 |
| Built-in function TILEMAP( | 6 | 6 | 1 |
| String SELECT CASE | 5 | 5 | 2 |
The code is github.com/teuler/walkr-robot, code-mmbasic, a hexapod's controller:
hpx_srv.bas, the server, which loads lib_input.bas and lib_st3215.bas with Library Load;configure_servo.bas;wifi_udp_test.bas.The servos are ST3215s on COM1 at 500 kbaud. Each program was censused as the others were, with its libraries merged in. The tester reports less than 5% from compiling.
| Program | In loops | Compiled in loops | Corpus, for comparison |
|---|---|---|---|
hpx_srv.bas + both libraries | 1,177 | 33.1% | 78.8% across the 650 runnable programs, 89% for the games |
configure_servo.bas | 57 | 47.4% |
Two thirds of the server's statements inside loops run as text, so a gain of a few percent is what the compiler can give it today. The reasons, for its 788 in-loop text statements:
| Needs | Statements | Needing only this |
|---|---|---|
Dotted names that are not structure members (ggn.state, srv.pCurr(), R.running): item 2 | 306 | 216 |
| PRINT (its debug and terminal output) | 114 | 114 |
Structure members (inp.cmd, ST.srv(i).ID): item 14 | 113 | 62 |
LOCAL forms (Local Float t = Timer, Local Float xyz1(2)): item 7 | 48 | 48 |
| MATH on arrays: item 9 | 44 | 44 |
| MAX, MIN, CHOICE, ATAN2, ACOS, TIMER, PEEK: item 8 | 125 | 42 |
| Whole arrays as arguments, strings through calls: items 15 and 6 | 59 | 11 |
The computing it times is almost all text:
R._computeMove: 18 of 54 statements compile.R._executeMove: 9 of 33.R._moveAllServos: 7 of 38.R.spinGaitGen: 7 of 17.The arithmetic itself compiles. Its statements stay text because they read and write the gait generator's state through dotted names, for example xyz1(0) = -ggn.xyzLeg(iL,0) + in.xyzBody_v(0) + g.xyz(iL,0) and ggn.state = GGN_MOVING. Only R._bodyIK (13 of 18) and R._legIK (14 of 22) are mostly compiled.
| With | Compiled in loops |
|---|---|
| Today | 33.1% |
| Dotted names alone | 51.4% |
| The next set without dotted names or structures | 58.4% |
| The next set with dotted names | 83.1% |
| And structure members | 92.6% |
update The dotted-name fix committed today (418892f) compiles dotted names only in programs that define no TYPE. hpx_srv.bas and configure_servo.bas both define TYPEs, so on 3071436 the server's census was unchanged at 33.1% of its in-loop statements, on 88f85a7, with items 5 and 8, it was 36.3%, on 74ed383, with item 7, 40.1%, and on V7.0.00b5 40.6% (VAL, CHOICE, MM.INFO and BLIT). The 51.4% row above needs the other half of item 2, which tells a structure member from a namespace name when TYPEs exist. Across the whole corpus, 221 in-loop statements need only that half, nearly all of them walkr's.
measured The author has since renamed every dotted name that is not a structure member (the TYPEs keep theirs), and measured on V7.0.00b4:
| Timing | COMPILE OFF | COMPILE ON |
|---|---|---|
| Loop | 0.330 ms | 0.299 ms |
| Move (computing it) | 8.538 ms | 5.227 ms |
| Exec (servo packets on COM1) | 9.326 ms | 8.593 ms |
Still, 959,055 of the 2,265,470 statements it ran (42%) were text. By command:
/* 149,461: three block comments in the main loop, fixed since (section 7);R__timing and R_spinGaitGen, called about 50,000 times each, open with Static Float dt_ms(499), Static Integer p = 0, first = 1, n = 0 and Local Float t = Timer, _t. Item 7 compiles all three on 74ed383, the STATICs on every call after the first: 10,000 calls of a SUB opening that way take 611 ms against 947 ms as text (Bas/bench/rblocal.py).Three things would hold the measured gain down even with full coverage. walkr was not run here, because it needs its servos, so these come from its source:
R.spinGaitGen waits for ggn.tWaitUntil, so faster code makes the loop spin more often, not finish sooner. Only its Move and Exec timings (R._computeMove, R._executeMove) measure interpreting.Pause in R.waitForServos.Option Profiling On, which counts every statement in both modes.| Item | In loops | Alone | Inner loops | All | Programs | Games |
|---|---|---|---|---|---|---|
| SUB calls written with brackets: Foo(a, b) | 2004 | 1351 | 545 | 2681 | 94 | 2 |
| Command PRINT | 1506 | 1506 | 166 | 7586 | 502 | 9 |
| SUB/FUNCTION string arguments and parameters | 1134 | 459 | 165 | 2590 | 120 | 7 |
| Command TURTLE | 924 | 924 | 305 | 1172 | 16 | 0 |
| Command TEXT | 855 | 855 | 254 | 1075 | 82 | 8 |
| LOCAL forms: LENGTH, arrays, AS after the name, with a value, trailing comment | 741 | 741 | 0 | 1060 | 151 | 8 |
| Array elements passed to SUBs/FUNCTIONs (BYREF element) | 651 | 368 | 233 | 714 | 71 | 8 |
| Built-in function TIMER | 511 | 475 | 127 | 993 | 150 | 8 |
| String array elements | 503 | 272 | 210 | 1047 | 67 | 5 |
| Command PLAY | 410 | 410 | 89 | 640 | 74 | 7 |
| Dotted names that are not structure members (R.running, srv.pCurr()) | 400 | 259 | 63 | 706 | 32 | 0 |
| Whole arrays passed to SUBs/FUNCTIONs, array copy | 383 | 142 | 75 | 710 | 95 | 4 |
| Command FRAMEBUFFER | 379 | 379 | 103 | 750 | 89 | 8 |
| Command CLS | 355 | 355 | 44 | 606 | 192 | 8 |
| Command PAUSE | 349 | 349 | 123 | 595 | 167 | 6 |
| Built-in function MM.INFO / MM.xxx | 345 | 301 | 125 | 717 | 179 | 9 |
| Command SPRITE | 326 | 326 | 169 | 538 | 41 | 2 |
| Command MATH | 321 | 321 | 59 | 565 | 50 | 1 |
| Structures (TYPE members, structure parameters) | 315 | 257 | 8 | 2706 | 25 | 0 |
| Command READ | 313 | 313 | 99 | 424 | 85 | 7 |
| String FUNCTION results | 248 | 98 | 81 | 267 | 37 | 5 |
| Built-in function INKEY$ | 212 | 202 | 78 | 231 | 102 | 6 |
| Command BLIT | 211 | 211 | 96 | 270 | 33 | 4 |
| Built-in function VAL( | 189 | 108 | 42 | 273 | 72 | 2 |
| Command TIMER | 182 | 182 | 19 | 320 | 124 | 0 |
| Command MID$( | 179 | 179 | 86 | 185 | 13 | 0 |
| Built-in function PEEK( | 161 | 117 | 79 | 229 | 51 | 5 |
| Command RESTORE | 150 | 150 | 0 | 383 | 48 | 7 |
| Built-in function MAX( | 139 | 79 | 52 | 151 | 21 | 1 |
| FUNCTION calls inside SUB/FOR/DO/SELECT/BOX... arguments | 137 | 42 | 50 | 163 | 40 | 3 |
| Command INPUT | 130 | 130 | 15 | 180 | 48 | 3 |
| Built-in function SPRITE( | 125 | 114 | 74 | 132 | 20 | 1 |
| Built-in function CHOICE( | 123 | 87 | 33 | 126 | 17 | 1 |
| Command CIRCLE | 112 | 112 | 30 | 154 | 51 | 2 |
| SUBs with more than 64 locals (RB_MAXULOCAL) | 107 | 107 | 0 | 107 | 1 | 0 |
| STATIC in a SUB | 105 | 105 | 0 | 124 | 38 | 1 |
| Built-in function INPUT$( | 91 | 76 | 9 | 137 | 26 | 0 |
| Built-in function MIN( | 91 | 42 | 31 | 105 | 22 | 0 |
| Single-line IF ... THEN x ELSE y with a call/command part | 81 | 76 | 18 | 158 | 50 | 7 |
| Command BLIT MEMORY | 77 | 77 | 64 | 77 | 9 | 0 |
| Command DRAW3D | 75 | 75 | 37 | 110 | 13 | 1 |
| Built-in function KEYDOWN( | 74 | 72 | 44 | 79 | 21 | 7 |
| Command TILEMAP | 68 | 68 | 13 | 178 | 18 | 4 |
| Command ON ERROR | 62 | 62 | 13 | 411 | 78 | 8 |
| GOTO, IF ... GOTO, THEN linenumber | 62 | 62 | 19 | 207 | 37 | 2 |
| String SELECT CASE | 61 | 43 | 11 | 61 | 32 | 2 |
| Built-in function FRAME( | 61 | 61 | 0 | 132 | 2 | 0 |
| Unexplained (IF) | 57 | 57 | 33 | 64 | 16 | 0 |
| String literals over 64 chars / with backslash | 54 | 31 | 14 | 180 | 18 | 2 |
| Optional (missing) arguments | 54 | 37 | 34 | 85 | 22 | 0 |
| Built-in function LINSTR( | 53 | 0 | 21 | 57 | 9 | 0 |
| Command POKE | 53 | 53 | 28 | 74 | 19 | 1 |
| Command MEMORY | 51 | 51 | 17 | 109 | 44 | 4 |
| Command CTRLVAL( | 48 | 48 | 0 | 95 | 8 | 0 |
| Command LINE INPUT | 47 | 47 | 4 | 75 | 32 | 3 |
| ELSE followed by two or more spaces (compiler bug) | 47 | 47 | 43 | 51 | 12 | 0 |
| Unexplained (LET) | 47 | 47 | 9 | 95 | 29 | 0 |
| Command I2C | 46 | 46 | 3 | 77 | 9 | 0 |
| Command SAVE | 45 | 45 | 15 | 74 | 37 | 7 |
| Command WEB | 45 | 45 | 8 | 127 | 15 | 0 |
| Command LONGSTRING | 45 | 45 | 11 | 116 | 22 | 0 |
| ^ (power) on integers | 45 | 33 | 28 | 47 | 13 | 0 |
| Command END | 44 | 44 | 14 | 445 | 297 | 9 |
| Built-in function TILEMAP( | 41 | 41 | 16 | 77 | 7 | 1 |
| Built-in function TOUCH( | 40 | 36 | 20 | 40 | 6 | 0 |
| Command PIN( | 39 | 39 | 2 | 67 | 28 | 0 |
| SELECT CASE with more than ~10 values | 38 | 38 | 13 | 60 | 28 | 6 |
| Built-in function EOF( | 38 | 38 | 3 | 63 | 36 | 2 |
| Command CLOSE | 36 | 36 | 0 | 234 | 74 | 5 |
| Command MODE | 32 | 32 | 8 | 214 | 162 | 7 |
| Command TRIANGLE | 32 | 32 | 19 | 55 | 14 | 0 |
| Built-in function PIN( | 31 | 21 | 11 | 60 | 16 | 0 |
| Command OPEN | 30 | 30 | 0 | 245 | 86 | 5 |
| Built-in function EPOCH( | 30 | 20 | 1 | 31 | 3 | 0 |
| Command LOAD | 28 | 28 | 7 | 73 | 40 | 3 |
| LOOP of the 33rd and later DO (RB_MAXDO 32) | 27 | 27 | 0 | 27 | 1 | 0 |
| Command FONT | 27 | 27 | 11 | 89 | 39 | 0 |
| Built-in function FIELD$( | 26 | 16 | 1 | 26 | 12 | 1 |
| Built-in function CINT( | 26 | 22 | 5 | 31 | 14 | 1 |
| Command ARC | 26 | 26 | 8 | 29 | 7 | 0 |
| Built-in function LLEN( | 26 | 0 | 13 | 31 | 11 | 0 |
| Command SETPIN | 25 | 25 | 0 | 238 | 59 | 1 |
| Built-in function ASIN( | 24 | 24 | 0 | 26 | 8 | 0 |
| Other: LINE: OPTION LEGACY (x,y)-(x,y) syntax | 23 | 23 | 6 | 45 | 3 | 0 |
| Command POLYGON | 20 | 20 | 13 | 30 | 14 | 1 |
| Built-in function PIXEL( | 20 | 18 | 8 | 28 | 10 | 0 |
| BOX/LINE/PIXEL vector forms (array arguments), LINE PLOT/GRAPH/AA | 19 | 19 | 13 | 30 | 17 | 1 |
| Built-in function DEVICE( | 19 | 19 | 7 | 48 | 16 | 2 |
| Built-in function LOC( | 18 | 12 | 10 | 19 | 6 | 0 |
| Built-in function TIME$ | 18 | 4 | 4 | 25 | 12 | 0 |
| Command CAT | 18 | 18 | 10 | 20 | 11 | 0 |
| Command MAP( | 17 | 17 | 1 | 68 | 7 | 2 |
| Command RAY | 17 | 17 | 9 | 25 | 1 | 0 |
| Other: LET: other syntax | 17 | 17 | 2 | 41 | 27 | 0 |
| Command VAR | 15 | 15 | 4 | 22 | 6 | 0 |
| Command PIO | 14 | 14 | 0 | 199 | 11 | 0 |
| Command PULSE | 14 | 14 | 4 | 23 | 9 | 0 |
| Command ARRAY SET | 13 | 13 | 3 | 17 | 7 | 1 |
| CONTINUE DO | 13 | 13 | 6 | 13 | 9 | 3 |
| Command GUI | 13 | 13 | 0 | 208 | 12 | 0 |
| Command FRAME | 12 | 12 | 0 | 352 | 2 | 0 |
| Built-in function DIR$( | 11 | 11 | 0 | 29 | 17 | 3 |
| Command RBOX | 11 | 11 | 3 | 22 | 11 | 1 |
| CSUB calls | 11 | 11 | 8 | 23 | 20 | 1 |
| GOSUB / RETURN to a label | 11 | 11 | 1 | 129 | 12 | 0 |
| Built-in function JSON$( | 11 | 0 | 0 | 25 | 4 | 0 |
| Other: ELSE: ELSE: IF table entry not found | 11 | 11 | 8 | 13 | 8 | 0 |
| Built-in function DATE$ | 11 | 3 | 0 | 16 | 7 | 0 |
| Built-in function LGETSTR$( | 11 | 0 | 6 | 13 | 7 | 0 |
| Built-in function DATETIME$( | 11 | 4 | 0 | 12 | 2 | 0 |
| Built-in function ATAN2( | 10 | 10 | 2 | 10 | 4 | 1 |
| Command I2C2 | 10 | 10 | 0 | 34 | 6 | 0 |
| Built-in function CTRLVAL( | 9 | 9 | 0 | 19 | 9 | 0 |
| Command WATCHDOG | 9 | 9 | 1 | 14 | 4 | 0 |
| Built-in function ACOS( | 8 | 7 | 0 | 9 | 8 | 0 |
| Command SEEK | 8 | 8 | 0 | 19 | 11 | 0 |
| Command BEZIER | 8 | 8 | 5 | 8 | 5 | 0 |
| Command TILE | 8 | 8 | 4 | 15 | 2 | 0 |
| Built-in function CALL( | 8 | 8 | 0 | 20 | 9 | 0 |
| Command WS2812 | 8 | 8 | 0 | 13 | 2 | 0 |
| Command ERROR | 7 | 7 | 0 | 37 | 25 | 4 |
| Command CALL | 7 | 7 | 2 | 64 | 8 | 0 |
| Built-in function BYTE( | 7 | 2 | 7 | 7 | 7 | 0 |
| Built-in function CWD$ | 7 | 7 | 0 | 7 | 7 | 0 |
| Other: SUBCALL: unknown command or undefined SUB | 7 | 7 | 0 | 8 | 1 | 0 |
| Numeric literals over 15 digits | 6 | 6 | 0 | 6 | 6 | 0 |
| Built-in function EVAL( | 6 | 0 | 0 | 6 | 1 | 0 |
| Built-in function BOUND( | 5 | 0 | 0 | 11 | 7 | 0 |
| Built-in function MSGBOX( | 5 | 5 | 0 | 5 | 5 | 0 |
| Built-in function STR2BIN( | 5 | 5 | 5 | 23 | 4 | 0 |
| Command DATE$ | 5 | 5 | 1 | 6 | 2 | 0 |
| Command RUN | 4 | 4 | 0 | 13 | 11 | 0 |
| Command PWM | 4 | 4 | 0 | 13 | 5 | 0 |
| Command SYNC | 4 | 4 | 2 | 4 | 2 | 0 |
| Command CPU | 4 | 4 | 0 | 6 | 5 | 0 |
| Built-in function CLICK( | 4 | 4 | 0 | 13 | 3 | 0 |
| Command RTC | 4 | 4 | 2 | 12 | 3 | 0 |
| Command MAP | 3 | 3 | 1 | 37 | 15 | 4 |
| Unexplained (LOOP) | 3 | 3 | 1 | 3 | 3 | 1 |
| IF/ELSEIF chains too big for one record | 3 | 3 | 0 | 3 | 3 | 1 |
| Command ERASE | 3 | 3 | 0 | 17 | 12 | 0 |
| Command EXECUTE | 3 | 3 | 0 | 7 | 5 | 0 |
| Command FILL | 3 | 3 | 1 | 4 | 3 | 0 |
| Command SORT | 3 | 3 | 0 | 13 | 10 | 0 |
| Built-in function TEMPR( | 3 | 0 | 0 | 6 | 3 | 0 |
| Command TEMPR START | 3 | 3 | 0 | 12 | 4 | 0 |
| Command FLASH | 2 | 2 | 0 | 50 | 32 | 5 |
| Other: LET: FUNCTION without brackets | 2 | 1 | 0 | 2 | 2 | 1 |
| Built-in function PORT( | 2 | 2 | 0 | 2 | 1 | 1 |
| Unexplained (BOX) | 2 | 2 | 0 | 2 | 2 | 0 |
| Unexplained (ELSE IF) | 2 | 2 | 2 | 2 | 1 | 0 |
| Command SETTICK | 2 | 2 | 0 | 61 | 23 | 0 |
| Built-in function DAY$( | 2 | 0 | 0 | 2 | 2 | 0 |
| ON n GOTO/GOSUB | 2 | 2 | 0 | 3 | 3 | 0 |
| Built-in function GETSCANLINE | 2 | 1 | 2 | 2 | 2 | 0 |
| Command ON PS2 | 2 | 2 | 0 | 4 | 2 | 0 |
| Command PORT( | 2 | 2 | 0 | 5 | 3 | 0 |
| Command TIME$ | 2 | 2 | 1 | 3 | 1 | 0 |
| Built-in function MATH( | 2 | 0 | 0 | 205 | 4 | 0 |
| Command I2CLCD | 1 | 1 | 0 | 7 | 1 | 0 |
| Command INTERRUPT | 1 | 1 | 0 | 5 | 2 | 0 |
| Command FLAGS | 1 | 1 | 0 | 4 | 2 | 0 |
| Command STEPPER | 1 | 1 | 0 | 79 | 1 | 0 |
| Command ONEWIRE | 1 | 1 | 0 | 35 | 2 | 0 |
| Built-in function LGETBYTE( | 1 | 0 | 0 | 1 | 1 | 0 |
| Command ADC | 1 | 1 | 0 | 2 | 1 | 0 |
| Unexplained (PIXEL) | 1 | 1 | 1 | 4 | 4 | 0 |
| Command BIT( | 1 | 1 | 0 | 5 | 1 | 0 |
| Built-in function PIO( | 1 | 0 | 0 | 41 | 11 | 0 |
| Command LIST | 1 | 1 | 0 | 6 | 4 | 0 |
| Other: IF: FUNCTION without brackets | 1 | 1 | 0 | 1 | 1 | 0 |
| Unexplained (SUBCALL) | 1 | 1 | 1 | 2 | 2 | 0 |
| Built-in function LOF( | 1 | 1 | 0 | 3 | 3 | 0 |
| Command COPY | 1 | 1 | 0 | 1 | 1 | 0 |
| Unexplained (SELECT CASE) | 1 | 1 | 0 | 1 | 1 | 0 |
| Unexplained (FOR) | 1 | 1 | 0 | 11 | 6 | 0 |
Inner loops: nesting depth two or more, counting a hot SUB as one level. All: every fallback statement needing the item, in a loop or not.
| Limit | Effect | Seen in | Change |
|---|---|---|---|
RB_MAXCODE 160 words per record | A SELECT CASE of 11 or more values, and a long IF/ELSEIF chain, never compile: every arm's test lives in the one record. | SELECT: 42 programs, 6 games (item 10). ELSEIF chains: 11 IFs in 7 programs, among them Prince of Pico's StepCharacter, zmim and chess. | Chain the tests through the CASE/ELSEIF records (item 10). |
RB_MAXDO 32 | The DO table holds the first 32 DOs of a compile, so the LOOP of every later DO runs as text: once per iteration. | 27 in-loop LOOPs in one SD program. The games stay under 32. | Size the table from the survey's DO count, in the compiler's temporary memory. |
RB_MAXULOCAL 64 | A SUB or FUNCTION with more than 64 parameters, LOCALs, STATICs and CONSTs cannot be listed, so none of its locals compile. | B:/planets.BAS coordinates (93 locals, 107 in-loop statements). | The same: from the survey, in temporary memory. |
| A header the survey cannot read | A parameter AS a structure type makes RBUnitHeader give up, and every local in that unit stays text. | The structure tests. | Read the structure name as a type word. |
| Numeric literals | RBLiteral takes at most 15 digits, so 90.11969000000001 sends its statement to text. | solar_eclipse (350 samples), planets.BAS. | Read 16-17 digits; getvalue already does. |
The tokeniser skips one space after a command and keeps the rest; skipspace then steps over them, so cmdline - sizeof(CommandToken) is the command token only when there were none. Three places looked the IF table up there:
cmd_else, which then scanned for its ENDIF;cmd_if, which then scanned for the next arm;RBCompileExit), which then stayed text.In the corpus, all 65 ELSEs with two or more spaces before a comment were fallbacks, 51 of them in Petrobot, and all 95 with one space compiled.
fixed and committed on development, 3 October (6497832). CmdTokenBefore() in Commands.c walks back over the spaces (a token's bytes are never spaces), and RBCompileExit is given the token. Measured with Bas/bench/elsespace.py, 2,000 passes over a 40-line block:
| Case | One space | Three spaces, before | Three spaces, after |
|---|---|---|---|
| ELSE, compile OFF | 34 ms | 84 ms | 35 ms |
| IF, compile OFF | 33 ms | 78 ms | 34 ms |
| ELSE, compile ON | 8 ms | 51 ms | 6 ms |
The census of the six programs with such ELSEs now compiles every one of them, and nothing else changed.
A tester ran a compiled program 20,000 times a second as text and 37,000 compiled, and then 57,000 after MMEdit's crunch, which takes out comments, blank lines and indentation. The cause was the stream, not the interpreter:
RBRun) stopped at it and went back through RunStream, once per line per pass.Measured on a two-statement compiled loop, 100,000 passes: no comment 237 ms, one comment line +18%, three comment lines +30%.
fixed and committed on development, 3 October (3071436, RB_VERSION 35). Such a line now has no record, only a map entry pointing at the next statement's record:
| Two-statement loop, compiled, 100,000 passes | Before | After |
|---|---|---|
| No comment | 237 ms | 237 ms |
| One comment line | +18% | 236 ms |
| One empty line | +18% | 237 ms |
| Three comment lines | +30% | 236 ms |
solar_eclipse.bas with its five reference inputs, compiled, took 7.27 s as written and 6.93 s crunched (LOAD "...",C) before the fix, and takes 6.81 s as written after it. Crunching has little left to save: a compiled statement never reads its text, so indentation costs only the statements that still run as text.
fixed too, c1cddda: a /* ... */ block. tokenise keeps the lines between the /* and the */ as plain text, and cmd_comment steps over them, but the /* had a fallback record. walkr-robot's main loop has three blocks, so it ran 149,461 text /* statements in one measured run. Every statement from a /* to its */ now has a map entry and no record, as a comment line has, and the compiler's surveys step over the block. A block in a compiled loop now costs nothing: 245 ms against 246 ms for 100,000 passes (Bas/bench/rbblockcmt.py).
One visible change: TRACE in a compiled run no longer lists comment, empty or label-only lines. Tests: Bas/bench/rbnopchain.py (OFF, ON and SHADOW print the same, errors and MM.ERRLINE after a comment line included, and the timing) and Bas/bench/rbstamp.py.
The test for item 8 faulted with OPTION COMPILE OFF, so in the interpreter itself, for one program length in 256. The program's last line was a false single-line IF with no ELSE:
cmd_if steps to the next line with skipline, which stops one byte past the line's terminator. On the last line that byte is the end marker's second zero.ExecuteProgram stops only on two zeros. The writers padded with zeros to a block boundary (256 bytes in flash, 32 in RAM, a word for LIBRARY SAVE) before the FF FF FF FF that starts the binaries, so when the marker ended a block the next byte was FF.FF FF and called command 16383 through a garbage pointer.fixed and committed on development, 3 October (bc0c157), in the writers, not the runtime: SaveProgramToFlash, SaveProgramToRAM, LIBRARY LOAD's writer and LIBRARY SAVE now put a word of zeros after the marker. Every reader already skips any run of zeros before the FF, so the symbol table and the CSUB and font records are found where they are written:
Bas/savetest prints the same signature as before, by LOAD and by AUTOSAVE.An image saved before the fix keeps its old ending until it is saved again.
Found with TSCP chess (B:/basicchess/chessfaster.bas), whose search recurses with a FOR loop around each call. The fault was in the interpreter as well as in compiled code.
NEXT j, i closes both.quiesce left search's loop and the next NEXT failed with "Cannot find a matching FOR".fixed in 5b90865: cmd_next and the compiled NEXT close another loop only at their own level. Bas/bench/fornextrec.py fails on the build before and passes after. main has the same code.
With NEXT fixed, the chess program's depth-5 bench met two limits.
The C stack. On HDMIWEB it runs from 0x20082000 down to heapend: 8 KB of scratch RAM, plus 4.2 KB below it, about 12.4 KB in all.
The local variables. With enough stack, chessfaster's full depth-5 search reached ply 14 and 21 call levels, using 13.9 KB of stack. Then it stopped with "Not enough Local variable memory".
search takes 14 a level and quiesce 8.OPTION LOCAL VARIABLES n, at the top of the program, moves entries from the globals to the locals.How much stack each call costs, and how many locals a build allows, are left for a later decision.
since Peter's Bas/chess/chessiter.bas (eaeab32) runs the same search as a loop, with each ply's alpha, beta, depth and move index in arrays, so a node no longer costs a BASIC call: the deepest call path is 7 levels at any ply. It is the version measured in sections 3 and 4. The original's recursion inside FOR loops is the form the manual documents as unreliable.
fixed in db2353e (4 October). The scenario harness (A:/gb/gbpop.bas, loaded with ,C) stopped at line 117, Dim INTEGER vPanelB, ..., with Error : Not enough memory for 2048 bytes: put OPTION SYMBOLS OFF first, compiled or not, after a CPU RESTART, without its profiling twin, and with OPTION SYMBOLS OFF too (the hint was wrong). MEMORY showed 192 variables in 197 KB and 6 MB of PSRAM free.
The cause was not the compiler. Since 509622a (1 October) the globals' records are in the heap, in chunks of 32 records (2 KB on the RP2350), taken from the SRAM heap only, as each chunk filled. The heap puts an array in SRAM first, so the game's arrays had taken all of it, and its 193rd variable could not get a seventh chunk. The locals' chunks, taken the first time the local stack is that deep, could fail the same way in the middle of a run.
Test: Bas/bench/varpsram.py, which fails on the build before with the same error. The harness now passes its 274 checks compiled and is measured in section 3: 96.6% of what it runs is compiled.
The same work found the RP2040 PICO image 4 bytes over its flash limit since 74ed383 (item 7 split two helpers out of cmd_dim for Route B). d53c5df inlines them back on the RP2040, where Route B does not exist: every RP2040 variant fits, PICO with 156 bytes to spare.
A:/cc/p.bas with one line, END, in front, then LOADed and RUN. PrepareProgram compiles the whole image at RUN, and the program stops at its first statement before it can set a mode, open a file or wait for a key.
&H11723000 on this build: PSRAMstream) and wrote each record's key, STMT word, op word and command token to A:/cc/d.bin, which came back by TFTP. A record is compiled when its STMT word has RB_COMPILED.
RB_LINESTART flags; every line, blank or not, has at least one record. LOAD's '#file line and the END line were skipped.A:/cc/k.bin, and a map entry whose key differs from its record's is taken as such a line. The update's scripts are in cc2/.: as tokenise does. 1,978 THEN and ELSE parts of single-line IFs, which are records of their own (RB_PART), are counted as statements in their own right.RBValue, RBFunction, RBFnSplice, RBArgs, RBParams, RBCompileLocal, RBCompileSplice and the block compilers. The SELECT CASE and ELSEIF size limit was calibrated on the corpus: compiled SELECTs reach 10 values and none with 11 or more compiles.
LINE (x,y)-(x,y) form, and a few others.CMM1, welcome and *cmm2* directories and pc3a (a backup of A:) were skipped altogether, and identical files were counted once, the repository's copy first.RBExec. MM.INFO(COMPILE) gives the stream's RAN and CODE counts. [PCSLINE] gives the 40 busiest lines, joined to the census of the same source. INPUT and PAUSE lines collect waiting time, and PRINT lines console time.cc/:
ccboard.py drives the census;ccmap.py matches records to statements;ccreason.py gives the reasons;analyze.py works out loop context;items.py groups and ranks;dyn.py, dynparse.py and dynjoin.py handle the measured runs;report.py writes this page.stmts.json (212,533 rows). Nothing was changed on the board except OPTION COMPILE ON (left on), the files in A:/cc, A:/dy and A:/gb (copied from B:/pc3a/gb), and one CPU RESTART.