Skip to content

Script Studio: LSL optimizer size work, LL's compiler as a reference, and SLua converter QA - #404

Merged
RyeMutt merged 59 commits into
developfrom
rye/script-optim
Oct 5, 2026
Merged

RyeMutt merged 59 commits into
developfrom
rye/script-optim

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 4, 2026

Copy link
Copy Markdown
Member

Script Studio's LSL optimizer, its references, and the SLua converter, with the round of QA feedback on the converter and the compare view.

LSL optimizer

  • Smaller code on each target (LSO, Mono, LSL on Luau), each rewrite taken only where ALLSLCosts measures it smaller on that target, every answer held by allslcosts_test:

    • operators: && as &, - as + -, shifts as products, -1 - x as ~x, ...
    • branches and loops: a truth returned, an if's shared ways merged, a known-first loop as a do, a loop that never ends as a jump back
    • functions, scratch globals, dead stores, constants kept once
    • LSO's string literals joined
  • Folding: library calls where the two VMs cannot part (L17), merged bit tests (L18), ranges a comparison's sides settle, a local's constant where it is read, and Tailslide's vector dot and cross products as the VM gives them.

  • Mono's new locals are charged an estimate of the grid's microthread frame (ALLSLCosts::localFrame).

  • Corpus, against the baseline before this work:

    Target Bytes saved From To
    LSO 2,518 107,865 105,347
    Mono 4,052 283,362 279,310
    Luau 3,878 138,442 134,564
  • The passes are one file each under alscript/lsl/optimizer/, and alscript is in folders: core, lint, lsl, lsl/optimizer, luau, preprocessor.

References (tests only, never linked into the viewer)

  • LL's LSL compiler and LSO VM restored in indra/lscript, built with the tests.
  • allslreferencecheck holds Tailslide's LSO images and CIL to LL's, byte for byte, and the Mono weigher to both.
  • Tailslide 1.5.14 with patches of ours (registry baseline e588cfb, pushed):
    • bitstream-move-to-end and mono-constants-as-ll: what it compiles is LL's output.
    • literals-as-32-bit: an integer literal past 0xFFFFFFFF is -1, as the grid's 32-bit hosts read it.

SLua converter

  • Comments carried over where the LSL had them:
    • a trailing comment stays after its statement;
    • an if written on one line stays on one line;
    • lined-up declarations stay lined up;
    • star and slash rules become dashes;
    • the //* toggle becomes ---[[.
  • Fewer calls:
    • bit32.btest for an & asked only whether it is nought;
    • one table.append for several values added together;
    • NULL_KEY for a key's default;
    • numeric fors stepping by a constant's name;
    • only functions called before their definition are forward-declared.
  • Hexadecimal kept where SLua reads the same number, or where only bit32 reads it. One note says what SLua reads past 0x7FFFFFFF, and another why bit32.s32 is there.
  • A literal past 0xFFFFFFFF is noted where it is written as -1.
  • Bug fix: field-mode handlers are taken off with LLEvents:off. SLua stops a script that assigns nil to an LLEvents field, which leaving a state did.
  • The compare view lines LSL up beside SLua at anchors, and the converter writes the result into the prim the LSL is in.

Studio

  • Vim: registers are shared by every editor in every studio window for the session, so a yank in one file can be put in another without the system clipboard.
  • Compare view: a copy leaves out the empty rows that line the two sides up.
  • New LSL lint: SlIntegerPast32Bits, a warning on a whole number past 0xFFFFFFFF, which LSL reads as -1.

Testing

  • On macOS (ninja, RelWithDebInfo), all of these pass: alscript 36/36, lscript 2/2, llui 112/112, newview 71/71.
  • The viewer builds.
  • scripts/content_tools/check_script_strings.py agrees.

🤖 Generated with Claude Code

RyeMutt and others added 30 commits October 4, 2026 03:40
…no larger

A call to a library function the definitions call pure, given only what
nothing in a straight run of a block's statements changes, and made two
or more times there, is made once into a local declared before the run;
each place reads the local. What the run changes is the effects
analysis's to say, a user function's writes to globals included.

Not across a label anywhere in the run, which a jump could bring the code
in by past the local; and a run begins only at a place its statement
always comes to -- an expression statement, a declaration's value, a
return, the check of an if, a while or a for, a for's first part -- so the
call runs no more often than it did. The outermost call first, so that the
calls it is given go with it.

Made only where the target has it no larger: the call at N places against
the local, each compiled in a function of its own given what the call
reads, cached for the run. LSO and LSL on Luau gain most; Mono seldom,
where a call given one variable is smaller than a local. Over the corpus:
LSO -364, Luau -409, Mono -18 bytes, 17 scripts smaller, none larger.

The local is named for the function, its ll taken off, numbered where any
identifier in the script has the name. The rounds run again after, for
what the places leave open. Note OptimizerKeptCall; test 51.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tailslide's arithmetic has both wrong. Its dot product takes each part by
the other's opposite part, x by z, so the optimizer folded
<1,3,-5> * <4,-2,-1> to -27 where LSL gives 3: a script with a constant
dot product was sent changed. Its cross product is a product less a
product, which the compiler fuses into one multiply-add where the VM
rounds each product, so a vector crossed with itself did not come to
zero.

ALLSLArithmetic now does both itself: each part worked out as a single
does it, a step at a time, and again as a double rounded once, and folded
only where the two agree -- as llRound is. Found by the conformance
script among Tailslide's tests, whose folded checks the optimizer's
output can be read against. Test 52.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is set again

