Script Studio: LSL optimizer size work, LL's compiler as a reference, and SLua converter QA - #404
Merged
Merged
Conversation
…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>
…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>
Contributor
|
Important Review skippedToo 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
📒 Files selected for processing (222)
You can disable this status message by setting the
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. Comment |
for more information, see https://pre-commit.ci
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ALLSLCostsmeasures it smaller on that target, every answer held byallslcosts_test:&&as&,-as+ -, shifts as products,-1 - xas~x, ...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:
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)
indra/lscript, built with the tests.allslreferencecheckholds Tailslide's LSO images and CIL to LL's, byte for byte, and the Mono weigher to both.e588cfb, pushed):bitstream-move-to-endandmono-constants-as-ll: what it compiles is LL's output.literals-as-32-bit: an integer literal past0xFFFFFFFFis -1, as the grid's 32-bit hosts read it.SLua converter
//*toggle becomes---[[.bit32.btestfor an&asked only whether it is nought;table.appendfor several values added together;NULL_KEYfor a key's default;0x7FFFFFFF, and another whybit32.s32is there.0xFFFFFFFFis noted where it is written as -1.LLEvents:off. SLua stops a script that assigns nil to anLLEventsfield, which leaving a state did.Studio
SlIntegerPast32Bits, a warning on a whole number past0xFFFFFFFF, which LSL reads as -1.Testing
scripts/content_tools/check_script_strings.pyagrees.🤖 Generated with Claude Code