sei-lisa / lsl-pyoptimizer Goto Github PK
View Code? Open in Web Editor NEWOptimizes a LSL2 script, folding constants, removing unused code and more. Adds new syntax features too.
License: GNU General Public License v3.0
Optimizes a LSL2 script, folding constants, removing unused code and more. Adds new syntax features too.
License: GNU General Public License v3.0
I was wondering if you had any plans for say pre-made binaries that the user could just download and run?
I locally setup a setup a working singlefile py2exe setup.py (using 32bit 2.7 python) and I edited main.py to correctly support the difference in the zip below.
LSL-PyOptimizer.zip
the specifics of the change to main.py are here
def main(argv):
"""Main executable."""
+ import os
+
+ if hasattr(sys,"frozen") and sys.frozen in ("windows_exe", "console_exe"):
+ lslopt.lslcommon.DataPath=(os.path.dirname(os.path.abspath(sys.executable)) + os.sep)
+ else:
# If it's good to append the basename to it, it's good to append the
# auxiliary files' names to it, which should be located where this file is.
lslopt.lslcommon.DataPath = __file__[:-len(os.path.basename(__file__))]
... and probably some other stuff from the latest server release as well.
Example code:
default
{
link_message(integer sender, integer num, string str, key keyVal)
{
integer dead = 0;
if (num == 5)
dead = 1;
else
dead = 2;
llOwnerSay("Not Dead");
}
}
Output with DCR + Constant Folding:
default
{
link_message(integer sender, integer num, string str, key keyVal)
{
num == 5;
llOwnerSay("Not Dead");
}
}
Without constant folding it is even worse:
default
{
link_message(integer sender, integer num, string str, key keyVal)
{
0;
if (num == 5)
1;
else
2;
llOwnerSay("Not Dead");
}
}
Re-running the output code back through the optimizer will remove the dead code (with Const folding on).
Expected Output:
default
{
link_message(integer sender, integer num, string str, key keyVal)
{
llOwnerSay("Not Dead");
}
}
Would it be possible to provide basic python packaging (setup.py/pyproject.toml, PEP-518) to simplify LSL-PyOptimizer installation? Were this done, the project could be installed via pip and the CLI automatically placed into the system path.
Assuming the input is x = 5, y = 2
The following function f returns 3:
integer f (integer x, integer y) { return llCeil(x * 1.0 / y); }
Previous function gets optimized to a function q like this, which returns 2:
integer q (integer x, integer y) { return llCeil(x / y); }
Therefore I think that the optimizer should be able to handle this type of implicit float casting, if not we will get unexpected results due to the properties of integer division.
Hi. Is there any way to use your excellent LSL-PyOptimizer to clean up scripts while keeping the constant flags intact in the output (like ZERO_VECTOR
, CHANGED_LINK
, and all the other constants defined in builtins.txt)?
The reason is that I was hoping to use your scripts to clean up some LSL for public release but still keep them human-readable. The dead code removal and the other features are just perfect for this but I would like the constant flags to remain as text in the results, rather than being converted to their true values.
For example, at the moment, CHANGED_LINK
is converted to 32
, and ZERO_VECTOR
is converted to <((float)0), ((float)0), ((float)0)>
. However, I would like all the flags to remain as (or be converted back to) the original text versions for the results.
I have tried the -ConstFold
option but that does not seem to achieve what I want.
Thanks again for all your amazing work :)
An update is needed to support PBR values such as for set prim params:
PRIM_RENDER_MATERIAL https://wiki.secondlife.com/wiki/PRIM_RENDER_MATERIAL
PRIM_GLTF_BASE_COLOR https://wiki.secondlife.com/wiki/PRIM_GLTF_BASE_COLOR
PRIM_GLTF_NORMAL https://wiki.secondlife.com/wiki/PRIM_GLTF_NORMAL
PRIM_GLTF_METALLIC_ROUGHNESS https://wiki.secondlife.com/wiki/PRIM_GLTF_METALLIC_ROUGHNESS
PRIM_GLTF_EMISSIVE https://wiki.secondlife.com/wiki/PRIM_GLTF_EMISSIVE
As well as:
New function llGetInventoryDesc - returns the description of the inventory item: https://wiki.secondlife.com/wiki/LlGetInventoryDesc
New function llIsFriend - returns TRUE if agent_id and the owner of the prim the script is in are friends: https://wiki.secondlife.com/wiki/LlIsFriend
New function llRezObjectWithParams - apply a list of prim properties that are applied when the object is rezzed: https://wiki.secondlife.com/wiki/LlRezObjectWithParams
New function llListFindListNext - find the index of non-unique list entries beyond the first instance: https://wiki.secondlife.com/wiki/LlListFindListNext
llSetDamage can now set negative damage: https://wiki.secondlife.com/wiki/LlSetDamage
New LSL changed() event type https://wiki.secondlife.com/wiki/CHANGED_RENDER_MATERIAL
And maybe some others that I've missed.
Hi Sei Lisa,
I tried the online version http://lsl.blacktulip-virtual.com/lsl-pyoptimizer/online.php
and got the optimizer output:
Traceback (most recent call last):
File "/home/sei/lslopt_master/main.py", line 742, in <module>
ret = main(sys.argv)
File "/home/sei/lslopt_master/main.py", line 738, in main
werr(e.__class__.__name__ + ': ' + str(e) + '\n')
File "/home/sei/lslopt_master/strutil.py", line 85, in werr
sys.stderr.write(any2u(s, sys.stderr))
File "/usr/lib/python2.7/encodings/__init__.py", line 100, in search_function
level=0)
MemoryError
Is the script too large for the online version?
Optimizer produces the error:
AttributeError: 'optimizer' object has no attribute 'PythonType2LSL'
When optimizing the following code with Constant Folding and Dead Code Removal
default
{
link_message(integer u1, integer u2, string u3, key u4)
{
vector vec = <1, 1, 1>;
float val = 2.0;
float x = vec.x * val;
llOwnerSay((string)x);
}
}
This is the smallest reproducible I managed to get that throws the error, my code uses a lot of macros and constant folding that eventually results in the same error.
Also, I've noticed that with only Dead Code Removal enabled. (Constant folding off) it produces this snippet:
default
{
link_message(integer u1, integer u2, string u3, key u4)
{
vector vec = <1, 1, 1>;
2.;
float x = vec.x * 2.;
llOwnerSay((string)x);
}
}
That 2.;
is pointless.
Under Windows 7, using
C:\Windows\System32\cmd.exe /k C:\PROGRA~2\Python27\python.exe ..\LSL-PyOptimizer\main.py --preshow -o main-preprocessed.lsl main.lsl
or
C:\Windows\System32\cmd.exe /k C:\PROGRA~2\Python27\python.exe ..\LSL-PyOptimizer\main.py --preshow >main-preprocessed.lsl main.lsl
results in a main-preprocessed.lsl file that is identical to main.lsl give or take a few changes in whitespace*. Specifically, #define declarations still exist in the output and the substitutions of defined values for the defined names have not been performed. Likewise #ifdef blocks are not removed, etc.
Simplified repro using with Constant folding, and DCR:
integer gPrevious;
default
{
touch_end(integer num)
{
integer now = llGetUnixTime();
if (now - gPrevious > 15)
{
gPrevious = now;
llOwnerSay("Touch");
}
}
}
Results in the following optimized code:
integer gPrevious;
default
{
touch_end(integer num)
{
integer now = llGetUnixTime();
if (15 < now + -gPrevious)
{
gPrevious = now;
llOwnerSay("Touch");
}
}
}
I'm not sure if the conversion of "if (now - gPrevious > 15)" to "if (15 < now + -gPrevious)" is correct, because now there is + and - which each take up a byte each, don't they?
Also, I would excpect the + - to end up being two operations at the lowest level: negation and addition, compared to - just being a single subtraction, which should be faster.
Or is there some weird LSL quirk I am not aware of?
the optimizer breaks this script:
integer driverListener;
startDriverAndOwnerListener(key driver)
{
llListenRemove(driverListener);
if (driver != NULL_KEY)
{
driverListener = llListen(483, "", driver, "");
}
}
default
{
state_entry()
{
startDriverAndOwnerListener(NULL_KEY);
}
}
output:
integer driverListener;
startDriverAndOwnerListener(key driver)
{
llListenRemove(driverListener);
if (!(driver == "00000000-0000-0000-0000-000000000000"))
{
driverListener = llListen(483, "", driver, "");
}
}
default
{
state_entry()
{
startDriverAndOwnerListener("");
}
}
NULL_KEY is replaced with "" in state_entry, but not in the if condition
The following code:
default
{
state_entry()
{
string tmp0 = llGetScriptName();
list tmp1 = [llGetOwner(), llGetOwner()];
list tmp2 = [llGetScriptName(), llGetScriptName()];
string encoded = llDumpList2String([tmp0, llList2CSV(tmp1), "Hello", 123, llList2CSV(tmp2)], "|");
llOwnerSay(encoded);
}
}
Produces:
default
{
state_entry()
{
string tmp0 = llGetScriptName();
list tmp1 = [llGetOwner(), llGetOwner()];
list tmp2 = [llGetScriptName(), llGetScriptName()];
string encoded = tmp0 + ("|123|Hello|123|" + (llList2CSV(tmp1) + ("|123|Hello|123|" + llList2CSV(tmp2))));
llOwnerSay(encoded);
}
}
Which results in way too many 123|Hello sequences
Options Used:
Constant folding, Concatenate constant strings, Dead code removal
Concatenate constant strings is the one that causes it.
Correct result is something like:
default
{
state_entry()
{
string tmp0 = llGetScriptName();
list tmp1 = [llGetOwner(), llGetOwner()];
list tmp2 = [llGetScriptName(), llGetScriptName()];
string encoded = tmp0 + ("|" + (llList2CSV(tmp1) + ("|" + ("Hello" + ("|" + ("123" + ("|" + llList2CSV(tmp2))))))));
llOwnerSay(encoded);
}
}
This is a little of a special case because jump statements are rarely used but take this function for an example:
list out(){ integer n = 3; @continue; llOwnerSay((string)n); --n; if( !n ) return []; jump continue; return []; }
After optimizing:
list _() { integer loc_n = 3; @J_autoGen00001; llOwnerSay((string)loc_n); --loc_n; if (!loc_n) return []; jump J_autoGen00001; // Note that return []; was removed here }
Even though the return [];
at the end of the original function is never reached because of the jump statement, LSL requires it in order to compile.
My code is using llChar to convert numbers to chars that are outside of what the compiler can handle. This causes error: invalid character '\002' in input stream
when the optimizer tries to convert it to a string.
Example: llChar( 2)
gets replaced with "�" which causes the error.
Hi there, there's a few new LSL functions that are missing:
llLinksetDataDeleteFound
llLinksetDataCountFound
patch attached
0001-Add-missing-code-to-support-postarg.patch.txt
Something like
ListAction([a,b], c);
results in a too many arguments error from the preprocessor when the macro is invoked, and the line is skipped. In the case of vectors and rotations this can probably be worked around by splitting the vector or rotation over 3 or 4 arguments, but in the case of a list with a variable number of arguments, no such workaround is feasible.
Would it be possible to massage the input to the preprocessor by replacing CSV formatted values, specifically lists, rotations, and vectors with, say, otherwise unlikely to occur, appropriately formatted strings, then restoring the original values on return from the preprocessor? Something like:
[a, [b], "c]", ""d""] <-> "pYoPtLiSt = [a, [b], "c]", "\"d\""]"
I believe that this would allow macros to handle such items gracefully with a minimum of effort and undesirable side effects.
(except for adding a backslash to embedded quotes and doubling the number of backslashes in ..." sequences, I don't think other character values would require special handling. Specifically, vectors and rotations inside lists should not need to be pre-preprocessed separately.)
I'm not familiar enough with mcpp and its ilk to be sure, but an alternative to the above string method might be simply to temporarily replace the commas in such values with another unique delimiter, escaping or doubling the delimiter if it occurs elsewhere in the value. Eg:
[a, [b], "c]", "de"] <-> [a
[b]"c]"
"de"] or [a, [b], "c]", "d
e"] <-> [a [b]
"c]"` "d``e"]
In the escaped case, it would be necessary to deal with ...sequences within the original value appropriately. However, in the doubled delimiter case,
...` sequences would not require separate handling.
Hi there,
on one script I tried to optimize, I get these kind of errors (one per each attempt, the numbers seem to be arbitrarily changing, the 2912 shows up often though):
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 4295: ordinal not in range(128)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 200: ordinal not in range(128)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 2912: ordinal not in range(128)
I tested my build process with a different script, and it ran fine, so I assume it's something in my current script the optimizer stumbles across.
My current build process:
VSCode Build Task macro:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "optimize",
"type": "shell",
"command": "/usr/bin/python ${userHome}/GitHub/LSL-PyOptimizer/main.py ${workspaceFolder}/Optimized/preprocessed.lsl -O +ShrinkNames -o ${workspaceFolder}/Optimized/optimized.lsl ",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
Test case:
string a = "x";
list b = [a];
default
{
timer()
{
b += "";
}
}
Output:
Traceback (most recent call last):
File "main.py", line 518, in <module>
ret = main()
File "main.py", line 501, in main
script = script_header + outs.output(ts, options)
File "<path redacted>/lslopt/lsloutput.py", line 441, in output
ret += self.OutCode(node)
File "<path redacted>/lslopt/lsloutput.py", line 385, in OutCode
ret += ' = ' + self.OutExpr(child[0]['orig'])
File "<path redacted>/lslopt/lsloutput.py", line 270, in OutExpr
ret = '[' + self.OutExprList(child) + ']'
File "<path redacted>/lslopt/lsloutput.py", line 193, in OutExprList
ret += self.OutExpr(item)
File "<path redacted>/lslopt/lsloutput.py", line 253, in OutExpr
return self.FindName(expr)
File "<path redacted>/lslopt/lsloutput.py", line 167, in FindName
if 'scope' in node and 'NewName' in self.symtab[node['scope']][node['name']]:
KeyError: 'a'
The reason is that the output handler tries to output the original expression for that list element, which is kept in 'orig', when the symbol has already been deleted and removed from the symbol table.
The fix is complicated, and will probably involve getting rid of the 'orig' hack and finding a more reliable solution for using key variables inside lists in globals.
I have the following snippet.lsl
that causes the above error.
integer menuChannel = 1;
showDialog(string user, string description, list buttons) {
llDialog(user, description, buttons, menuChannel);
}
default {
state_entry() {
string pageString = " ";
list options = [];
showDialog(llGetOwner(), "Test", ["◀" + " Prev" + pageString, "▼" + " Back", "▶" + " Next" + pageString] + llList2List(options, page * 9, page * 9 + 8);
}
}
I run python ./optimizer/main.py -H -O addstrings,-extendedglobalexpr,optsigns,optfloats .\snippet.lsl -o .\snippet.lslo
And here's a traceback with -y.
Traceback (most recent call last):
File "./optimizer/main.py", line 661, in <module>
ret = main(sys.argv)
File "./optimizer/main.py", line 630, in main
ReportError(script, e)
File "./optimizer/main.py", line 36, in ReportError
sys.stderr.write(script[fieldpos(script, '\n', e.lno):lastpos].decode('utf8') + u"\n")
File "C:\Python27\lib\encodings\cp850.py", line 12, in encode
return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u25c0' in position 43: character maps to <undefined>
The line that causes the error is showDialog(llGetOwner(), "Test", ["◀" + " Prev" + pageString, "▼" + " Back", "▶" + " Next" + pageString] + llList2List(options, page * 9, page * 9 + 8);
More specifically, I'm missing a closing parenthesis.
I'm on a Windows 10 machine, if that matters 👍
At the beginning of an inlined function, all parameters get assigned to new local variables even if they are never written to and therefore could be used without creating a copy.
An example with the default settings + Manual function inlining:
foo(integer num) inline {
llSay(num, "foo");
}
default {
touch_start(integer num) {
foo(num);
}
}
Gets turned into:
default
{
touch_start(integer LslLibrary)
{
{
integer loc_num = LslLibrary;
{
llSay(loc_num, "foo");
}
}
}
}
There does not seem to be a reason to create loc_num in this case.
This is likely a Python 2.7 thing, but this optimizer transforms something like this:
["⑬","①","⑲","⑨","⑩"]
Into this:
[ "⑬", "①", "⑲", "⑨", "⑩"]
Making this tool unfortunately unusable if any Unicode characters are present at all, it seems.
First of all, thank you for LSL-PyOptimizer, it's clearly been thoughtfully designed and written!
I'm putting these as one issue since they're all relatively small and are closely related, but let me know if you'd prefer I split them up. For reference, they're all based on assertions from LL's LSL Language Test 2.
llOwnerSay((string)((float)"-0.0"));
is optimized to llOwnerSay("0.000000");
, but SL outputs -0.000000
.
I think the sign bit is lost due to the denormal handling at
LSL-PyOptimizer/lslopt/lslbasefuncs.py
Line 508 in c29475d
math.copysign()
may be needed if rounding to 0.0
is correct there.
llOwnerSay((string)(1.4e-45 == (float)"1.4e-45"));
is optimized to llOwnerSay("0");
, but SL outputs 1
. I think this is also related to the above handling of denormals in string->float
conversions, but I'm not sure what the correct fix would be.
llOwnerSay((string)0.00000000000001);
fails to compile and an IndexError
is raised at
LSL-PyOptimizer/lslopt/lslbasefuncs.py
Line 391 in c29475d
It looks like this may be an issue with any float small enough that the string representation is 0.000[...]
, but is not actually equal to 0.0
.
When attempting to use the NAK constant to detect uncached notecards with llGetNotecardLineSync () in a script, LSL-PO replaces the NAK constant with a string consisting of "\n^U\n", (i.e., %0A%15%0A).
The Firestorm compiler then produces the following error:
error: generic lexer error: invalid character '\025' in input stream
when it encounters the CTRL-U.
Hi Sei Lisa,
for me (windows/firefox) it would be nice if the Optimizer output would be placed inside a textarea or to have an option to get the raw optimizer output. With both variants I would be able to mark the complete output (windows: ctrl+A) and copy it to the clipboard. With the current version I have to mark the optimizer output manually (a little boring with larger scripts).
Greetings
Leona
For some reason I have only been able to reproduce it in this particular script:
https://pastebin.com/2NR7Dgam
Line 153: runMethod((string)LINK_ROOT, "got Status", 1, (list)("")+ ((list)1 + 3 + (integer)(-cost*100) + "" + 0), JSON_INVALID);
When optimized becomes: D("1", "got Status", 1, (list)1 + 3 + (integer)(-loc_cost * 100) + "" + 0, "�");
As you can see it starts with (list)1+3...
when it should be (list)"" + 1 +3...
I am using the Firestorm preprocessor conditional defines quite extensively. So my workflow looks like this:
It would be nice if that workflow could be reduced a bit if PyOptimize would recognize the FS conditional defines.
Sometimes I disable ConstFold
to maintain readability of if
/ else
chains, but it appears that causes issues when you have an SAIdentifier
on the RHS of a global's declaration.
Given the script
integer FOO=1;
integer BAR=FOO;
default {
state_entry() {
llOwnerSay((string)BAR);
}
}
A llOwnerSay("1");
will be correctly emitted when ConstFold
is enabled, but when it's disabled an exception is raised:
Traceback (most recent call last):
File "/home/user/source/LSL-PyOptimizer/main.py", line 779, in <module>
ret = main(sys.argv)
File "/home/user/source/LSL-PyOptimizer/main.py", line 742, in main
script = script_header + script_timestamp + outs.output(ts, options)
File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 556, in output
ret += self.OutCode(node)
File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 483, in OutCode
ret += ' = ' + self.OutExpr(child[0])
File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 305, in OutExpr
return self.FindName(expr)
File "/home/user/source/LSL-PyOptimizer/lslopt/lsloutput.py", line 186, in FindName
and 'NewName' in self.symtab[node.scope][node.name]):
KeyError: 'FOO'
Seems like FOO
isn't in the symbol table for its scope at the point the RHS of BAR
's declaration is being handled?
The following code:
default
{
link_message(integer p_val, integer notUsed1, string notUsed2, key notUsed3)
{
float pivot_x = 0.0;
float pivot_y = -0.65;
float theta = 0.01 * p_val;
float x = pivot_x * llCos(theta) - pivot_y * llSin(theta);
float y = pivot_x * llSin(theta) + pivot_y * llCos(theta);
llOwnerSay("Pivot: " + (string)x + ", " + (string)y);
}
}
Optimizes to:
default
{
link_message(integer p_val, integer notUsed1, string notUsed2, key notUsed3)
{
float theta = 0.01 * p_val;
float x = --0.65 * llSin(theta);
float y = -0.65 * llCos(theta);
llOwnerSay("Pivot: " + (string)x + ", " + (string)y);
}
}
--0.65
is a syntax error
Using options: Constant folding, Dead code removal, but with Optimize signs disabled.
Because of pivot_x being 0, the formula collapses in to:
x = 0 - y * llSin(θ)
x = 0 - (-0.65 * sin(θ))
x = -(-0.65 * sin(θ))
but since the optimizer does not use parentheses, it becomes a --
operator.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.