Tailslide gives a variable a value only where nothing sets it after its
declaration. FlowValues, a ConstantDeterminingVisitor of the optimizer's
own, follows a local's or a parameter's value as the code runs: set to a
constant, it is that constant at each read before it is set again, on
every way there.

What is known goes with the code. A statement's reads see it as the
statement begins, and its writes change it as it ends; a read that
something earlier in the same expression may write -- LSL runs a binary
operator's right side first -- sees nothing. Only a whole `x = constant`
gives a value; any other write forgets one. Both ways of an if are
followed and what they agree on goes on; a loop forgets what it writes
before it begins, and everything where it holds a label; a label forgets
everything, a jump coming in from anywhere; after a return, a jump or a
state change nothing is known until a label. A local declared with no
value is known only once it is set. Lists are left as Tailslide leaves
them, and on LSL on Luau vectors and rotations too, whose literal is
larger there than the arithmetic it stands for.

The folder writes such a read out only where every read of the variable
has a value, so that the variable goes: a literal in place of a read
that stays is larger. An expression that folds with it is its own fold,
and an if's condition is read for dead code as before. With it, a
parameter set and never read loses its writes, as a local does.

Tests 2, 4, 11 and 12 kept a variable by setting it again after reading
it; they set it, under a branch, before. Test 53. Over the corpus after
the last step: LSO -191, Mono -193, Luau -136 bytes, none larger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elow nought

What a number can be, the least and the most, where something says: a
constant; what a library function answers, by a table kept beside the
definitions (ALLSLTraits::bounds) -- a count nought or more, a find -1
or more, a truth the definitions say is 1 or 0, a list entry's type
within TYPE_INVALID to TYPE_ROTATION; llFrand's, from nought towards a
magnitude whose sign is known; a comparison's or a !'s 1 or 0; an & with a
number not below nought; a list's length written l != []; and a local set
to one of these where it is declared and never after.

A comparison of numbers whose sides' ranges decide it is that 1 or 0, the
dead branch going after: llGetListLength(l) < 0 is never true, a find's
> -2 always. Only where neither side changes anything, since neither runs
any more. llGetUnixTime is left out of the table, its answer wrapping
below nought in 2038. Shapes' "never below -1" reads the same table.

Integer comparisons of two constants, which Tailslide's arithmetic never
folded, settle with it. Over the corpus: LSO -42, Mono -36, Luau -120
bytes, in the conformance script. Note OptimizerSettled; test 54.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bit32 answers 0 to 4294967295, where LSL's integers were signed; the
converter said so in a note and left it. A script whose link messages
carry numbers with their top bit set -- an AO's, a set of scripts' own
protocol -- writes its constants as LSL's values, below nought, so that
`(num & MASK) == MSG` became bit32.band(num, MASK) == MSG, which nothing
ever made true, and every message was ignored; and the number it sent,
bit32.bor(MSG, FN), was past what an integer argument holds. QA found it
converting an AO, which ran and did nothing.

A bit32 call whose answer can pass 2147483647 is now made LSL's integer
by a helper the text defines once, int32: an &, | or ^ with an operand
that may be below nought, a ~, a <<, a >> of what may be below nought.
Not where bit32 takes the answer again, nor where only truth is asked,
nor an & with a number not below nought -- a constant, LSL's own, a
global the script never sets that is declared one, or an & or | of such.
The SluaBit32 note goes with what it warned of.

Test 36 holds a protocol of such numbers, written for the test; tests 2,
24 and 25 read the bits as now written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every comment was dropped: a script's author and licence, what each
constant is, the code a scripter kept commented out. QA found it with
the AO whose bits the last change put right.

Each comment is found in the LSL, past strings, and given to what it
stood over -- a global, a function, a state, a handler, a statement -- and
written over that as SLua writes it, whatever order the text comes out
in: the globals before the functions, state_entry last. One inside what
it stands over, such as a list's lines, goes over it; one after a bare
statement on its line is about that statement, and one on a block's
opening brace about what the block is the body of. One between an if's
branches goes over the elseif or into the else; one with nothing after
it in its block, at the block's end. The script's own first comments,
apart from its first global by a blank line, go at the very top, and a
blank line over a comment is kept.

// is --, and a line comment beginning [ gets a space, which --[[ would
otherwise make a block of. /* */ is --[[ ]], at a level of brackets
nothing in it closes. One that reads as the converter's own -- LSL: note
is spaced so that it is not taken for one. Any that nothing wrote is
written at the end rather than lost. Options::keepComments, on by default.

Test 37, on a script written for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… flags

The viewer builds every target with reassociation, contraction,
reciprocal division and no signed zeros allowed (00-Common.cmake). The
optimizer folds LSL's floats to what the VM gives, a step at a time, and
prints them to read back as themselves, so allslvalues.cpp and
allsloptimizer.cpp are now built with IEEE's arithmetic: -ffp-contract=off
-fno-associative-math -fno-reciprocal-math -fsigned-zeros, /fp:precise on
Windows, and without the precompiled header those flags went into.

It showed: llVecNorm(<0.75, 3, 1>) folded to 0.75 * (1 / 3.25) where the
code divides, the compiler having made the three divisions by the length
one reciprocal and three products. Clang keeps signbit checks under the
flags, but turns x + 0.0 into x.

Which of the two the VM does is not known -- LLVector3's division, today's
and 2007's, is a product with 1 / length, and its length is worked out in
singles -- so llVecNorm, llVecMag and llVecDist now fold on LSO and Mono
only where the single-precision length and a double's agree, and
llVecNorm only where dividing and multiplying by the reciprocal agree,
as llRound folds only where LSO's and Mono's sums agree. LSL on Luau keeps
the double's. Test 55 fails built with the flags. The corpus folds the
same as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…jump direction

