lsl-luau vm bug: lists are incorrectly evaluated to empty when included on the left-hand and right-hand side of an expression
planned
TrishAce Resident
I encountered this Mono vs LSL VM discrepancy while trying to use AVSitter compiled to the LSL VM (offsets would fail to work due to how lists are handled in sitA)
Here is a sample script that breaks on LSL VM but works on Mono:
default {
state_entry()
{
list vectors = [<1,0,1>,<0,-0.192,2.479>];
vectors = [(vector)llList2String(vectors, 0),(vector)llList2String(vectors, 1)]; // <--- does not work in lsl vm
llSay(0,"LSL VM TEST VECTOR PARSE: " + (string)((vector)llList2String(vectors, 1)));
}
}
This pattern breaks compatibility due to the self-referenced assignment, the vectors array becomes [<0,0,0>, <0,0,0>] instead of keeping the same values [<1,0,1>,<0,-0.192,2.479>].
The expected output of llSay here is <0,-0.192,2.479> like in Mono, but in lsl vm it outputs <0,0,0>
Tested in Clawed region, current simulator: Luau 2026-06-09.27238848717
A simpler version that also breaks:
default {
state_entry()
{
list integers = [1,2,3];
integers = [llList2Integer(integers, 0)];
llSay(0, llList2String(integers, 0)); // Expected Output: 1, Actual: 0
}
}
Some further observations from Suzanna:
Another example, tested on the Beta grid, Luau 2026-07-24.30110090421:
default {
state_entry() {
list myList = ["test"];
myList = [myList == []]; // myList should be [0]
llOwnerSay((string)myList); // --> 1
}
}
When the list being assigned to is also used inside the expression, its value is evaluated to an empty list.
I think the bug comes from here:
where the destination list is assigned a new empty table before evaluating the expression.
Log In
H
Harold Linden
updated the status to
planned
H
Harold Linden
Thanks TrishAce Resident ! I believe you're correct about the location of the bug. From my reading, the problem is that as soon as we see that
[
, we create a new list at the storage point where the current list exists.It's a nice optimization, and saves a later
MOVE
to move the new list from temporary storage to its ultimate destination, but that doesn't work out so well if the list is referenced anywhere within that list expression. Since we evaluate those expressions _after_ creating the storage for the new list, those expressions end up operating on our new, totally empty list. Even worse, imagine a (maybe pathological) sub-expression that reassigns the list like list foo = [1,2,3]; foo = [llList2Integer(foo = [4,5,6], 0), llList2Integer(foo, 1), llList2Integer(foo, 2) ];
. Big oversight on my part :).I think ultimately we want something smart enough where we can avoid a temporary register for the new list in cases where it can be avoided, but I can't think of great heuristics for that at the moment. Might just need to use a temporary register +
MOVE
at the end of evaluating the expression for now.TrishAce Resident
Harold Linden What about doing something like a "SymbolUseVisitor" that would allow you to do SymbolUseVisitor.visit(expression, symbol) and returns true if the symbol is found within the expression? If not found, then the optimized method can be used.
That way the optimization can still exist for all simple/complex assignments that do not point to that list, and it would not be applied to cases where it references itself.
At a glance, with this idea, the only thing I'm unsure about is how it would handle things for global lists, as they can be used in function calls. If so, seeing a lsl user-defined function would require the visitor to also visit the function and check for use of the symbol. For cases like:
list myGlobalList = [1];
list myListMutator() {
myGlobalList += [2];
}
// Some function then does...
myGlobalList = [myListMutator()];
H
Harold Linden
TrishAce Resident Yeah, things get quite hairy in the global lists case because what the global name refers to may be mutated non-locally. I want to say it would be mitigated by the fact that each reference to a global requires loading a reference to it into a register first, but I'll see if I can figure out a way to make it work with minimal fuss.
M
Mercury Linden
updated the status to
tracked
Thanks for the report! Issue is being tracked