alchemy-registry 4dc44e2 brings two patched ports:

- tailslide 1.5.12#4 (vector-products.patch): two vectors' dot product
  takes each part by the same part of the other, not x by z, and the
  cross product rounds each product as the VM does rather than letting
  the compiler fuse it into the difference. ALLSLArithmetic's own
  working of both stays, for its check that a single's steps and a
  double agree; its comment no longer calls Tailslide wrong.
- slua 2026-09-17#3 (lsl-jump-direction.patch): the LSL compiler decides
  a jump's direction by where its label stands in the script, not by the
  two nodes' addresses. The same text weighed on LSL on Luau no longer
  differs by a byte or two from run to run: a jump script weighed the
  same twelve times over, and the corpus four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chain of tests of one value, each `x & c`, merges in the size shapes:

- one-bit tests all asked, (x & 4) && (x & 8), as !~(x | -13);
- tests all clear, !(x & 4) && !(x & 8), as !(x & 12);
- an |, (x & 4) | (x & 8), as x & 12;
- an ||, where only whether it is true counts, as that too.

Each form is one the merge makes, so a longer chain merges a link at a
time. x is read once where it was read at each test, so it must be the
same at any time: an integer with no call but a pure one. Not where the
tests are of two values, nor an all-set test of more than one bit, where
(x & 6) && (x & 8) asks something else.

Every form is smaller on every target, by 8 to 35 bytes a merge
(ALLSLCosts::bitTestsMerged, held by allslcosts_test's pairs). The corpus
has no such chain. O25's remainder; test 56.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ives

column_select_left, _right, _up and _down are keys a step at a time,
which no item of the menus gives, and check_script_strings.py said so:
registered with add, and no item. addUnlistedEditorCommand registers a
command of the text's own with addUnlisted, the same in every other way,
and the checker reads it as unlisted. It agrees again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ALScriptConvertKeepComments, on by default, under LSL to SLua in the
studio's preferences beside the -- LSL: notes: whether the script's own
comments come over, each over what it stood over. Off, the SLua has none
of them, as before. Neither preset sets it, as neither sets the notes:
it is the scripter's either way. Cancel puts it back with the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
O22 left sorting, CSV, parsing and the rotation arithmetic unfolded, their
edges differing between LSO and Mono. They fold now where no edge is in
play, and where every way of working them out agrees.

- llParseString2List, and llParseStringKeepNulls without spacers: where
  which separator or spacer is found at a place cannot be in question --
  at most eight of each, as many as the VM reads, none empty and none
  the start of another -- and all is ASCII.
- llCSV2List: no value empty, none starting or ending with a space, and
  no angle brackets, which keep a vector's commas in.
- llListSort: the keys all of one type -- integers, floats, or strings of
  letters of one case and digits, which a culture orders as their code
  points do -- no two the same, so that a sort that is not stable sorts
  them alike, the stride dividing the list, and ascending TRUE or FALSE.
- llRotBetween, llAngleBetween and llAxes2Rot: the VM's way of working
  each out is not known, and ways the same on paper part in the last bit.
  Each is worked out as LLQuaternion has it today and as it had it in 2007
  (shortestArc's two algorithms; a matrix's quaternion made unit always,
  or only when more than a part in a million off), in singles and in
  doubles, and by a way apart (the angle by its arctangent, each part of
  a quaternion from its own square root), and folded only where every way
  comes to the same singles. A quarter turn from axes is left: made unit
  as today it is a bit larger in its last place than as in 2007. Not on
  Luau, for the angle, whose double the VM gives by a way not known.

The corpus calls none of them with constants. Test 57.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cost test let a jump on Luau be a byte either side of 93, since the
compiler made `jump L; @l;` JUMP one run and JUMPBACK, two bytes more,
another. With slua's jump direction taken from the script (the baseline
before last), a forward jump is JUMP every time: 92, which the table now
says and the test holds exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bit32 answers 0 to 4294967295 where LSL's integers were signed, and the
converter made them LSL's with a helper of its own, int32, written into
every script that needed it. SLua has bit32.s32 for exactly this, so the
converted script calls it and carries no helper, and int32 is no longer
a name the converter keeps for itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The files of LL's compiler as they stood before ca08bd5 removed them:
the grammar and lexer, the tree that type checks a script and emits its
LSO bytecode and CIL, scopes, errors, the heap and resource passes, the
bytecode format and conversion headers, and the library table the
compiler reads its builtins from. Unchanged, so every repair that follows
can be read against LL's own code with git diff ca08bd5^ -- indra/lscript.

Nothing builds these yet: the repairs come in commits of their own, and
the result is a reference for tests and tools, never part of the viewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lscript_rt_interface.h, where LL declared lscript_compile() for its callers,
as it stood before ca08bd5 removed it. Unchanged; it also declares the
VM's lscript_run(), which nothing defines until the VM is restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions (L1)

LL's server build writes the parts of its compiler that follow from the
grid's definitions with lsl-definitions' generator, spliced in at marker
comments or included, and so does this:

- the lexer's constants and events, at /* GENERATED LEXER CONSTANTS */ and
  /* GENERATED LEXER EVENTS */, so it needs none of the viewer's constant
  headers it read them from;
- the grammar's event tokens, types, alternatives and rules, at its four
  markers;
- the event nodes the generator writes (state_entry and the newest),
  included from lscript_tree_events.h and .inc in place of the 2015
  state_entry node;
- the library table, included in LLScriptLibrary::init() in place of the
  2015 list: every function at its own number, which dangerousAddFunction
  places it at, leaving a gap where no function has a number. The places
  that walk the table pass a gap by.

The events the generator leaves to hand-written code stay or are written
so: listen, changed and on_rez as they were, and linkset_data,
experience_permissions and experience_permissions_denied in their style.
transaction_result, path_update, those three and the rest new since 2015
have nodes written from LL's own template, and LSTT entries and state
bits by their event ids (35 to 43), and the type pass knows their
arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
indra/lscript builds as one static library, lscript, and only with tests
on: a reference, never linked into the viewer. Its CMakeLists replaces
LL's three, their bison.bat and the Windows unistd.h shim (flex is told
YY_NO_UNISTD_H there instead):

- lsl-definitions' generator, which the port's config names in
  LSL_DEFINITIONS_GENERATOR, writes the lexer, the grammar, the event
  nodes and the library table from lsl_definitions.yaml into generated/;
- bison and flex, found as CMake finds them, make the parser and scanner;
- the sources are built without the viewer's float flags, as allslvalues.cpp
  is, and without warnings as errors, which LL's code was never held to.

Where bison, flex, the generator or Python with PyYAML and llsd is
missing, the library is left out, and the configuration report's new row
says what it needs; requirements.txt gains PyYAML.

BOOL, TRUE and FALSE, gone from llcommon, are what LL's code and the
generated code are written in: lscript_byteformat.h, which the rest
includes, defines them as llcommon did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cope (L1)

Three bugs of LL's own:

- remote_data's rule registered id4 with the allocation manager three
  times and id5 and id6 never, so freeing a parse with a remote_data
  handler deleted id4 three times and leaked the other two;
- an lvalue's IDENTIFIER PERIOD IDENTIFIER registered id twice and its
  accessor never, the same again;
- LLScriptScope::findEntry() walked out through the parent scopes but
  looked in its own map each time, so a name declared in an outer scope
  was never found; it looks in each scope's, as findEntryTyped() does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (L1)

The type pass rejects a list in a list literal only where it initialises
a global, so a local's [a_list] reaches the CIL pass, whose box of an
element asserted on any type it had no box for. The grid's compiler is a
release build, where llassert is nothing: it boxes nothing and goes on.
A build with asserts on, as the tests run, now does the same, rather
than stopping where the grid writes CIL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ALLSLReference::compile() hands LL's compiler a script and returns the LSO
image or the CIL it made, and what else it wrote: its errors and notes.
LL's compiler reads and writes files and keeps its parse in globals, so a
compile goes through a temporary folder and one runs at a time. The CIL's
class is named from a fixed script id, as LL's compiler names it from the
script's.

The tests, each to LSO and to CIL:

- a script makes a whole 16 KB LSO2 image whose default state handles
  state_entry and whose llSay is library function 23 (LL's compiler calls
  every library function by the two-byte CALLLIB), and CIL with the class,
  the global and the handler;
- llLinksetDataWrite is function 650, and constants newer than the 2015
  lexer read as the definitions give them;
- the events new since 2015 compile, and LSO's state carries bits 34 to 42;
- a global read in a function and an event, and a remote_data handler,
  compile and free cleanly;
- a syntax error is rejected, and a list or an unset global in a global's
  list only where LSO's image is built: the CIL never runs that pass and
  takes both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ALTextView calls drawBeforeRows() and draws its line tints before it
clips to the text, and what they drew followed rows scrolled out of
sight. The command palette's list (ALChoiceList), scrolled with the
wheel past its choice, drew the choice's band below the list and out of
the window; a code editor's current line did the same scrolled away
from the caret, and a line tint by part of a row at the top or the
bottom. Each is clipped to the text now. The gutter, which that hook
also draws and which lies outside the text, is as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script converted from a prim's contents went to the inventory's
Scripts folder all the same. It is made in that prim now, as the
explorer makes a new script there (ALScriptWorkspace::create), and
opened once the prim lists it, with the SLua put in unsaved and set
beside the LSL: by the id the region gave it, or where the region named
it only through the contents, by its name, as one of that name that was
not there before. A script in the inventory, or a file, converts into
the inventory as before; a refusal says why, as a new script's does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ALTextDiff::lines() takes anchors: pairs of a left line and a right line
that stand for each other however they differ, an LSL statement and the
SLua written of it. Of those given, the most that rise on both sides are
kept (the longest rising run, at most one to a right line), each pair is
kept beside each other, and the stretches between them are compared on
their own. A pair that differs is a change of its own, after a Same run
of no lines where a change comes just before, so that a view pairing a
change's lines by their order keeps the pair's first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… source

ALDiffView::setTexts() takes ALTextDiff's anchors. Both sides being read
only, a character, a line broken or joined, a tab, a paste or a cut typed
in it went nowhere; whoever shows it is told now (setOnEdit) where, as a
line and column of the right's text, and answers with the view to type
in instead, which the key goes on to. rightAtCaret() says where: the
row's line, its column on the right or inline, and from a row the right
has no line on, the nearest it has after it, else before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ALLSLToSLua::Result::anchors pairs where each global, function, state,
handler and statement begins in the LSL with the first line of code
written of it -- past its notes and comments -- counted from the top of
the SLua, the head and the helpers included. What a comparison of the
two lines up by, where otherwise a working LSL script and a working SLua
one have hardly a line in common.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y to typing

A conversion's comparison, like the Explorer's or a history's, titled the
tab's side "(unsaved)" once, when it opened, and kept saying it after the
script was saved; it is titled again as the tab's unsaved dot moves. Its
sides could not be typed in, so an edit asked for Escape first; what is
typed in any comparison now brings the source back with the caret on the
line it was on, and goes on there. A conversion's comparison lines the
LSL up with the SLua by the converter's anchors, each statement beside
what it became.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The files of LL's LSO interpreter as they stood before ca08bd5 removed
them: lscript_execute (the VM, which runs an LSO image a handler at a
time), lscript_readlso (its disassembler), lscript_heapruntime (the heap
as it runs), the resource pool and consumer, and their headers.
Unchanged, so every repair can be read against LL's own code.

Restored now as scaffolding for the tests, on the user's word, ahead of
the library stand-ins that would let it run a script's calls: nothing
builds it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lscript_execute, lscript_readlso, lscript_heapruntime and the resource
pool join the lscript library, in place of their own CMakeLists; they
build as they were, without a warning. The library's functions stay
dummy_func, as LL's viewer copy had them: a test answers a call it needs
through LLScriptLibrary's own table.

A test compiles a script with a global, a function and a cast, runs its
image in the VM a quantum at a time, and hears its llSay -- answered by a
stand-in -- say 42, one handler run and no fault.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… metadata name

ALScriptWeigh gains what LL's compiler is to be held against: Tailslide's
LSO image and CIL text as lso() and mono() have them before weighing
(tailslideLSO, tailslideCIL), and monoOfCIL(), mono()'s accounting over
any assembly's text -- the accounting split out of mono(), which gives
what it gave. Without Tailslide's tree, a method is named from the
assembly (a function from its 'g' name), and LL's text is read a line to
an instruction: its `cil managed` on a line of its own, and what it runs
onto a print's line, put right.

A member a script refers to was counted by its text: its quotes, and once
for each way its owner was written (`class`, `valuetype`, bare). The
metadata holds a member once and its name without the quotes the
assembler reads it by, so it is counted so; a Mono reference costs 17
rather than 19, and a global, which a field's reference sets, two less,
or four where a rotation's helper is quoted too (ALLSLCosts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RyeMutt and others added 14 commits October 4, 2026 08:59
…back

- Two string literals joined, as the addstrings option would, always on
  LSO, which writes every string where it is used: 4 bytes a join. On
  Mono and Luau the joined may be one more string held beside its two,
  and the option still says.
- do S while (TRUE) as @loop; S jump loop; on LSO and Mono (ALLSLCosts::
  jumpForForever), 6 and 5 bytes a loop: a jump back in place of the
  check's constant and its jump. In a last run of the branches pass, once
  the rounds are done, since what follows such a loop holds a label the
  dead code would take for one a jump comes back to; the body's
  statements put in the block where they declare nothing, so that a jump
  out of the loop and the jump back become one jump back where the check
  fails -- LSL's break.
- A jump over statements with nothing else in its way is written if (!c)
  S, a comparison turned round, rather than an empty branch for the
  simplifier.

Test 66, the costs facts, the LSO golden. Corpus over the last step: LSO
-82, Mono -51; over the baseline LSO -2518, Mono -4052, Luau -3878.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SLua's LLEvents puts on a handler assigned to a field as LLEvents:on does,
beside any others, and stops the script at a field assigned nil: setState
left a state of a script converted with handlers as fields by assigning
nil. It takes them off with LLEvents:off now, and the option's tooltip
says what an assignment does rather than that it keeps others out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each side's empty rows, standing beside the lines the other side has, are
spacer lines of its text view now, which a copy and the primary selection
leave out with their breaks; the text's last line gives no break for the
spacers after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An & asked only whether it is nought is bit32.btest of its operands; values
added to a list together are one table.append, which has each before it
adds any, as the list written out did; a key's default is NULL_KEY, which
uuid("") gives in SLua; and the casts of a string are each one call of
llcompat's list item, which converts as the cast does, in place of Lua
patterns that read neither hexadecimal floats nor 32-bit wrapping.
Hexadecimal stays as the LSL wrote it where SLua reads the same number:
not past 0x7FFFFFFF, which LSL's integers wrapped below nought.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…what is called early

A for stepping by a global nothing sets, or by one of LSL's constants, is
Luau's numeric for, its step written by the name; a counter rejected for
that kept every other for it was used in as a while too. And only the
functions a function written before them calls are declared first, the
rest local functions, where one such call declared every function first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A comment after a statement on its line stays after it, at the end of the
first line written of it, where the statement is one line or no if or
loop; an if the LSL wrote on one line, one statement and no else, is
written on one line; a rule of stars or slashes, a heading in it or not,
is one of dashes as long; and the //* ... //*/ toggle is ---[[ ... --]],
its other way /* ... //*/ a block comment closed by --]].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Declarations on lines one after another, each one line and given a
value, whose = the LSL put at one column, have their = at one column
again, and the comments after them, where the LSL put those at one, at
one past the longest line that has one: what each is written as is as
long as it is, a type's name longer than another's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndow

A register belonged to its editor's keymap, so what was yanked in one
script was not there to put in another unless it went by the system
clipboard. The registers are in the state the keymaps share now, with
the histories and the settings, and the studio's windows share one such
state for the session, as vim shares one for all its buffers: a window
popped out puts what another yanked. The vimrc is read into it once,
and again when it changes, so a window opened later leaves a :set
typed in another as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bit32 takes 0xF0000000 as the -268435456 LSL wrapped it to, and either is
one constant, so an operand of bit32's is written as the LSL wrote it.
Elsewhere such a number stays the one LSL had: bit32.s32 of the
hexadecimal is a call SLua's compiler does not fold, 41 bytes more for a
global's value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid's compilers run on 32-bit hosts, whose strtoul stops at
0xFFFFFFFF, so a literal past it is -1, decimal or hexadecimal, on LSO,
Mono and LSL on SLua alike. Tailslide and LL's restored compiler, built
64-bit, wrapped it instead: 4294967296 was 0. The registry's baseline
moves to e588cfb, whose Tailslide 1.5.14#1 reads a literal as the grid
does (literals-as-32-bit.patch), and LL's lexer here reads one the same
way, so that the two still make the same image of a script with one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A variable given hexadecimal past 0x7FFFFFFF and never set, which nothing
but bit32 reads -- S_MASK = 0xF0000000 in every & -- keeps the hexadecimal:
bit32 takes it as the -268435456 LSL wrapped it to, and either is one
constant. One read as a number keeps the number LSL had. And a number
past 0xFFFFFFFF, which the grid's 32-bit hosts read as -1, is said to be
where it is written as -1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as -1

A lint of LSL's, a warning: the grid's compilers read a number past
0xFFFFFFFF as -1 -- their 32-bit hosts' strtoul stops there -- decimal or
hexadecimal, in an integer, a float or a vector, and as 1 with a minus
before it. Read from the text, past strings, comments and floats, since
Tailslide's tree keeps only the -1. Its place is the table's last, so
that the lints' masks keep their bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s32 are

Neither was said anywhere: a mask kept as 0xF0000000 is 4026531840 in SLua,
not LSL's -268435456, and one read as a number is written -268435456; and
bit32.s32 is there because bit32 answers 0 to 4294967295 where LSL's
integers were signed. A scripter from LSL who compares the mask, or takes
bit32.s32 for noise, has a script that no longer does what it did. Each
is a note over the first place it is written, once a script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 222 files, which is 122 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 57232d51-30ae-4a16-bef0-d21be8a20d5a
📥 Commits

Reviewing files that changed from the base of the PR and between ff7fcfb and a2817eb.

📒 Files selected for processing (222)
  • CLAUDE.md
  • doc/ARCHITECTURE.md
  • doc/BUILD.md
  • indra/CMakeLists.txt
  • indra/alscript/CMakeLists.txt
  • indra/alscript/allsloptimizer.cpp
  • indra/alscript/core/almessagemap.cpp
  • indra/alscript/core/almessagemap.h
  • indra/alscript/core/alnotecardformat.cpp
  • indra/alscript/core/alnotecardformat.h
  • indra/alscript/core/alscriptengine.h
  • indra/alscript/core/alscriptformatter.cpp
  • indra/alscript/core/alscriptformatter.h
  • indra/alscript/core/alscriptlexicon.cpp
  • indra/alscript/core/alscriptlexicon.h
  • indra/alscript/core/alscriptmessages.cpp
  • indra/alscript/core/alscriptmessages.h
  • indra/alscript/core/alscriptnameindex.cpp
  • indra/alscript/core/alscriptnameindex.h
  • indra/alscript/core/alscriptproblem.h
  • indra/alscript/core/alscriptspan.h
  • indra/alscript/core/alscriptstack.cpp
  • indra/alscript/core/alscriptstack.h
  • indra/alscript/core/alscriptsymbol.h
  • indra/alscript/core/alscripttempfiles.cpp
  • indra/alscript/core/alscripttempfiles.h
  • indra/alscript/core/alscriptweight.cpp
  • indra/alscript/core/alscriptweight.h
  • indra/alscript/lint/allsllintpass.cpp
  • indra/alscript/lint/allsllintpass.h
  • indra/alscript/lint/alscriptfixes.cpp
  • indra/alscript/lint/alscriptfixes.h
  • indra/alscript/lint/alscriptlintpass.cpp
  • indra/alscript/lint/alscriptlintpass.h
  • indra/alscript/lint/alselenefilters.cpp
  • indra/alscript/lint/alselenefilters.h
  • indra/alscript/lsl/allslconsts.cpp
  • indra/alscript/lsl/allslconsts.h
  • indra/alscript/lsl/allsleffects.cpp
  • indra/alscript/lsl/allsleffects.h
  • indra/alscript/lsl/allsleventtext.inc
  • indra/alscript/lsl/allslexports.cpp
  • indra/alscript/lsl/allslexports.h
  • indra/alscript/lsl/allslservice.cpp
  • indra/alscript/lsl/allslservice.h
  • indra/alscript/lsl/allsltoslua.cpp
  • indra/alscript/lsl/allsltoslua.h
  • indra/alscript/lsl/allsltraits.cpp
  • indra/alscript/lsl/allsltraits.h
  • indra/alscript/lsl/allsltraits.inc
  • indra/alscript/lsl/allsluuids.inc
  • indra/alscript/lsl/allslvalues.cpp
  • indra/alscript/lsl/allslvalues.h
  • indra/alscript/lsl/optimizer/allslbranches.cpp
  • indra/alscript/lsl/optimizer/allslbranches.h
  • indra/alscript/lsl/optimizer/allslconstantglobals.cpp
  • indra/alscript/lsl/optimizer/allslconstantglobals.h
  • indra/alscript/lsl/optimizer/allslcosts.cpp
  • indra/alscript/lsl/optimizer/allslcosts.h
  • indra/alscript/lsl/optimizer/allsldeadcode.cpp
  • indra/alscript/lsl/optimizer/allsldeadcode.h
  • indra/alscript/lsl/optimizer/allslflowvalues.cpp
  • indra/alscript/lsl/optimizer/allslflowvalues.h
  • indra/alscript/lsl/optimizer/allslfolder.cpp
  • indra/alscript/lsl/optimizer/allslfolder.h
  • indra/alscript/lsl/optimizer/allslglobals.cpp
  • indra/alscript/lsl/optimizer/allslglobals.h
  • indra/alscript/lsl/optimizer/allslinliner.cpp
  • indra/alscript/lsl/optimizer/allslinliner.h
  • indra/alscript/lsl/optimizer/allsllibraryfold.cpp
  • indra/alscript/lsl/optimizer/allsllibraryfold.h
  • indra/alscript/lsl/optimizer/allslnames.cpp
  • indra/alscript/lsl/optimizer/allslnames.h
  • indra/alscript/lsl/optimizer/allsloptimizer.cpp
  • indra/alscript/lsl/optimizer/allsloptimizer.h
  • indra/alscript/lsl/optimizer/allsloptimizerpass.cpp
  • indra/alscript/lsl/optimizer/allsloptimizerpass.h
  • indra/alscript/lsl/optimizer/allslprinter.cpp
  • indra/alscript/lsl/optimizer/allslprinter.h
  • indra/alscript/lsl/optimizer/allslrepeatedcalls.cpp
  • indra/alscript/lsl/optimizer/allslrepeatedcalls.h
  • indra/alscript/lsl/optimizer/allslshapes.cpp
  • indra/alscript/lsl/optimizer/allslshapes.h
  • indra/alscript/lsl/optimizer/allslsignatures.cpp
  • indra/alscript/lsl/optimizer/allslsignatures.h
  • indra/alscript/lsl/optimizer/allslsimplifier.cpp
  • indra/alscript/lsl/optimizer/allslsimplifier.h
  • indra/alscript/luau/alluauconfig.cpp
  • indra/alscript/luau/alluauconfig.h
  • indra/alscript/luau/alluauexports.cpp
  • indra/alscript/luau/alluauexports.h
  • indra/alscript/luau/alluauservice.cpp
  • indra/alscript/luau/alluauservice.h
  • indra/alscript/preprocessor/aldiskcache.cpp
  • indra/alscript/preprocessor/aldiskcache.h
  • indra/alscript/preprocessor/aldiskincludes.cpp
  • indra/alscript/preprocessor/aldiskincludes.h
  • indra/alscript/preprocessor/alincludeidentity.cpp
  • indra/alscript/preprocessor/alincludeidentity.h
  • indra/alscript/preprocessor/alincludesearch.cpp
  • indra/alscript/preprocessor/alincludesearch.h
  • indra/alscript/preprocessor/almodulelook.cpp
  • indra/alscript/preprocessor/almodulelook.h
  • indra/alscript/preprocessor/alpreprocessor.cpp
  • indra/alscript/preprocessor/alpreprocessor.h
  • indra/alscript/preprocessor/alscriptenvelope.cpp
  • indra/alscript/preprocessor/alscriptenvelope.h
  • indra/alscript/preprocessor/alscriptjoblane.h
  • indra/alscript/preprocessor/alscriptsnapshot.cpp
  • indra/alscript/preprocessor/alscriptsnapshot.h
  • indra/alscript/preprocessor/alscripttextcache.cpp
  • indra/alscript/preprocessor/alscripttextcache.h
  • indra/alscript/preprocessor/alsourcemap.cpp
  • indra/alscript/preprocessor/alsourcemap.h
  • indra/alscript/tests/aldiskcache_test.cpp
  • indra/alscript/tests/aldiskincludes_test.cpp
  • indra/alscript/tests/alincludeidentity_test.cpp
  • indra/alscript/tests/alincludesearch_test.cpp
  • indra/alscript/tests/allslconsts_test.cpp
  • indra/alscript/tests/allslcosts_test.cpp
  • indra/alscript/tests/allsleffects_test.cpp
  • indra/alscript/tests/allsloptimizer_test.cpp
  • indra/alscript/tests/allslservice_test.cpp
  • indra/alscript/tests/allsltoslua_test.cpp
  • indra/alscript/tests/alluauconfig_test.cpp
  • indra/alscript/tests/alluauservice_test.cpp
  • indra/alscript/tests/almessagemap_test.cpp
  • indra/alscript/tests/almodulelook_test.cpp
  • indra/alscript/tests/alnotecardformat_test.cpp
  • indra/alscript/tests/alpreprocessor_test.cpp
  • indra/alscript/tests/alpreprocessorc11_test.cpp
  • indra/alscript/tests/alscript_bench.cpp
  • indra/alscript/tests/alscriptenvelope_test.cpp
  • indra/alscript/tests/alscriptfixes_test.cpp
  • indra/alscript/tests/alscriptformatter_test.cpp
  • indra/alscript/tests/alscriptjoblane_test.cpp
  • indra/alscript/tests/alscriptlexicon_test.cpp
  • indra/alscript/tests/alscriptlintpass_test.cpp
  • indra/alscript/tests/alscriptmessages_test.cpp
  • indra/alscript/tests/alscriptnameindex_test.cpp
  • indra/alscript/tests/alscriptsnapshot_test.cpp
  • indra/alscript/tests/alscriptstack_test.cpp
  • indra/alscript/tests/alscripttempfiles_test.cpp
  • indra/alscript/tests/alscripttextcache_test.cpp
  • indra/alscript/tests/alscriptweight_test.cpp
  • indra/alscript/tests/alsourcemap_test.cpp
  • indra/alscript/tests/optimizer/test_lso_expected.lsl
  • indra/alscript/tests/optimizer/test_shrink_expected.lsl
  • indra/alscript/tests/optimizer/test_vendor_expected.lsl
  • indra/cmake/ConfigurationReport.cmake
  • indra/llui/alchoicelist.cpp
  • indra/llui/alcodeeditor.cpp
  • indra/llui/aldiffview.cpp
  • indra/llui/aldiffview.h
  • indra/llui/altextdiff.cpp
  • indra/llui/altextdiff.h
  • indra/llui/altextview.cpp
  • indra/llui/altextview.h
  • indra/llui/alvimkeymap.cpp
  • indra/llui/alvimkeymap.h
  • indra/llui/tests/aldiffview_test.cpp
  • indra/llui/tests/altextdiff_test.cpp
  • indra/llui/tests/alvimkeymap_test.cpp
  • indra/lscript/CMakeLists.txt
  • indra/lscript/allslreference.cpp
  • indra/lscript/allslreference.h
  • indra/lscript/llscriptresource.h
  • indra/lscript/llscriptresourceconsumer.h
  • indra/lscript/llscriptresourcepool.h
  • indra/lscript/lscript_alloc.h
  • indra/lscript/lscript_byteconvert.h
  • indra/lscript/lscript_byteformat.h
  • indra/lscript/lscript_compile/indra.l
  • indra/lscript/lscript_compile/indra.y
  • indra/lscript/lscript_compile/lscript_alloc.cpp
  • indra/lscript/lscript_compile/lscript_bytecode.cpp
  • indra/lscript/lscript_compile/lscript_bytecode.h
  • indra/lscript/lscript_compile/lscript_error.cpp
  • indra/lscript/lscript_compile/lscript_error.h
  • indra/lscript/lscript_compile/lscript_heap.cpp
  • indra/lscript/lscript_compile/lscript_heap.h
  • indra/lscript/lscript_compile/lscript_resource.cpp
  • indra/lscript/lscript_compile/lscript_resource.h
  • indra/lscript/lscript_compile/lscript_scope.cpp
  • indra/lscript/lscript_compile/lscript_scope.h
  • indra/lscript/lscript_compile/lscript_tree.cpp
  • indra/lscript/lscript_compile/lscript_tree.h
  • indra/lscript/lscript_compile/lscript_typecheck.cpp
  • indra/lscript/lscript_compile/lscript_typecheck.h
  • indra/lscript/lscript_execute.h
  • indra/lscript/lscript_execute/llscriptresource.cpp
  • indra/lscript/lscript_execute/llscriptresourceconsumer.cpp
  • indra/lscript/lscript_execute/llscriptresourcepool.cpp
  • indra/lscript/lscript_execute/lscript_execute.cpp
  • indra/lscript/lscript_execute/lscript_heapruntime.cpp
  • indra/lscript/lscript_execute/lscript_heapruntime.h
  • indra/lscript/lscript_execute/lscript_readlso.cpp
  • indra/lscript/lscript_execute/lscript_readlso.h
  • indra/lscript/lscript_export.h
  • indra/lscript/lscript_http.h
  • indra/lscript/lscript_library.h
  • indra/lscript/lscript_library/lscript_alloc.cpp
  • indra/lscript/lscript_library/lscript_export.cpp
  • indra/lscript/lscript_library/lscript_library.cpp
  • indra/lscript/lscript_rt_interface.h
  • indra/lscript/tests/allslreference_test.cpp
  • indra/lscript/tests/allslreferencecheck_test.cpp
  • indra/newview/alfloaterscriptpreferences.cpp
  • indra/newview/alfloaterscriptstudio.cpp
  • indra/newview/alfloaterscriptstudio.h
  • indra/newview/alscriptstudiodoc.h
  • indra/newview/alscriptstudiovim.cpp
  • indra/newview/alscriptstudiovim.h
  • indra/newview/app_settings/settings_alchemy.xml
  • indra/newview/llscripteditorws.cpp
  • indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml
  • indra/newview/skins/default/xui/en/strings.xml
  • indra/newview/tests/alscriptanalyzers_test.cpp
  • indra/vcpkg-configuration.json
  • requirements.txt
  • scripts/content_tools/check_script_strings.py
  • scripts/content_tools/generate_lsl_traits.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

pre-commit-ci Bot and others added 6 commits October 4, 2026 19:51
LSO's VM and LSL on SLua cast a string to an integer by strtoul, base 16
only after a leading 0x, on 32-bit Linux, whose strtoul stops at
0xFFFFFFFF: a number past it is -1, its minus or not. The fold called
this host's strtoul through Tailslide, which wraps it on 64 bits:
(integer)"4294967296" was 0. And llJson2List's numbers past 32 bits
are left to the simulator: they were read as this host's long, 64 bits
here and 32 on Windows, so the tooling did not even agree with itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s do

The registry's baseline moves to ef285cd, whose Tailslide 1.5.14#2 casts
a string to an integer by a strtoul of 32 bits, as the VMs do on the
grid's 32-bit Linux hosts (string-casts-as-32-bit.patch): the weigher's
compiles and the studio's checks fold it so too, and the optimizer's own
copy of the cast goes, its test holding Tailslide's to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m out agree

llSin and the rest folded by this host's libm, which is not the grid's --
glibc on 32-bit Linux, .NET's under Mono -- and on Luau to a double. Each
folds now only where it agrees in double rounded once, as the VMs work it
out, and in single precision, where an answer near a single's rounding
makes the two part as two libraries could; and to a single on every
target, as each VM answers one (SLua's luaSL_pushfloat rounds its own).
SLua's edges too: a pow or an atan2 of -0 is 0, and on Luau sin, cos and
tan take an argument that is no single as it is, so those are left. A
vector's length on Luau, which the simulator gives by a way not known,
folds only where the single is the double, and llVecNorm only where its
division and its product agree there as elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tests that link llui do

On Linux the link pulls llui's URL entries, which call rlvGetAnonym, so
the test failed to link there with it undefined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RyeMutt
RyeMutt merged commit 1e88085 into develop Oct 5, 2026
15 of 20 checks passed
@RyeMutt
RyeMutt deleted the rye/script-optim branch October 5, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant