From 6b5cf17f1f6d4a5227b007199cb49d16f6541474 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:40:05 -0400 Subject: [PATCH 01/59] The optimizer keeps a pure call made again in a local, where that is 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 --- indra/alscript/allsloptimizer.cpp | 490 ++++++++++++++++-- indra/alscript/tests/allsloptimizer_test.cpp | 49 ++ .../newview/skins/default/xui/en/strings.xml | 1 + 3 files changed, 501 insertions(+), 39 deletions(-) diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index 8f7a20ef53..c0ae2aa748 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -52,7 +52,9 @@ #include #include +#include #include +#include #include #include #include @@ -3697,6 +3699,399 @@ namespace } }; + // A pure library call made more than once in a straight run of a + // block's statements, given only what nothing in the run changes: made + // once, into a local declared before the run, which each place then + // reads -- where the target's compiler says that is no larger, the call + // at each place weighed against the local. The run holds no label, which + // a jump could bring the code in by past the local; and its first place + // is one its statement always comes to, so that the call runs no more + // often than it did. It goes ahead of that statement, which changes + // nothing it is given. + class RepeatedCalls : public ASTVisitor, public Pass + { + public: + RepeatedCalls(Ctx& c, Report& r, const ALLSLOptimizer::Options& o, LSLScript* script) : Pass(c, r, o) + { + // Every name the script writes, which a local's may be none of: + // LSL has no shadowing. + each(script, [this](LSLASTNode* n) { + if (n->getNodeType() == NODE_IDENTIFIER && static_cast(n)->getName()) + { + mTaken.insert(static_cast(n)->getName()); + } + }); + } + + // The block's own runs first: a local before one of its statements + // holds the places in the blocks that statement holds too. + bool visit(LSLCompoundStatement* block) override + { + while (keepOne(block)) + { + } + visitChildren(block); + return false; + } + + private: + // Each call that may be kept, by what it is: its text and the + // variables it reads, in the order it reads them. + struct Group + { + std::vector reads; + std::vector> places; + size_t length = 0; + }; + + template static void each(LSLASTNode* root, const F& f) + { + std::vector stack{ root }; + while (!stack.empty()) + { + LSLASTNode* node = stack.back(); + stack.pop_back(); + if (!node) + { + continue; + } + f(node); + for (LSLASTNode* child = node->getChild(0); child; child = child->getNext()) + { + stack.push_back(child); + } + } + } + + static bool keepable(LSLFunctionExpression* call) + { + LSLSymbol* sym = call->getSymbol(); + if (!sym || sym->getSubType() != SYM_BUILTIN || !isPure(sym->getName()) || !call->getArguments() || !call->getArguments()->hasChildren()) + { + return false; + } + switch (call->getIType()) + { + case LST_INTEGER: + case LST_FLOATINGPOINT: + case LST_STRING: + case LST_KEY: + case LST_VECTOR: + case LST_QUATERNION: + case LST_LIST: + return sideEffectFree(call); + default: + return false; + } + } + + static const char* typeName(LSLIType type) + { + switch (type) + { + case LST_INTEGER: return "integer"; + case LST_FLOATINGPOINT: return "float"; + case LST_STRING: return "string"; + case LST_KEY: return "key"; + case LST_VECTOR: return "vector"; + case LST_QUATERNION: return "rotation"; + case LST_LIST: return "list"; + default: return nullptr; + } + } + + // Whether a statement's own evaluation always comes to a node in + // it: through expressions alone, from an expression statement, a + // declaration's value, a return, or what an if, a while or a for + // looks at first. + static bool always(LSLASTNode* node, LSLASTNode* statement) + { + LSLASTNode* child = node; + for (LSLASTNode* up = node->getParent(); up; child = up, up = up->getParent()) + { + if (up == statement) + { + switch (statement->getNodeSubType()) + { + case NODE_EXPRESSION_STATEMENT: + case NODE_DECLARATION: + case NODE_RETURN_STATEMENT: + return true; + case NODE_IF_STATEMENT: + case NODE_WHILE_STATEMENT: + return child == statement->getChild(0); + case NODE_FOR_STATEMENT: + // Its first part, and its check, which runs once + // at the least. + return child == statement->getChild(0) || child == statement->getChild(1); + default: + return false; + } + } + if (up->getNodeType() != NODE_EXPRESSION && up->getNodeType() != NODE_AST_NODE_LIST) + { + return false; + } + } + return false; + } + + static bool holdsLabel(LSLASTNode* root) + { + bool found = false; + each(root, [&found](LSLASTNode* n) { found = found || (n->getNodeType() == NODE_STATEMENT && n->getNodeSubType() == NODE_LABEL); }); + return found; + } + + // A name of the script's own for the local: the function's without + // its ll, a length's `length`, numbered where it is taken. + std::string fresh(const char* function) + { + const std::string_view called = function; + std::string base = called == "llGetListLength" || called == "llStringLength" ? std::string("length") : std::string(called.substr(2)); + base[0] = static_cast(std::tolower(static_cast(base[0]))); + const auto usable = [&](const std::string& name) { + return !mTaken.contains(name) && ALScriptLexicon::lslWord(name) == ALScriptLexicon::LSL_NAME && + !(ctx.context->builtins && ctx.context->builtins->lookup(name.c_str(), SYM_ANY)); + }; + std::string name = base; + for (int n = 2; !usable(name); ++n) + { + name = base + std::to_string(n); + } + mTaken.insert(name); + return name; + } + + // Whether the local is no larger than the call at `places` places, + // each compiled in a function of its own given what the call reads. + bool smaller(LSLFunctionExpression* call, const std::vector& reads, size_t places) + { + const char* type = typeName(call->getIType()); + std::string given; + std::vector seen; + for (LSLSymbol* sym : reads) + { + if (sym->getSubType() == SYM_BUILTIN || std::find(seen.begin(), seen.end(), sym) != seen.end()) + { + continue; + } + const char* its = typeName(sym->getIType()); + if (!its) + { + return false; + } + seen.push_back(sym); + given += std::string(given.empty() ? "" : ", ") + its + " " + sym->getName(); + } + const std::string text = render(call); + const std::string head = "keptCall(" + given + ")\n{\n"; + const std::string tail = "}\ndefault\n{\n state_entry()\n {\n }\n}\n"; + std::string asCalls = head; + std::string asLocal = head + " " + type + " kept = " + text + ";\n"; + for (size_t i = 0; i < places; ++i) + { + asCalls += " llOwnerSay((string)(" + text + "));\n"; + asLocal += " llOwnerSay((string)kept);\n"; + } + asCalls += tail; + asLocal += tail; + const std::string key = "kept\n" + asCalls; + if (const auto found = ctx.answers.find(key); found != ctx.answers.end()) + { + return found->second; + } + const ALScriptWeight byCalls = weigh(ctx.target, asCalls); + const ALScriptWeight byLocal = weigh(ctx.target, asLocal); + const bool no = byCalls.compiled && byLocal.compiled && byLocal.total <= byCalls.total; + ctx.answers.emplace(key, no); + return no; + } + + // The first call worth keeping in the block kept; whether there was one. + bool keepOne(LSLCompoundStatement* block) + { + std::vector statements; + for (LSLASTNode* stmt : *block) + { + statements.push_back(stmt); + } + std::vector groups; + boost::unordered_flat_map> index; + for (size_t i = 0; i < statements.size(); ++i) + { + each(statements[i], [&](LSLASTNode* n) { + if (n->getNodeType() != NODE_EXPRESSION || n->getNodeSubType() != NODE_FUNCTION_EXPRESSION || + !keepable(static_cast(n))) + { + return; + } + auto* call = static_cast(n); + std::vector reads; + each(call, [&reads](LSLASTNode* m) { + if (m->getNodeType() == NODE_EXPRESSION && m->getNodeSubType() == NODE_LVALUE_EXPRESSION) + { + reads.push_back(m->getSymbol()); + } + }); + const std::string text = render(call); + std::string key = text; + for (LSLSymbol* sym : reads) + { + key += "\n" + std::to_string(reinterpret_cast(sym)); + } + auto [at, fresh] = index.try_emplace(key, groups.size()); + if (fresh) + { + groups.push_back(Group{ std::move(reads), {}, text.size() }); + } + groups[at->second].places.emplace_back(i, call); + }); + } + // The outermost first: a call kept takes with it the calls it is + // given, which would otherwise be kept apart. + std::vector order; + for (Group& g : groups) + { + if (g.places.size() > 1) + { + order.push_back(&g); + } + } + if (order.empty()) + { + return false; + } + std::stable_sort(order.begin(), order.end(), [](const Group* a, const Group* b) { return a->length > b->length; }); + std::vector writes(statements.size()); + std::vector labelled(statements.size()); + for (size_t i = 0; i < statements.size(); ++i) + { + writes[i] = ctx.effects->of(statements[i]); + labelled[i] = holdsLabel(statements[i]); + } + for (Group* g : order) + { + // Runs of statements that change nothing the call reads, each + // begun where a place is always come to. + size_t open = std::string::npos; + LSLFunctionExpression* first = nullptr; + std::vector held; + const auto close = [&]() { + const bool kept = held.size() > 1 && smaller(first, g->reads, held.size()); + if (kept) + { + keep(block, statements[open], first, held); + } + open = std::string::npos; + held.clear(); + return kept; + }; + size_t next = 0; + for (size_t i = 0; i < statements.size(); ++i) + { + const bool barred = labelled[i] || std::any_of(g->reads.begin(), g->reads.end(), [&](LSLSymbol* s) { return writes[i].writes(s); }); + std::vector here; + while (next < g->places.size() && g->places[next].first == i) + { + here.push_back(g->places[next++].second); + } + if (barred) + { + if (close()) + { + return true; + } + continue; + } + if (here.empty()) + { + continue; + } + if (open == std::string::npos) + { + const auto starts = std::find_if(here.begin(), here.end(), [&](LSLFunctionExpression* c) { return always(c, statements[i]); }); + if (starts == here.end()) + { + continue; + } + open = i; + first = *starts; + } + held.insert(held.end(), here.begin(), here.end()); + } + if (close()) + { + return true; + } + } + return false; + } + + // `first`'s call made the value of a local declared before `before`, + // and each place held made a read of it. + void keep(LSLCompoundStatement* block, LSLASTNode* before, LSLFunctionExpression* first, const std::vector& held) + { + LSLType* type = first->getType(); + const std::string local = fresh(first->getSymbol()->getName()); + const std::string said = report.wanted() ? render(first) : std::string(); + const char* name = ctx.allocator->copyStr(local.c_str()); + auto* id = ctx.allocator->newTracked(type, name); + id->setLoc(first->getLoc()); + auto* decl = ctx.allocator->newTracked(id, static_cast(nullptr)); + decl->setLoc(first->getLoc()); + auto* sym = ctx.allocator->newTracked(name, type, SYM_VARIABLE, SYM_LOCAL, first->getLoc(), nullptr, decl); + id->setSymbol(sym); + for (LSLFunctionExpression* place : held) + { + auto* read = ctx.allocator->newTracked(type, name); + read->setSymbol(sym); + read->setLoc(place->getLoc()); + auto* lvalue = ctx.allocator->newTracked(read, static_cast(nullptr)); + lvalue->setType(type); + lvalue->setLoc(place->getLoc()); + lvalue->setIsFoldable(true); + LSLASTNode::replaceNode(place, lvalue); + } + decl->setChild(1, first); + { + // The statements are the ones they were: counted neither out + // nor in again. The references are found again after the pass. + const Uncounted uncounted(*ctx.context); + std::vector statements; + for (LSLASTNode* stmt : *block) + { + statements.push_back(stmt); + } + for (LSLASTNode* stmt : statements) + { + block->removeChild(stmt); + } + for (LSLASTNode* stmt : statements) + { + if (stmt == before) + { + block->pushChild(decl); + } + block->pushChild(stmt); + } + } + if (LSLSymbolTable* table = block->getSymbolTable()) + { + table->define(sym); + } + else + { + decl->defineSymbol(sym); + } + report.note(first->getLoc(), "OptimizerKeptCall", "kept [1] in the local [2], which its [3] places read", + { said, local, std::to_string(held.size()) }); + ++changes; + } + + boost::unordered_flat_set> mTaken; + }; + // What can never run, and what is never used. class DeadCode : public ASTVisitor, public Pass { @@ -5751,51 +6146,68 @@ namespace propagate(); walks(2); }; - for (int round = 0; round < 64; ++round) - { - // No round begun that the budget cannot see through its passes. - if (visited + perWalk * 3 > options.visitBudget) - { - result.stoppedEarly = true; - report.note(nullptr, "OptimizerStoppedEarly", "stopped after [1] rounds: there may be more to do", { std::to_string(round) }); - break; - } - // The references and values found again after the folder and the - // simplifier together, not after each: what the folder makes are - // constants, whose values the simplifier reads off them, and what - // counts it leaves behind can only be too high -- which a pass - // takes as a reason to do less, never as leave to do wrong. Dead - // code is looked for with everything found again, since what - // runs is told from the values. - int changes = 0; - if (options.constfold) - { - Folder folder(ctx, report, options); - script->visit(&folder); - walks(1); - Simplifier simplifier(ctx, report, options); - script->visit(&simplifier); - walks(1); - if (folder.changes + simplifier.changes) + const auto settle = [&]() { + for (int round = 0; round < 64; ++round) + { + // No round begun that the budget cannot see through its passes. + if (visited + perWalk * 3 > options.visitBudget) { - changes += folder.changes + simplifier.changes; - refresh(); + result.stoppedEarly = true; + report.note(nullptr, "OptimizerStoppedEarly", "stopped after [1] rounds: there may be more to do", { std::to_string(round) }); + break; } - } - if (options.dcr) - { - DeadCode dead(ctx, report, options); - const int removed = dead.run(script); - walks(1); - if (removed) + // The references and values found again after the folder and the + // simplifier together, not after each: what the folder makes are + // constants, whose values the simplifier reads off them, and what + // counts it leaves behind can only be too high -- which a pass + // takes as a reason to do less, never as leave to do wrong. Dead + // code is looked for with everything found again, since what + // runs is told from the values. + int changes = 0; + if (options.constfold) + { + Folder folder(ctx, report, options); + script->visit(&folder); + walks(1); + Simplifier simplifier(ctx, report, options); + script->visit(&simplifier); + walks(1); + if (folder.changes + simplifier.changes) + { + changes += folder.changes + simplifier.changes; + refresh(); + } + } + if (options.dcr) + { + DeadCode dead(ctx, report, options); + const int removed = dead.run(script); + walks(1); + if (removed) + { + changes += removed; + refresh(); + } + } + if (!changes) { - changes += removed; - refresh(); + break; } } - if (!changes) + }; + settle(); + // A call made again and again kept in a local, once nothing more + // will fold; and the rounds again where one was, for what the + // places it leaves open. + if (options.constfold && !result.stoppedEarly) + { + RepeatedCalls repeated(ctx, report, options, script); + script->visit(&repeated); + walks(1); + if (repeated.changes) { - break; + refresh(); + settle(); } } // What is made for size alone, once nothing more will fold. diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 9bea916508..8b1d2a43ac 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -1933,4 +1933,53 @@ namespace tut const ALScriptWeight asLso = ALScriptWeigh::mono(ALLSLOptimizer::run(source, plain).text); ensure("smaller on Mono than named for LSO", named.compiled && asLso.compiled && named.total < asLso.total); } + + template<> template<> + void allsloptimizer_object::test<51>() + { + set_test_name("a pure call made again, given what nothing between changes, is kept in a local where the target has that no larger (L12); " + "not across a write or a label, nor begun where its statement may not come to it"); + ALLSLOptimizer::Options lso = options(); + lso.target = ALLSLOptimizer::Target::LSO; + const std::string head = "default\n{\n touch_start(integer n)\n {\n string s = llDetectedName(0);\n"; + const std::string tail = " }\n}\n"; + const auto body = [&](const ALLSLOptimizer::Result& r) { + const size_t at = r.text.find(" string s = llDetectedName(0);\n"); + return at == std::string::npos ? r.text : r.text.substr(at + 38); + }; + + const std::string three = head + " llOwnerSay(llToUpper(s));\n llSay(0, llToUpper(s));\n llWhisper(0, llToUpper(s));\n" + tail; + ALLSLOptimizer::Result r = ALLSLOptimizer::run(three, lso); + ensure("optimized: " + notes(r), r.optimized); + ensure_equals("kept", body(r), " string toUpper = llToUpper(s);\n llOwnerSay(toUpper);\n llSay(0, toUpper);\n llWhisper(0, toUpper);\n" + tail); + ensure("noted: " + notes(r), has(r, "kept llToUpper(s) in the local toUpper, which its 3 places read")); + // On Mono a call given one variable is smaller than a local. + r = ALLSLOptimizer::run(three, options()); + ensure("not on Mono: " + r.text, r.text.find("toUpper") == std::string::npos); + + // A place in a loop after one its statement always comes to. + r = ALLSLOptimizer::run(head + " llSay(0, llToUpper(s));\n integer i;\n for (i = 0; i < n; ++i)\n llSay(i, llToUpper(s));\n" + tail, lso); + ensure("into the loop: " + r.text, r.text.find("string toUpper = llToUpper(s);\n llSay(0, toUpper);") != std::string::npos && + r.text.find("llSay(i, toUpper);") != std::string::npos); + + // The outermost call, and the call it is given with it. + r = ALLSLOptimizer::run(head + " llSay(0, llGetSubString(llToUpper(s), 0, 2));\n llSay(1, llGetSubString(llToUpper(s), 0, 2));\n" + tail, lso); + ensure_equals("outermost", body(r), + " string getSubString = llGetSubString(llToUpper(s), 0, 2);\n llSay(0, getSubString);\n llSay(1, getSubString);\n" + tail); + + // A name the script has is not taken again. + r = ALLSLOptimizer::run(head + " string toUpper = llDetectedName(1);\n llSay(0, toUpper);\n llSay(0, llToUpper(s));\n llSay(1, llToUpper(s));\n" + tail, lso); + ensure("numbered: " + r.text, r.text.find("string toUpper2 = llToUpper(s);") != std::string::npos); + + // What it is given written between: two places, each alone. + const std::string written = head + " llSay(0, llToUpper(s));\n s = llDetectedName(1);\n llSay(0, llToUpper(s));\n llSay(0, s);\n" + tail; + r = ALLSLOptimizer::run(written, lso); + ensure("not across a write: " + r.text, r.text.find("toUpper") == std::string::npos); + // A label between, which a jump could bring the code in by. + r = ALLSLOptimizer::run(head + " llSay(0, llToUpper(s));\n @again;\n llSay(0, llToUpper(s));\n if (llFrand(1) < 0.5)\n jump again;\n" + tail, lso); + ensure("not across a label: " + r.text, r.text.find("toUpper") == std::string::npos); + // The first place in a branch: kept, it would run where it did not. + r = ALLSLOptimizer::run(head + " if (n)\n llSay(0, llToUpper(s));\n llSay(0, llToUpper(s));\n" + tail, lso); + ensure("not begun in a branch: " + r.text, r.text.find("toUpper") == std::string::npos); + } } // namespace tut diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 3abe215c0e..4817659aa5 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -635,6 +635,7 @@ If you feel this is an error, please contact support@secondlife.com the condition of this if is always false; kept only what runs the condition of this if is always true; kept only what runs inlined [1] to [2] + kept [1] in the local [2], which its [3] places read the LSL definitions are not loaded, so nothing was optimized not optimized: the script does not compile as it stands, and compiling it says why not optimized: what the optimizer made of this script did not compile ([1]), so it goes as it was; please report it From 15335326b5b5e35814c42c1ceced8b00b7e2d24c Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:40:51 -0400 Subject: [PATCH 02/59] Two vectors' dot and cross products fold to what the VM gives 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 --- indra/alscript/allslvalues.cpp | 42 ++++++++++++++++++++ indra/alscript/tests/allsloptimizer_test.cpp | 22 ++++++++++ 2 files changed, 64 insertions(+) diff --git a/indra/alscript/allslvalues.cpp b/indra/alscript/allslvalues.cpp index 95e2ff861f..5c019a6ee9 100644 --- a/indra/alscript/allslvalues.cpp +++ b/indra/alscript/allslvalues.cpp @@ -106,6 +106,48 @@ LSLConstant* ALLSLArithmetic::operation(LSLOperator op, LSLConstant* cv, LSLCons break; } } + // Two vectors' dot and cross products, which Tailslide's arithmetic has + // wrong: its dot takes each part by the other's opposite part, x by z, + // and its cross is a product less a product that a compiler may fuse, + // where the VM rounds each. Worked out as a single does it, a step at a + // time, and as a double rounded once, and folded only where the two + // agree. + if (other && cv->getIType() == LST_VECTOR && other->getIType() == LST_VECTOR && (op == OP_MUL || op == OP_MOD)) + { + const Vector3* u = static_cast(cv)->getValue(); + const Vector3* v = static_cast(other)->getValue(); + if (!u || !v) + { + return nullptr; + } + // One operation of singles in double, which is exact enough, rounded + // once: what a single's own operation gives. + const auto mul = [](double a, double b) { return static_cast(static_cast(a * b)); }; + const auto add = [](double a, double b) { return static_cast(static_cast(a + b)); }; + const auto sub = [](double a, double b) { return static_cast(static_cast(a - b)); }; + const auto agreed = [](double stepped, double once, double& out) { + out = stepped; + return std::isfinite(stepped) && static_cast(static_cast(once)) == stepped; + }; + const double ux = u->x, uy = u->y, uz = u->z; + const double vx = v->x, vy = v->y, vz = v->z; + if (op == OP_MUL) + { + double dot = 0.0; + if (!agreed(add(add(mul(ux, vx), mul(uy, vy)), mul(uz, vz)), ux * vx + uy * vy + uz * vz, dot)) + { + return nullptr; + } + return _mAllocator->newTracked(dot); + } + double x = 0.0, y = 0.0, z = 0.0; + if (!agreed(sub(mul(uy, vz), mul(uz, vy)), uy * vz - uz * vy, x) || !agreed(sub(mul(uz, vx), mul(ux, vz)), uz * vx - ux * vz, y) || + !agreed(sub(mul(ux, vy), mul(uy, vx)), ux * vy - uy * vx, z)) + { + return nullptr; + } + return _mAllocator->newTracked(static_cast(x), static_cast(y), static_cast(z)); + } return rounded(TailslideOperationBehavior::operation(op, cv, other, lloc)); } diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 8b1d2a43ac..d97a29c75f 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -1982,4 +1982,26 @@ namespace tut r = ALLSLOptimizer::run(head + " if (n)\n llSay(0, llToUpper(s));\n llSay(0, llToUpper(s));\n" + tail, lso); ensure("not begun in a branch: " + r.text, r.text.find("toUpper") == std::string::npos); } + + template<> template<> + void allsloptimizer_object::test<52>() + { + set_test_name("two vectors' dot and cross products fold to what the VM gives: each part by the same part, and a vector by itself crossed to nothing"); + // Each may be set again before it is read, so that its fold shows + // in its declaration. + const std::string source = wrap("", " float d = <1, 3, -5> * <4, -2, -1>;\n vector c = <1.0, 2.0, 3.0> % <4.0, 5.0, 6.0>;\n" + " vector z = <44608876.0, 125129528.0, 357885664.0> % <44608876.0, 125129528.0, 357885664.0>;\n" + " if (llFrand(1) < 0.5)\n {\n d = 0;\n c = z = ZERO_VECTOR;\n }\n" + " llSay(0, (string)d + (string)c + (string)z);\n"); + for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::LSO }) + { + ALLSLOptimizer::Options o = options(); + o.target = target; + const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); + ensure("optimized: " + notes(r), r.optimized); + ensure("dot: " + r.text, about(numbers(r.text, "float d"), { 3.f })); + ensure("cross: " + r.text, about(numbers(r.text, "vector c"), { -3.f, 6.f, -3.f })); + ensure("crossed by itself: " + r.text, about(numbers(r.text, "vector z"), { 0.f, 0.f, 0.f })); + } + } } // namespace tut From d8f8440e951d08a54498e21a34f98c5748a7ceab Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:42:00 -0400 Subject: [PATCH 03/59] A local set to a constant is that constant where it is read before it 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 --- indra/alscript/allsloptimizer.cpp | 503 ++++++++++++++++++- indra/alscript/tests/allsloptimizer_test.cpp | 68 ++- 2 files changed, 554 insertions(+), 17 deletions(-) diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index c0ae2aa748..b4ae46e4dc 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -1910,6 +1910,418 @@ namespace } }; + // Tailslide's values, and with them a local's or a parameter's through + // the code that runs on from where it was set to a constant: read + // before it is set again, on every way there, it is that constant -- + // where Tailslide gives one only to a variable never set after it is + // declared. What is known goes with the code as it runs: a statement's + // reads see it as the statement begins and its writes change it as it + // ends, and a read that something in the same statement may write + // first -- LSL runs a binary operator's right side before its left -- + // sees nothing. Both ways of an if are followed, and what they agree on + // goes on; a loop forgets what it writes before it begins, since it may + // come round again; and a label forgets everything, since a jump may + // come in from anywhere -- a loop holding one, everything before it + // begins. Lists are left alone, as Tailslide leaves them; and a local + // declared with no value is known only once it is set. + class FlowValues : public ConstantDeterminingVisitor + { + public: + // `vectors` where a vector's or a rotation's literal is no larger + // than what it is worked out from: not on Luau, which builds one + // where LSO and Mono load it. + FlowValues(AOperationBehavior* behavior, ScriptAllocator* allocator, const ALLSLEffects& effects, bool vectors) + : ConstantDeterminingVisitor(behavior, allocator), mEffects(effects), mVectors(vectors) + { + } + + bool beforeDescend(LSLASTNode* node) override + { + if (!ConstantDeterminingVisitor::beforeDescend(node)) + { + if (node->getNodeType() == NODE_STATEMENT || node->getNodeType() == NODE_EXPRESSION) + { + forget(mEffects.of(node)); + } + return false; + } + switch (node->getNodeType()) + { + case NODE_GLOBAL_FUNCTION: + case NODE_EVENT_HANDLER: + mState = State{}; + return true; + case NODE_STATEMENT: + statement(node); + return false; + default: + return true; + } + } + + bool visit(LSLLValueExpression* lvalue) override + { + ConstantDeterminingVisitor::visit(lvalue); + LSLSymbol* sym = lvalue->getSymbol(); + if (!sym || sym->getAssignments() == 0 || !tracked(sym) || !mState.reachable || written(lvalue)) + { + return true; + } + const auto found = mState.known.find(sym); + if (found == mState.known.end() || (mHot && mHot->writes(sym) && !readFirst(lvalue, sym))) + { + return true; + } + LSLConstant* cv = found->second; + if (LSLIdentifier* member = lvalue->getMember()) + { + cv = part(cv, member->getName()); + } + lvalue->setConstantValue(cv); + return true; + } + + bool visit(LSLDeclaration* decl) override + { + ConstantDeterminingVisitor::visit(decl); + LSLSymbol* sym = decl->getSymbol(); + LSLASTNode* init = decl->getChild(1); + if (!sym || !tracked(sym)) + { + return false; + } + // The value Tailslide gave the symbol from what it was declared + // with, made the symbol's type. + LSLConstant* cv = init && init->getNodeType() != NODE_NULL ? sym->getConstantValue() : nullptr; + if (cv && mState.reachable) + { + mState.known[sym] = cv; + } + else + { + mState.known.erase(sym); + } + return false; + } + + private: + struct State + { + boost::unordered_flat_map known; + // False after a return, a jump or a change of state, until a label. + bool reachable = true; + }; + + bool tracked(LSLSymbol* sym) const + { + if (sym->getSymbolType() != SYM_VARIABLE || + (sym->getSubType() != SYM_LOCAL && sym->getSubType() != SYM_FUNCTION_PARAMETER && sym->getSubType() != SYM_EVENT_PARAMETER)) + { + return false; + } + switch (sym->getIType()) + { + case LST_INTEGER: + case LST_FLOATINGPOINT: + case LST_STRING: + case LST_KEY: + return true; + case LST_VECTOR: + case LST_QUATERNION: + return mVectors; + default: + return false; + } + } + + // What an assignment or an increment writes, not a read. + static bool written(LSLLValueExpression* lvalue) + { + LSLASTNode* parent = lvalue->getParent(); + return parent && parent->getNodeType() == NODE_EXPRESSION && operation_mutates(static_cast(parent)->getOperation()) && + parent->getChild(0) == lvalue; + } + + // Whether nothing of the expression a read is in that runs before it + // writes what it reads. + bool readFirst(LSLLValueExpression* lvalue, LSLSymbol* sym) const + { + for (LSLASTNode* earlier : ALLSLEffects::before(mRoot, lvalue)) + { + if (mEffects.of(earlier).writes(sym)) + { + return false; + } + } + return true; + } + + // A vector's or a rotation's part, as Tailslide reads one. + LSLConstant* part(LSLConstant* cv, const char* member) const + { + if (!member) + { + return nullptr; + } + if (cv->getIType() == LST_VECTOR) + { + const Vector3* v = static_cast(cv)->getValue(); + switch (member[0]) + { + case 'x': return _mAllocator->newTracked(v->x); + case 'y': return _mAllocator->newTracked(v->y); + case 'z': return _mAllocator->newTracked(v->z); + default: return nullptr; + } + } + if (cv->getIType() == LST_QUATERNION) + { + const Quaternion* q = static_cast(cv)->getValue(); + switch (member[0]) + { + case 'x': return _mAllocator->newTracked(q->x); + case 'y': return _mAllocator->newTracked(q->y); + case 'z': return _mAllocator->newTracked(q->z); + case 's': return _mAllocator->newTracked(q->s); + default: return nullptr; + } + } + return nullptr; + } + + static bool same(LSLConstant* a, LSLConstant* b) + { + if (a == b) + { + return true; + } + if (a->getNodeSubType() != b->getNodeSubType()) + { + return false; + } + // Bit for bit: -0.0 is not 0.0, and NaN is not itself. + const auto bits = [](double x, double y) { return x == y && std::signbit(x) == std::signbit(y); }; + switch (a->getNodeSubType()) + { + case NODE_INTEGER_CONSTANT: + return static_cast(a)->getValue() == static_cast(b)->getValue(); + case NODE_FLOAT_CONSTANT: + return bits(static_cast(a)->getValue(), static_cast(b)->getValue()); + case NODE_STRING_CONSTANT: + return !strcmp(static_cast(a)->getValue(), static_cast(b)->getValue()); + case NODE_KEY_CONSTANT: + return !strcmp(static_cast(a)->getValue(), static_cast(b)->getValue()); + case NODE_VECTOR_CONSTANT: + { + const Vector3* u = static_cast(a)->getValue(); + const Vector3* v = static_cast(b)->getValue(); + return bits(u->x, v->x) && bits(u->y, v->y) && bits(u->z, v->z); + } + case NODE_QUATERNION_CONSTANT: + { + const Quaternion* p = static_cast(a)->getValue(); + const Quaternion* q = static_cast(b)->getValue(); + return bits(p->x, q->x) && bits(p->y, q->y) && bits(p->z, q->z) && bits(p->s, q->s); + } + default: + return false; + } + } + + // What two ways that meet both know. + static State merged(const State& a, const State& b) + { + if (!a.reachable) + { + return b; + } + if (!b.reachable) + { + return a; + } + State out; + for (const auto& [sym, cv] : a.known) + { + const auto there = b.known.find(sym); + if (there != b.known.end() && same(cv, there->second)) + { + out.known.emplace(sym, cv); + } + } + return out; + } + + void forget(const ALLSLEffects::Writes& writes) + { + for (LSLSymbol* sym : writes.variables) + { + mState.known.erase(sym); + } + } + + static bool holdsLabel(LSLASTNode* root) + { + std::vector stack{ root }; + while (!stack.empty()) + { + LSLASTNode* n = stack.back(); + stack.pop_back(); + if (n->getNodeType() == NODE_STATEMENT && n->getNodeSubType() == NODE_LABEL) + { + return true; + } + for (LSLASTNode* child = n->getChild(0); child; child = child->getNext()) + { + stack.push_back(child); + } + } + return false; + } + + // A loop about to begin: what it writes forgotten, or everything + // where a jump could come into it. + void entering(LSLASTNode* loop, const ALLSLEffects::Writes& writes) + { + if (holdsLabel(loop)) + { + mState.known.clear(); + return; + } + forget(writes); + } + + // An expression run as one: its reads see what is known as it + // begins, and what it writes is known as it ends -- a variable set + // by the whole of it to a constant, that constant. + void expression(LSLASTNode* expr) + { + if (!expr || expr->getNodeType() == NODE_NULL) + { + return; + } + const ALLSLEffects::Writes writes = mEffects.of(expr); + LSLASTNode* const root = mRoot; + const ALLSLEffects::Writes* hot = mHot; + mRoot = expr; + mHot = &writes; + expr->visit(this); + mRoot = root; + mHot = hot; + LSLSymbol* set = nullptr; + LSLConstant* to = nullptr; + if (expr->getNodeType() == NODE_EXPRESSION && static_cast(expr)->getOperation() == OP_ASSIGN && + expr->getChild(0)->getNodeSubType() == NODE_LVALUE_EXPRESSION && !static_cast(expr->getChild(0))->getMember()) + { + set = expr->getChild(0)->getSymbol(); + LSLASTNode* value = expr->getChild(1); + to = set && !mEffects.of(value).writes(set) ? value->getConstantValue() : nullptr; + } + forget(writes); + if (set && to && tracked(set) && mState.reachable) + { + if (to->getType() != set->getType()) + { + to = to->getType()->canCoerce(set->getType()) ? _mOperationBehavior->cast(set->getType(), to, to->getLoc()) : nullptr; + } + if (to) + { + mState.known[set] = to; + } + } + } + + void statement(LSLASTNode* stmt) + { + switch (stmt->getNodeSubType()) + { + case NODE_COMPOUND_STATEMENT: + visitChildren(stmt); + return; + case NODE_EXPRESSION_STATEMENT: + expression(stmt->getChild(0)); + return; + case NODE_RETURN_STATEMENT: + expression(stmt->getChild(0)); + mState.reachable = false; + return; + case NODE_DECLARATION: + // Known as visit(LSLDeclaration*) says, once its value has run. + stmt->getChild(0)->visit(this); + expression(stmt->getChild(1)); + return; + case NODE_STATE_STATEMENT: + case NODE_JUMP_STATEMENT: + visitChildren(stmt); + mState.reachable = false; + return; + case NODE_LABEL: + visitChildren(stmt); + mState = State{}; + return; + case NODE_IF_STATEMENT: + { + expression(stmt->getChild(0)); + const State before = mState; + stmt->getChild(1)->visit(this); + const State yes = std::move(mState); + mState = before; + stmt->getChild(2)->visit(this); + mState = merged(yes, mState); + return; + } + case NODE_WHILE_STATEMENT: + { + entering(stmt, mEffects.of(stmt)); + expression(stmt->getChild(0)); + const State checked = mState; + stmt->getChild(1)->visit(this); + mState = merged(checked, mState); + return; + } + case NODE_DO_STATEMENT: + { + entering(stmt, mEffects.of(stmt)); + const State begun = mState; + stmt->getChild(0)->visit(this); + expression(stmt->getChild(1)); + mState = merged(begun, mState); + return; + } + case NODE_FOR_STATEMENT: + { + for (LSLASTNode* init = stmt->getChild(0)->getChild(0); init; init = init->getNext()) + { + expression(init); + } + ALLSLEffects::Writes loops = mEffects.of(stmt->getChild(1)); + loops.add(mEffects.of(stmt->getChild(2))); + loops.add(mEffects.of(stmt->getChild(3))); + entering(stmt, loops); + expression(stmt->getChild(1)); + const State checked = mState; + stmt->getChild(3)->visit(this); + for (LSLASTNode* step = stmt->getChild(2)->getChild(0); step; step = step->getNext()) + { + expression(step); + } + mState = merged(checked, mState); + return; + } + default: + visitChildren(stmt); + forget(mEffects.of(stmt)); + return; + } + } + + const ALLSLEffects& mEffects; + const bool mVectors; + State mState; + // The expression being run, and what it writes. + LSLASTNode* mRoot = nullptr; + const ALLSLEffects::Writes* mHot = nullptr; + }; + struct Pass { Ctx& ctx; @@ -2031,7 +2443,7 @@ namespace return false; } LSLConstant* cv = lvalue->getConstantValue(); - if (cv && inlineable(cv) && writeOut(sym, cv)) + if (cv && inlineable(cv) && writeOut(sym, cv) && (sym->getAssignments() == 0 || sym->getSubType() == SYM_GLOBAL || allKnown(lvalue, sym))) { fold(lvalue, cv, "OptimizerInlinedConstant", "inlined"); } @@ -2039,6 +2451,59 @@ namespace } private: + // Of a variable set more than once, whose reads have values where + // the code that runs to them set it to a constant (FlowValues): + // whether every read has one, so that with all of them written out + // the variable goes. Where one has none the variable stays, and a + // value written at a read alone is a literal in place of a read -- + // larger, for most -- unless what it is read in folds with it, + // which is that expression's fold, not this. + bool allKnown(LSLLValueExpression* lvalue, LSLSymbol* sym) + { + if (!mCounted) + { + mCounted = true; + std::vector stack{ lvalue->getRoot() }; + while (!stack.empty()) + { + LSLASTNode* n = stack.back(); + stack.pop_back(); + if (n->getNodeType() == NODE_EXPRESSION && n->getNodeSubType() == NODE_LVALUE_EXPRESSION && n->getSymbol() && + n->getSymbol()->getAssignments() > 0) + { + auto* read = static_cast(n); + LSLASTNode* parent = n->getParent(); + const bool set = parent && parent->getNodeType() == NODE_EXPRESSION && + static_cast(parent)->getOperation() == OP_ASSIGN && parent->getChild(0) == n; + if (!set) + { + // A compound assignment or an increment reads it + // too, with nothing to write in its place. + const bool mutated = parent && parent->getNodeType() == NODE_EXPRESSION && + operation_mutates(static_cast(parent)->getOperation()) && parent->getChild(0) == n; + LSLConstant* known = mutated ? nullptr : read->getConstantValue(); + auto& count = mReads[n->getSymbol()]; + ++count.first; + if (known && inlineable(known) && (n->getSymbol()->getIType() != LST_KEY || keyMayInline(read))) + { + ++count.second; + } + } + } + for (LSLASTNode* child = n->getChild(0); child; child = child->getNext()) + { + stack.push_back(child); + } + } + } + const auto found = mReads.find(sym); + return found != mReads.end() && found->second.first == found->second.second; + } + + // Each such variable's reads, and those of them with a value. + boost::unordered_flat_map> mReads; + bool mCounted = false; + // A global's value goes where it is read only where writing it at // every place it is read costs less than the global does: a vector // read ten times is ten vectors. What goes where is the target's @@ -5157,11 +5622,17 @@ namespace LSLASTNode::replaceNode(node, ctx.allocator->newTracked()); } + static bool parameter(LSLSymbol* sym) + { + return sym->getSubType() == SYM_FUNCTION_PARAMETER || sym->getSubType() == SYM_EVENT_PARAMETER; + } + // A variable -- a local, a global -- set and never read goes, and // each place it is set keeps what setting it ran: `x = f();` is // `f();`, `x++;` nothing, and `integer h = llListen(...);` the // call. Only where every place that names it but its declaration - // sets it, and nothing reads what was set. + // sets it, and nothing reads what was set. A parameter so set + // loses its writes the same way, and stays. void writeOnly(LSLScript* script) { struct Uses @@ -5192,7 +5663,7 @@ namespace else if (node->getNodeType() == NODE_EXPRESSION && node->getNodeSubType() == NODE_LVALUE_EXPRESSION) { LSLSymbol* sym = node->getSymbol(); - if (!sym || (sym->getSubType() != SYM_LOCAL && sym->getSubType() != SYM_GLOBAL)) + if (!sym || (sym->getSubType() != SYM_LOCAL && sym->getSubType() != SYM_GLOBAL && !parameter(sym))) { return; } @@ -5214,13 +5685,16 @@ namespace // A global never used at all goes with the unused (globals()), // and a local set to nothing that changes anything goes with // those (unusedLocal); one set to what does is this pass's. + // A parameter's writes go, and the parameter stays. Uses& use = uses[sym]; const bool global = use.declaration && use.declaration->getNodeType() == NODE_GLOBAL_VARIABLE; - if (use.read || !use.declaration || (global && use.writes.empty())) + const bool given = parameter(sym); + if (use.read || (!use.declaration && !given) || ((global || given) && use.writes.empty())) { continue; } - report.note(use.declaration->getLoc(), "OptimizerRemovedWriteOnly", "removed [1], which is set and never read", { sym->getName() }); + report.note(given ? use.writes.front()->getLoc() : use.declaration->getLoc(), "OptimizerRemovedWriteOnly", + "removed [1], which is set and never read", { sym->getName() }); for (LSLASTNode* write : use.writes) { LSLASTNode* value = write->getNodeSubType() == NODE_BINARY_EXPRESSION ? write->getChild(1) : nullptr; @@ -5240,7 +5714,7 @@ namespace script->getSymbolTable()->remove(sym); script->getGlobals()->removeChild(use.declaration); } - else + else if (!given) { auto* decl = static_cast(use.declaration); LSLASTNode* init = decl->getInitializer(); @@ -6103,7 +6577,17 @@ namespace script->determineTypes(); script->recalculateReferenceData(); ALLSLArithmetic behavior(&parser.allocator, options.addstrings, options.target); - const auto propagate = [&]() { + // Tailslide's values until what the script writes is known, and + // with it, where folding is asked for, what the locals hold as the + // code runs (FlowValues). + const ALLSLEffects* flowing = nullptr; + const auto propagate = [&]() { + if (flowing) + { + FlowValues values(&behavior, &parser.allocator, *flowing, options.target != ALLSLOptimizer::Target::Luau); + script->visit(&values); + return; + } ConstantDeterminingVisitor values(&behavior, &parser.allocator); script->visit(&values); }; @@ -6118,6 +6602,11 @@ namespace } const ALLSLEffects effects(script); + if (options.constfold) + { + flowing = &effects; + propagate(); + } Ctx ctx; ctx.allocator = &parser.allocator; ctx.context = &parser.context; diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index d97a29c75f..87d41112ab 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -171,9 +171,9 @@ namespace tut void allsloptimizer_object::test<2>() { set_test_name("floats fold in single precision and print so that they read back exactly"); - // Each variable is assigned again later, so that it stays a - // variable and the fold shows in its declaration. - const std::string source = wrap("", " float a = 0.1 + 0.2;\n float b = PI * 2;\n float c = 1.0 / 3;\n llSay(0, (string)a + (string)b + (string)c);\n a = b = c = 0;\n"); + // Each variable may be assigned again before it is read, so that + // it stays a variable and the fold shows in its declaration. + const std::string source = wrap("", " float a = 0.1 + 0.2;\n float b = PI * 2;\n float c = 1.0 / 3;\n if (llFrand(1) < 0.5)\n {\n a = b = c = 0;\n }\n llSay(0, (string)a + (string)b + (string)c);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); ensure("optimized", r.optimized); // 0.1f + 0.2f is 0.3 exactly as a single, PI * 2 is 6.2831855f, @@ -184,7 +184,7 @@ namespace tut // A float that is a whole number prints as an integer where LSL // would convert one, and stays a float where it would not. - r = ALLSLOptimizer::run(wrap("", " float f = 2.0;\n list l = [2.0];\n string s = (string)2.0;\n vector v = <1.0, 2.0, 3.5>;\n llSay(0, (string)f + llList2CSV(l) + s + (string)v);\n f = 0; l = []; s = \"\"; v = ZERO_VECTOR;\n"), options()); + r = ALLSLOptimizer::run(wrap("", " float f = 2.0;\n list l = [2.0];\n string s = (string)2.0;\n vector v = <1.0, 2.0, 3.5>;\n if (llFrand(1) < 0.5)\n {\n f = 0; l = []; s = \"\"; v = ZERO_VECTOR;\n }\n llSay(0, (string)f + llList2CSV(l) + s + (string)v);\n"), options()); ensure("whole float as integer: " + r.text, r.text.find("float f = 2;") != std::string::npos); // As the cast of an integer on Mono, where that is smaller (ALLSLCosts::castForWholeFloat). ensure("list element stays a float: " + r.text, r.text.find("list l = (list)((float)2);") != std::string::npos); @@ -211,7 +211,7 @@ namespace tut void allsloptimizer_object::test<4>() { set_test_name("pure library calls with constant arguments are evaluated, others left"); - const std::string source = wrap("", " integer a = llAbs(-3);\n float b = llSqrt(16);\n string c = llToUpper(\"abc\");\n string d = llGetSubString(\"abcdef\", 4, 1);\n string e = llDeleteSubString(\"abcdef\", 1, 3);\n integer f = llStringLength(\"hello\");\n string g = llEscapeURL(\"a b\");\n string h = llBase64ToString(llStringToBase64(\"hi\"));\n integer i = llList2Integer([1, 2, 3], -1);\n string j = llList2String([1, 2.5, <1,2,3>], 1);\n list k = llList2List([1, 2, 3, 4], 1, 2);\n integer l = llFloor(2.7) + llCeil(2.2);\n string m = llGetSubString(\"h\xc3\xa9llo\", 0, 1);\n integer n = llGetUnixTime();\n string o = llList2CSV([1, \"a\", 2.0]);\n llSay(0, (string)a + (string)b + c + d + e + (string)f + g + h + (string)i + j + llList2CSV(k) + (string)l + m + (string)n + o);\n a = f = i = l = n = 0; b = 0; c = d = e = g = h = j = m = o = \"\"; k = [];\n"); + const std::string source = wrap("", " integer a = llAbs(-3);\n float b = llSqrt(16);\n string c = llToUpper(\"abc\");\n string d = llGetSubString(\"abcdef\", 4, 1);\n string e = llDeleteSubString(\"abcdef\", 1, 3);\n integer f = llStringLength(\"hello\");\n string g = llEscapeURL(\"a b\");\n string h = llBase64ToString(llStringToBase64(\"hi\"));\n integer i = llList2Integer([1, 2, 3], -1);\n string j = llList2String([1, 2.5, <1,2,3>], 1);\n list k = llList2List([1, 2, 3, 4], 1, 2);\n integer l = llFloor(2.7) + llCeil(2.2);\n string m = llGetSubString(\"h\xc3\xa9llo\", 0, 1);\n integer n = llGetUnixTime();\n string o = llList2CSV([1, \"a\", 2.0]);\n if (llFrand(1) < 0.5)\n {\n a = f = i = l = n = 0; b = 0; c = d = e = g = h = j = m = o = \"\"; k = [];\n }\n llSay(0, (string)a + (string)b + c + d + e + (string)f + g + h + (string)i + j + llList2CSV(k) + (string)l + m + (string)n + o);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); ensure("optimized", r.optimized); ensure("abs: " + r.text, r.text.find("integer a = 3;") != std::string::npos); @@ -383,7 +383,7 @@ namespace tut void allsloptimizer_object::test<11>() { set_test_name("the rotation functions fold over unit rotations by the viewer's own quaternion, and leave the rest"); - const std::string source = wrap("", " rotation a = llEuler2Rot(<0, 0, PI_BY_TWO>);\n vector b = llRot2Euler(<0, 0, 0.7071068, 0.7071068>);\n rotation c = llAxisAngle2Rot(<0, 0, 2>, PI);\n vector d = llRot2Axis(<0, 0, 0.7071068, 0.7071068>);\n float e = llRot2Angle(<0, 0, 0.7071068, 0.7071068>);\n vector f = llRot2Fwd(<0, 0, 0.7071068, 0.7071068>);\n vector h = llRot2Up(<0.7071068, 0, 0, 0.7071068>);\n vector i = llRot2Axis(<0, 0, 0, 1>);\n vector j = llRot2Fwd(<0, 0, 2, 2>);\n llSay(0, (string)a + (string)b + (string)c + (string)d + (string)e + (string)f + (string)h + (string)i + (string)j);\n a = c = ZERO_ROTATION; b = d = f = h = i = j = ZERO_VECTOR; e = 0;\n"); + const std::string source = wrap("", " rotation a = llEuler2Rot(<0, 0, PI_BY_TWO>);\n vector b = llRot2Euler(<0, 0, 0.7071068, 0.7071068>);\n rotation c = llAxisAngle2Rot(<0, 0, 2>, PI);\n vector d = llRot2Axis(<0, 0, 0.7071068, 0.7071068>);\n float e = llRot2Angle(<0, 0, 0.7071068, 0.7071068>);\n vector f = llRot2Fwd(<0, 0, 0.7071068, 0.7071068>);\n vector h = llRot2Up(<0.7071068, 0, 0, 0.7071068>);\n vector i = llRot2Axis(<0, 0, 0, 1>);\n vector j = llRot2Fwd(<0, 0, 2, 2>);\n if (llFrand(1) < 0.5)\n {\n a = c = ZERO_ROTATION; b = d = f = h = i = j = ZERO_VECTOR; e = 0;\n }\n llSay(0, (string)a + (string)b + (string)c + (string)d + (string)e + (string)f + (string)h + (string)i + (string)j);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); ensure("optimized", r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; @@ -402,7 +402,7 @@ namespace tut void allsloptimizer_object::test<12>() { set_test_name("llJsonGetValue folds a string or a plain number out of strict JSON, and leaves the rest to the simulator"); - const std::string source = wrap("", " string a = llJsonGetValue(\"{\\\"name\\\": \\\"Ann\\\", \\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"name\"]);\n string b = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", -7]}\", [\"tags\", 1]);\n string c = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"tags\", 2, \"k\"]);\n string d = llJsonGetValue(\"[1.5]\", [0]);\n string e = llJsonGetValue(\"[true]\", [0]);\n string f = llJsonGetValue(\"[1]\", [3]);\n string g = llJsonGetValue(\"{\\\"a\\\": 1, \\\"a\\\": 2}\", [\"a\"]);\n string h = llJsonGetValue(\"[1,]\", [0]);\n string i = llJsonGetValue(\"[\\\"a\\\\\\\"b\\\"]\", [0]);\n llSay(0, a + b + c + d + e + f + g + h + i);\n a = b = c = d = e = f = g = h = i = \"\";\n"); + const std::string source = wrap("", " string a = llJsonGetValue(\"{\\\"name\\\": \\\"Ann\\\", \\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"name\"]);\n string b = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", -7]}\", [\"tags\", 1]);\n string c = llJsonGetValue(\"{\\\"tags\\\": [\\\"x\\\", 7, {\\\"k\\\": \\\"v\\\"}]}\", [\"tags\", 2, \"k\"]);\n string d = llJsonGetValue(\"[1.5]\", [0]);\n string e = llJsonGetValue(\"[true]\", [0]);\n string f = llJsonGetValue(\"[1]\", [3]);\n string g = llJsonGetValue(\"{\\\"a\\\": 1, \\\"a\\\": 2}\", [\"a\"]);\n string h = llJsonGetValue(\"[1,]\", [0]);\n string i = llJsonGetValue(\"[\\\"a\\\\\\\"b\\\"]\", [0]);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = i = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h + i);\n"); ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, options()); ensure("optimized", r.optimized); auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; @@ -416,7 +416,7 @@ namespace tut ensure("a trailing comma is left", has("string h = llJsonGetValue")); ensure("an escape unescaped: " + r.text, has("string i = \"a\\\"b\";")); // The answers that are constants are the constants' names. - const std::string more = wrap("", " string a = llJsonGetValue(\"[true, null]\", [0]);\n string b = llJsonGetValue(\"[1]\", [3]);\n string c = llJsonValueType(\"{\\\"a\\\": [1, {}]}\", [\"a\", 1]);\n string d = llJsonValueType(\"{\\\"a\\\": 1}\", [\"b\"]);\n string e = llJsonValueType(\"[1,]\", []);\n string f = llList2Json(JSON_OBJECT, [\"name\", \"Ann Lee\", \"count\", 3]);\n string g = llList2Json(JSON_ARRAY, [1, \"true\"]);\n string h = llList2Json(JSON_ARRAY, [1.5]);\n list i = llJson2List(\"[1, \\\"two\\\", -3]\");\n list j = llJson2List(\"{\\\"k\\\": \\\"v\\\"}\");\n list k = llJson2List(\"[true]\");\n llSay(0, a + b + c + d + e + f + g + h + llList2CSV(i) + llList2CSV(j) + llList2CSV(k));\n a = b = c = d = e = f = g = h = \"\"; i = j = k = [];\n"); + const std::string more = wrap("", " string a = llJsonGetValue(\"[true, null]\", [0]);\n string b = llJsonGetValue(\"[1]\", [3]);\n string c = llJsonValueType(\"{\\\"a\\\": [1, {}]}\", [\"a\", 1]);\n string d = llJsonValueType(\"{\\\"a\\\": 1}\", [\"b\"]);\n string e = llJsonValueType(\"[1,]\", []);\n string f = llList2Json(JSON_OBJECT, [\"name\", \"Ann Lee\", \"count\", 3]);\n string g = llList2Json(JSON_ARRAY, [1, \"true\"]);\n string h = llList2Json(JSON_ARRAY, [1.5]);\n list i = llJson2List(\"[1, \\\"two\\\", -3]\");\n list j = llJson2List(\"{\\\"k\\\": \\\"v\\\"}\");\n list k = llJson2List(\"[true]\");\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\"; i = j = k = [];\n }\n llSay(0, a + b + c + d + e + f + g + h + llList2CSV(i) + llList2CSV(j) + llList2CSV(k));\n"); ALLSLOptimizer::Result m = ALLSLOptimizer::run(more, options()); ensure("optimized", m.optimized); auto hasm = [&m](const char* text) { return m.text.find(text) != std::string::npos; }; @@ -433,7 +433,7 @@ namespace tut ensure("a true in it is left: " + m.text, hasm("list k = llJson2List")); // A nested value comes out written the compact way; a set puts a // value in, at a key or an index, where the document is plain. - const std::string sets = wrap("", " string a = llJsonGetValue(\"{\\\"a\\\": [1, {\\\"b\\\": true}, \\\"c\\\"]}\", [\"a\"]);\n string b = llJsonSetValue(\"{\\\"a\\\": 1}\", [\"b\"], \"two\");\n string c = llJsonSetValue(\"[1, 2]\", [1], \"9\");\n string d = llJsonSetValue(\"[1, 2]\", [JSON_APPEND], JSON_TRUE);\n string e = llJsonSetValue(\"[1, 2]\", [5], \"9\");\n string f = llJsonSetValue(\"{}\", [\"a\", \"b\"], \"x\");\n string g = llJsonSetValue(\"[1]\", [0], \"1.5\");\n string h = llJsonGetValue(\"[1.5]\", []);\n llSay(0, a + b + c + d + e + f + g + h);\n a = b = c = d = e = f = g = h = \"\";\n"); + const std::string sets = wrap("", " string a = llJsonGetValue(\"{\\\"a\\\": [1, {\\\"b\\\": true}, \\\"c\\\"]}\", [\"a\"]);\n string b = llJsonSetValue(\"{\\\"a\\\": 1}\", [\"b\"], \"two\");\n string c = llJsonSetValue(\"[1, 2]\", [1], \"9\");\n string d = llJsonSetValue(\"[1, 2]\", [JSON_APPEND], JSON_TRUE);\n string e = llJsonSetValue(\"[1, 2]\", [5], \"9\");\n string f = llJsonSetValue(\"{}\", [\"a\", \"b\"], \"x\");\n string g = llJsonSetValue(\"[1]\", [0], \"1.5\");\n string h = llJsonGetValue(\"[1.5]\", []);\n if (llFrand(1) < 0.5)\n {\n a = b = c = d = e = f = g = h = \"\";\n }\n llSay(0, a + b + c + d + e + f + g + h);\n"); ALLSLOptimizer::Result n = ALLSLOptimizer::run(sets, options()); ensure("optimized", n.optimized); auto hasn = [&n](const char* text) { return n.text.find(text) != std::string::npos; }; @@ -445,7 +445,7 @@ namespace tut ensure("a path to create is left: " + n.text, hasn("string f = llJsonSetValue")); ensure("a value that is a fraction is left: " + n.text, hasn("string g = llJsonSetValue")); ensure("a document with a fraction is left: " + n.text, hasn("string h = llJsonGetValue")); - const std::string dels = wrap("", " string a = llJsonSetValue(\"{\\\"a\\\": 1, \\\"b\\\": 2}\", [\"a\"], JSON_DELETE);\n string b = llJsonSetValue(\"[1, 2, 3]\", [1], JSON_DELETE);\n string c = llJsonSetValue(\"[1, 2, 3]\", [7], JSON_DELETE);\n llSay(0, a + b + c);\n a = b = c = \"\";\n"); + const std::string dels = wrap("", " string a = llJsonSetValue(\"{\\\"a\\\": 1, \\\"b\\\": 2}\", [\"a\"], JSON_DELETE);\n string b = llJsonSetValue(\"[1, 2, 3]\", [1], JSON_DELETE);\n string c = llJsonSetValue(\"[1, 2, 3]\", [7], JSON_DELETE);\n if (llFrand(1) < 0.5)\n {\n a = b = c = \"\";\n }\n llSay(0, a + b + c);\n"); ALLSLOptimizer::Result q = ALLSLOptimizer::run(dels, options()); auto hasq = [&q](const char* text) { return q.text.find(text) != std::string::npos; }; ensure("a key deleted: " + q.text, hasq("string a = \"{\\\"b\\\":2}\";")); @@ -2004,4 +2004,52 @@ namespace tut ensure("crossed by itself: " + r.text, about(numbers(r.text, "vector z"), { 0.f, 0.f, 0.f })); } } + + template<> template<> + void allsloptimizer_object::test<53>() + { + set_test_name("a local set to a constant is that constant where it is read before it is set again, on every way there (L15); " + "not where the ways disagree, in a loop that sets it, past a label, nor after a write in the same statement"); + const std::string head = "default\n{\n touch_start(integer n)\n {\n"; + const std::string tail = " }\n}\n"; + ALLSLOptimizer::Result r = + ALLSLOptimizer::run(head + " integer x = 5;\n llSay(0, (string)x);\n x = 7;\n llSay(0, (string)x);\n n = 3;\n llSay(n, \"n\");\n" + tail, options()); + ensure("optimized: " + notes(r), r.optimized); + ensure_equals("each read its value, and the variable gone", r.text, + head + " llSay(0, \"5\");\n llSay(0, \"7\");\n llSay(3, \"n\");\n" + tail); + ensure("noted: " + notes(r), has(r, "folded (string)x to \"7\"") && has(r, "inlined n to 3") && has(r, "removed x, which is set and never read")); + + // The ways of an if: what both say goes on, what they do not stops. + r = ALLSLOptimizer::run(head + " integer x = 1;\n integer y = 1;\n if (n)\n {\n x = 2;\n llSay(0, \"a\");\n }\n" + " else\n {\n llSay(0, \"b\");\n }\n llSay(x, (string)y);\n x = y = 0;\n" + tail, + options()); + ensure("agreed: " + r.text, r.text.find("llSay(x, \"1\");") != std::string::npos); + // A way that returns says nothing of what comes after. + r = ALLSLOptimizer::run(head + " integer x = 1;\n if (n)\n {\n x = 2;\n return;\n }\n llSay(x, \"c\");\n x = 0;\n" + tail, + options()); + ensure("a way that ends: " + r.text, r.text.find("llSay(1, \"c\");") != std::string::npos); + + // A loop that sets it: not inside, nor after. + r = ALLSLOptimizer::run(head + " integer i = 0;\n while (i < n)\n {\n llSay(i, \"d\");\n ++i;\n }\n llSay(i, \"e\");\n" + tail, + options()); + ensure("in a loop: " + r.text, r.text.find("llSay(i, \"d\");") != std::string::npos && r.text.find("llSay(i, \"e\");") != std::string::npos); + // Past a label, which a jump may come to with anything. + r = ALLSLOptimizer::run(head + " integer x = 1;\n @top;\n llSay(x, \"f\");\n x = 2;\n if (llFrand(1) < 0.5)\n jump top;\n" + tail, + options()); + ensure("past a label: " + r.text, r.text.find("llSay(x, \"f\");") != std::string::npos); + // A loop holding a label forgets everything as it begins. + r = ALLSLOptimizer::run(head + " integer x = 1;\n while (llFrand(1) < 0.5)\n {\n @in;\n llSay(x, \"g\");\n }\n" + " x = 2;\n if (llFrand(1) < 0.5)\n jump in;\n" + tail, + options()); + ensure("a loop with a label: " + r.text, r.text.find("llSay(x, \"g\");") != std::string::npos); + // LSL runs a binary operator's right side first: the read on the + // left comes after the write. + r = ALLSLOptimizer::run(head + " integer x = 1;\n llSay(0, (string)(x + (x = 3)));\n llSay(x, \"h\");\n" + tail, options()); + ensure("after a write in the statement: " + r.text, r.text.find("(string)(x + (x = 3))") != std::string::npos); + // A read left where another read of the variable is not known: a + // literal in place of a read is no smaller, and the variable stays. + r = ALLSLOptimizer::run(head + " string s = \"a long string of text\";\n llSay(0, s);\n if (n)\n s = llDetectedName(0);\n llSay(1, s);\n" + tail, + options()); + ensure("not one read alone: " + r.text, r.text.find("llSay(0, s);") != std::string::npos); + } } // namespace tut From c2864ac4a4e2cc26a0867e038a25f3ef30a52b74 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:42:57 -0400 Subject: [PATCH 04/59] A comparison the ranges of its sides settle folds: a count is never below 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 --- indra/alscript/allsloptimizer.cpp | 172 +++++++++++++++++- indra/alscript/allsltraits.cpp | 48 +++++ indra/alscript/allsltraits.h | 6 + indra/alscript/tests/allsloptimizer_test.cpp | 38 ++++ .../newview/skins/default/xui/en/strings.xml | 1 + 5 files changed, 261 insertions(+), 4 deletions(-) diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index b4ae46e4dc..40ee24052d 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -2842,6 +2842,10 @@ namespace { return false; } + if (settled(expr)) + { + return false; + } const LSLOperator op = expr->getOperation(); const LSLIType type = expr->getIType(); const bool keepLeft = left->getIType() == type; @@ -3087,6 +3091,165 @@ namespace } private: + // What a number can be, the least and the most, where something + // says: a constant; a library function's answer (ALLSLTraits::bounds), + // llFrand's of a magnitude whose sign is known; a truth; an & with a + // number not below nought; a list's length as l != []; a local set + // to one of these where it is declared and never after. + struct Range + { + double least = 0.0; + double most = 0.0; + }; + std::optional range(LSLExpression* e, int depth = 0) const + { + e = bare(e); + if (!e || depth > 8) + { + return std::nullopt; + } + if (LSLConstant* cv = e->getConstantValue()) + { + double v = 0.0; + if (cv->getNodeSubType() == NODE_INTEGER_CONSTANT) + { + v = static_cast(cv)->getValue(); + } + else if (cv->getNodeSubType() == NODE_FLOAT_CONSTANT && std::isfinite(static_cast(cv)->getValue())) + { + v = static_cast(cv)->getValue(); + } + else + { + return std::nullopt; + } + return Range{ v, v }; + } + const auto number = [](LSLExpression* x) { return x && (x->getIType() == LST_INTEGER || x->getIType() == LST_FLOATINGPOINT); }; + switch (e->getNodeSubType()) + { + case NODE_LVALUE_EXPRESSION: + { + auto* read = static_cast(e); + LSLSymbol* sym = read->getSymbol(); + LSLASTNode* decl = sym && !read->getMember() && read->getIsFoldable() && sym->getSubType() == SYM_LOCAL && sym->getAssignments() == 0 + ? sym->getVarDecl() + : nullptr; + LSLASTNode* init = decl ? decl->getChild(1) : nullptr; + return init && init->getNodeType() == NODE_EXPRESSION ? range(static_cast(init), depth + 1) : std::nullopt; + } + case NODE_FUNCTION_EXPRESSION: + { + LSLSymbol* sym = e->getSymbol(); + if (!sym || sym->getSubType() != SYM_BUILTIN) + { + return std::nullopt; + } + if (!strcmp(sym->getName(), "llFrand")) + { + // From nought towards what it is given, whose sign it + // has; the bound itself let in, which rounding can give. + LSLASTNode* arg = static_cast(e)->getArguments()->getChild(0); + const std::optional mag = arg && arg->getNodeType() == NODE_EXPRESSION ? range(static_cast(arg), depth + 1) + : std::nullopt; + if (!mag) + { + return std::nullopt; + } + return Range{ std::min(0.0, mag->least), std::max(0.0, mag->most) }; + } + S32 least = 0, most = 0; + if (e->getIType() == LST_INTEGER && ALLSLTraits::bounds(sym->getName(), least, most)) + { + return Range{ static_cast(least), static_cast(most) }; + } + return std::nullopt; + } + case NODE_UNARY_EXPRESSION: + return e->getOperation() == OP_BOOLEAN_NOT ? std::optional(Range{ 0.0, 1.0 }) : std::nullopt; + case NODE_BINARY_EXPRESSION: + { + auto* b = static_cast(e); + switch (b->getOperation()) + { + case OP_LESS: + case OP_GREATER: + case OP_LEQ: + case OP_GEQ: + case OP_EQ: + case OP_BOOLEAN_AND: + case OP_BOOLEAN_OR: + return Range{ 0.0, 1.0 }; + case OP_NEQ: + // A list's != is how much longer it is; LSO's of + // a string not only 1 or 0. + if (b->getLHS()->getIType() == LST_LIST && isEmptyList(b->getRHS())) + { + return Range{ 0.0, static_cast(INT32_MAX) }; + } + return number(b->getLHS()) && number(b->getRHS()) ? std::optional(Range{ 0.0, 1.0 }) : std::nullopt; + case OP_BIT_AND: + for (LSLExpression* side : { b->getLHS(), b->getRHS() }) + { + const std::optional r = side && side->getIType() == LST_INTEGER ? range(side, depth + 1) : std::nullopt; + if (r && r->least >= 0.0) + { + return Range{ 0.0, r->most }; + } + } + return std::nullopt; + default: + return std::nullopt; + } + } + default: + return std::nullopt; + } + } + + // A comparison of numbers settled by what each side can be: never + // below nought is never below -1, and a random float of a positive + // magnitude never below nought. Only where neither side changes + // anything, since neither is run any more. + bool settled(LSLBinaryExpression* expr) + { + const LSLOperator op = expr->getOperation(); + if (op != OP_LESS && op != OP_GREATER && op != OP_LEQ && op != OP_GEQ && op != OP_EQ && op != OP_NEQ) + { + return false; + } + LSLExpression* left = expr->getLHS(); + LSLExpression* right = expr->getRHS(); + const auto number = [](LSLExpression* x) { return x->getIType() == LST_INTEGER || x->getIType() == LST_FLOATINGPOINT; }; + if (!number(left) || !number(right) || expr->getConstantValue() || !changesNothing(left) || !changesNothing(right)) + { + return false; + } + const std::optional a = range(left); + const std::optional b = range(right); + if (!a || !b) + { + return false; + } + std::optional is; + const bool apart = a->most < b->least || b->most < a->least; + switch (op) + { + case OP_LESS: is = a->most < b->least ? std::optional(true) : a->least >= b->most ? std::optional(false) : std::nullopt; break; + case OP_LEQ: is = a->most <= b->least ? std::optional(true) : a->least > b->most ? std::optional(false) : std::nullopt; break; + case OP_GREATER: is = a->least > b->most ? std::optional(true) : a->most <= b->least ? std::optional(false) : std::nullopt; break; + case OP_GEQ: is = a->least >= b->most ? std::optional(true) : a->most < b->least ? std::optional(false) : std::nullopt; break; + case OP_EQ: is = apart ? std::optional(false) : std::nullopt; break; + default: is = apart ? std::optional(true) : std::nullopt; break; + } + if (!is) + { + return false; + } + fold(expr, ctx.integer(*is ? 1 : 0), "OptimizerSettled", "settled"); + return true; + } + static bool empty(LSLStatement* s) { if (!s) @@ -3609,8 +3772,8 @@ namespace wrote(expr, made, before); } - // Whether a value is never below -1: a find's, or a local's that - // is set to one where it is declared and never after. + // Whether a value is never below -1: a find's, a count's, or a + // local's that is set to one where it is declared and never after. static bool atLeastMinusOne(LSLExpression* x) { x = bare(x); @@ -3625,8 +3788,9 @@ namespace { return false; } - LSLSymbol* sym = static_cast(x)->getSymbol(); - return sym && sym->getSubType() == SYM_BUILTIN && ALLSLTraits::atLeastMinusOne(sym->getName()); + LSLSymbol* sym = static_cast(x)->getSymbol(); + S32 least = 0, most = 0; + return sym && sym->getSubType() == SYM_BUILTIN && ALLSLTraits::bounds(sym->getName(), least, most) && least >= -1; } static bool integerOperands(LSLBinaryExpression* expr) diff --git a/indra/alscript/allsltraits.cpp b/indra/alscript/allsltraits.cpp index 064a6fb0af..dd0301cc8b 100644 --- a/indra/alscript/allsltraits.cpp +++ b/indra/alscript/allsltraits.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -194,6 +195,53 @@ bool ALLSLTraits::changesNothing(Tailslide::LSLASTNode* node) return quiet(node, true); } +// static +bool ALLSLTraits::bounds(const char* name, S32& least, S32& most) +{ + if (!name) + { + return false; + } + if (atLeastMinusOne(name)) + { + least = -1; + most = INT32_MAX; + return true; + } + struct Row + { + std::string_view name; + S32 least; + S32 most; + }; + // Not llGetUnixTime, whose answer wraps below nought in 2038. + static constexpr Row ROWS[] = { + { "llGetAttached", 0, INT32_MAX }, { "llGetFreeMemory", 0, INT32_MAX }, { "llGetFreeURLs", 0, INT32_MAX }, + { "llGetInventoryNumber", 0, INT32_MAX }, { "llGetLinkNumber", 0, INT32_MAX }, { "llGetLinkNumberOfSides", 0, INT32_MAX }, + { "llGetListEntryType", 0, 6 }, { "llGetListLength", 0, INT32_MAX }, { "llGetMemoryLimit", 0, INT32_MAX }, + { "llGetNumberOfPrims", 0, INT32_MAX }, { "llGetNumberOfSides", 0, INT32_MAX }, { "llGetObjectPrimCount", 0, INT32_MAX }, + { "llGetRegionAgentCount", 0, INT32_MAX }, { "llGetSPMaxMemory", 0, INT32_MAX }, { "llGetUsedMemory", 0, INT32_MAX }, + { "llStringLength", 0, INT32_MAX }, + }; + for (const Row& row : ROWS) + { + if (row.name == name) + { + least = row.least; + most = row.most; + return true; + } + } + const Trait* t = of(name); + if (t && (t->slua & SluaBool)) + { + least = 0; + most = 1; + return true; + } + return false; +} + // static bool ALLSLTraits::atLeastMinusOne(const char* name) { diff --git a/indra/alscript/allsltraits.h b/indra/alscript/allsltraits.h index ba62fe46f4..932b556608 100644 --- a/indra/alscript/allsltraits.h +++ b/indra/alscript/allsltraits.h @@ -106,4 +106,10 @@ class ALLSLTraits // something, and INVENTORY_NONE. What the definitions do not say, so // kept by hand. static bool atLeastMinusOne(const char* name); + // The least and the most an integer function answers, where either is + // narrower than any integer: a count's nought or more, a find's -1 or + // more, 1 or 0 where the definitions say it answers a truth, a type's + // TYPE_INVALID to TYPE_ROTATION. Kept by hand beside the definitions' + // truths; false for a function it says nothing of. + static bool bounds(const char* name, S32& least, S32& most); }; diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 87d41112ab..c4b7e4c4bc 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -2052,4 +2052,42 @@ namespace tut options()); ensure("not one read alone: " + r.text, r.text.find("llSay(0, s);") != std::string::npos); } + + template<> template<> + void allsloptimizer_object::test<54>() + { + set_test_name("a comparison settled by what each side can be (L16): a count never below nought, a find never below -1, llFrand of a positive " + "magnitude, a truth 1 or 0, a type within its constants; not where the ranges meet, nor where a side changes something"); + const std::string source = "integer said() { llOwnerSay(\"said\"); return 1; }\n" + "default\n{\n touch_start(integer n)\n {\n" + " list l = llParseString2List(llDetectedName(0), [\" \"], []);\n" + " if (llGetListLength(l) < 0) llSay(0, \"a\");\n" + " integer count = llGetListLength(l);\n" + " if (count >= 0) llSay(0, \"b\");\n" + " if (llFrand(10.0) < 0) llSay(0, \"c\");\n" + " if (llListFindList(l, [\"x\"]) > -2) llSay(0, \"d\");\n" + " if (llGetListEntryType(l, 0) == 9) llSay(0, \"e\");\n" + " if (llSameGroup(llDetectedKey(0)) == 2) llSay(0, \"f\");\n" + " if (llFrand(10.0) < 5) llSay(0, \"g\");\n" + " if (llGetListLength(l) > 0) llSay(0, \"h\");\n" + " if (llStringLength(llList2String(l, said())) < 0) llSay(0, \"i\");\n" + " }\n}\n"; + for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::LSO }) + { + ALLSLOptimizer::Options o = options(); + o.target = target; + const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); + ensure("optimized: " + notes(r), r.optimized); + const auto said = [&r](const char* what) { return r.text.find(std::string("llSay(0, \"") + what + "\")") != std::string::npos; }; + ensure("a count below nought never: " + r.text, !said("a")); + ensure("a count's local nought or more always: " + r.text, said("b") && r.text.find("count") == std::string::npos); + ensure("llFrand of ten never below nought: " + r.text, !said("c")); + ensure("a find above -2 always: " + r.text, said("d") && r.text.find("llListFindList") == std::string::npos); + ensure("a type past TYPE_ROTATION never: " + r.text, !said("e")); + ensure("a truth never 2: " + r.text, !said("f")); + ensure("where the ranges meet, kept: " + r.text, said("g") && said("h")); + ensure("where a side changes something, kept: " + r.text, said("i")); + ensure("noted: " + notes(r), has(r, "settled llGetListLength(l) < 0 to 0") || has(r, "settled (l != []) < 0 to 0") || has(r, "settled l != [] < 0 to 0")); + } + } } // namespace tut diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 4817659aa5..7594732f6f 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -653,6 +653,7 @@ If you feel this is an error, please contact support@secondlife.com removed [1], which can never run removed a while loop whose condition is always false renamed the [1] [2] to [3] + settled [1] to [2] simplified [1] to [2] put the value of [1] where it is read, and removed it swapped the branches of if ([1]) and dropped the ! From 42c005956a339d92b1492fc0b545d46b9f297f27 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:43:41 -0400 Subject: [PATCH 05/59] Convert to SLua makes bit32's answers LSL's signed integers 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 --- indra/alscript/allsltoslua.cpp | 126 ++++++++++++++++-- indra/alscript/tests/allsltoslua_test.cpp | 52 +++++++- .../newview/skins/default/xui/en/strings.xml | 1 - 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/indra/alscript/allsltoslua.cpp b/indra/alscript/allsltoslua.cpp index 4fb8f81e45..be8ba8bb6d 100644 --- a/indra/alscript/allsltoslua.cpp +++ b/indra/alscript/allsltoslua.cpp @@ -179,7 +179,7 @@ namespace "torotation", "touuid", "ipairs", "pairs", "next", "select", "error", "assert", "pcall", "xpcall", "unpack", "rawget", "rawset", "rawequal", "rawlen", "setmetatable", "getmetatable", "require", "lljson", "llbase64", // What the text written here defines of its own. - "states", "currentState", "setState", "joinLists", "lslInteger", "lslFloat", "detected", "setTimer", "timerHandle", + "states", "currentState", "setState", "joinLists", "int32", "lslInteger", "lslFloat", "detected", "setTimer", "timerHandle", "timerHandler", }; return RESERVED.contains(name); @@ -353,6 +353,12 @@ namespace Expr lvalue(LSLLValueExpression* e); Expr call(LSLFunctionExpression* e); Expr binary(LSLBinaryExpression* e); + // A bit32 call as LSL's signed integer, but where `same` says its + // answer is that already, or nothing asks for more. + Expr signedUnless(bool same, const std::string& call); + // A number not below nought: as nonNegative says, or a global the + // script never sets, declared one. + bool notBelowZero(LSLExpression* e) const; Expr unary(LSLUnaryExpression* e); Expr typecast(LSLTypecastExpression* e); // An assignment or a step inside an expression, which SLua makes a @@ -612,7 +618,11 @@ namespace boost::unordered_flat_map mNames; boost::unordered_flat_set mTaken; // What the text needs of its own, defined once over it. + // The globals the script never sets, declared a number not below + // nought. + boost::unordered_flat_set mSteadyNonNegative; bool mJoinLists = false; + bool mInt32 = false; bool mLslInteger = false; bool mLslFloat = false; bool mManyStates = false; @@ -1043,6 +1053,37 @@ namespace } } + // Whether what an expression gives is bit32's to take again, which takes + // a number below nought as it takes the same past 2147483647: an operand + // of &, | or ^, what ~ turns over, or what a shift moves. + bool intoBit32(LSLASTNode* e) + { + LSLASTNode* node = e; + LSLASTNode* parent = node->getParent(); + while (parent && parent->getNodeSubType() == NODE_PARENTHESIS_EXPRESSION) + { + node = parent; + parent = parent->getParent(); + } + if (!parent || parent->getNodeType() != NODE_EXPRESSION) + { + return false; + } + switch (static_cast(parent)->getOperation()) + { + case OP_BIT_AND: + case OP_BIT_OR: + case OP_BIT_XOR: + case OP_BIT_NOT: + return true; + case OP_SHIFT_LEFT: + case OP_SHIFT_RIGHT: + return node->getParentSlot() == 0; + default: + return false; + } + } + // A whole number written out, and what it is: a constant, or one with a // minus before it. bool wholeNumber(LSLExpression* e, int& v) @@ -1461,6 +1502,43 @@ namespace return { "llcompat." + bare + "(" + args(e->getArguments(), params) + ")" }; } + Expr Writer::signedUnless(bool same, const std::string& call) + { + if (same) + { + return { call }; + } + mInt32 = true; + return { "int32(" + call + ")" }; + } + + bool Writer::notBelowZero(LSLExpression* e) const + { + if (nonNegative(e)) + { + return true; + } + while (e && e->getNodeSubType() == NODE_PARENTHESIS_EXPRESSION) + { + e = static_cast(e)->getChildExpr(); + } + if (e && e->getNodeSubType() == NODE_BINARY_EXPRESSION) + { + // An & with one, an | or ^ of two, a >> of one. + auto* b = static_cast(e); + switch (b->getOperation()) + { + case OP_BIT_AND: return notBelowZero(b->getLHS()) || notBelowZero(b->getRHS()); + case OP_BIT_OR: + case OP_BIT_XOR: return notBelowZero(b->getLHS()) && notBelowZero(b->getRHS()); + case OP_SHIFT_RIGHT: return notBelowZero(b->getLHS()); + default: return false; + } + } + return e && e->getNodeSubType() == NODE_LVALUE_EXPRESSION && !static_cast(e)->getMember() && + mSteadyNonNegative.contains(static_cast(e)->getIdentifier()->getSymbol()); + } + Expr Writer::binary(LSLBinaryExpression* e) { // == and ~= the other way round under a not (unary). @@ -1520,15 +1598,14 @@ namespace "LSL ran the right side of this before the left, and Luau runs the left first: one side changes what the other reads."); } const auto bit = [&](const char* fn) -> Expr { - // Not where the answer is the same number: an & with a number not - // below nought, or an | or ^ of two; nor where it is only asked - // whether it is nought. - const bool same = (op == OP_BIT_AND && (nonNegative(lhs) || nonNegative(rhs))) || - ((op == OP_BIT_OR || op == OP_BIT_XOR) && nonNegative(lhs) && nonNegative(rhs)) || truthOnly(e); - if (!same) - { - noteOnce(e, "SluaBit32", "bit32 answers 0 to 4294967295; LSL's integers were signed, from -2147483648."); - } + // bit32 answers 0 to 4294967295, where LSL's integers were signed: + // made LSL's (int32), but where the answer is the same number -- + // an & with a number not below nought, an | or ^ of two, a >> of + // one -- or where it is only asked whether it is nought, or + // bit32 takes it again. + const bool same = (op == OP_BIT_AND && (notBelowZero(lhs) || notBelowZero(rhs))) || + ((op == OP_BIT_OR || op == OP_BIT_XOR) && notBelowZero(lhs) && notBelowZero(rhs)) || + (op == OP_SHIFT_RIGHT && notBelowZero(lhs)) || truthOnly(e) || intoBit32(e); // An &, | or ^ of the same again, one call of them all: bit32's // take as many as are given. if (op == OP_BIT_AND || op == OP_BIT_OR || op == OP_BIT_XOR) @@ -1550,9 +1627,9 @@ namespace }; gather(lhs); gather(rhs); - return { std::string("bit32.") + fn + "(" + all + ")" }; + return signedUnless(same, std::string("bit32.") + fn + "(" + all + ")"); } - return { std::string("bit32.") + fn + "(" + value(lhs).text + ", " + value(rhs).text + ")" }; + return signedUnless(same, std::string("bit32.") + fn + "(" + value(lhs).text + ", " + value(rhs).text + ")"); }; switch (op) { @@ -1710,8 +1787,7 @@ namespace return { "not " + bracketed(condition(child), UNARY), UNARY, true }; } case OP_BIT_NOT: - noteOnce(e, "SluaBit32", "bit32 answers 0 to 4294967295; LSL's integers were signed, from -2147483648."); - return { "bit32.bnot(" + value(child).text + ")" }; + return signedUnless(truthOnly(e) || intoBit32(e), "bit32.bnot(" + value(child).text + ")"); case OP_PRE_INCR: case OP_PRE_DECR: case OP_POST_INCR: @@ -4680,6 +4756,16 @@ local function joinLists(a: { any }, b: { any }): { any } return out end +)LUA"; + } + if (mInt32) + { + out += R"LUA(-- What bit32 answers, 0 to 4294967295, as LSL's integers were: signed, from +-- -2147483648. +local function int32(n: number): number + return if n >= 0x80000000 then n - 0x100000000 else n +end + )LUA"; } if (mLslInteger) @@ -4748,6 +4834,18 @@ end { timersPreamble(); } + // The globals never set, declared a number not below nought: what an & + // with one of them gives needs no sign. + const ALLSLEffects::Writes written = mEffects.of(mScript); + for (LSLASTNode* g = mScript->getGlobals()->getChild(0); g; g = g->getNext()) + { + int v = 0; + if (g->getNodeType() == NODE_GLOBAL_VARIABLE && !written.writes(g->getSymbol()) && + wholeNumber(static_cast(g)->getInitializer(), v) && v >= 0) + { + mSteadyNonNegative.insert(g->getSymbol()); + } + } globals(); functions(); states(); diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index 68543ba1ef..32d65fbf61 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -196,7 +196,9 @@ namespace tut ensure("a float as LSL writes it: " + r.text, has(r, "string.format(\"%.6f\", f)")); ensure("a string as an integer: " + r.text, has(r, "lslInteger(\"12abc\")") && has(r, "local function lslInteger")); ensure("lists compared by length: " + r.text, has(r, "#l == #{1}") && noted(r, "SluaListCompare")); - ensure("bits: " + r.text, has(r, "bit32.bor(bit32.band(a, b), 4)") && noted(r, "SluaBit32")); + // bit32 answers 0 to 4294967295: made LSL's signed integer, but where + // bit32 takes it again. + ensure("bits: " + r.text, has(r, "int32(bit32.bor(bit32.band(a, b), 4))") && has(r, "local function int32")); ensure("a vector's part set: " + r.text, has(r, "v = vector(4, v.y, v.z)") && has(r, "v = vector(v.x, v.y, v.z + 1)")); ensure("a vector as LSL writes it: " + r.text, has(r, "ll.DumpList2String({v}, \"\")")); checksClean(r); @@ -762,7 +764,7 @@ namespace tut template<> template<> void allsltoslua_object::test<24>() { - set_test_name("a note only where it says something: bit32's range not for an & with a flag nor a truth, && not over a read, a deprecated call's reason rather than its indexes"); + set_test_name("a note only where it says something: && not over a read, a deprecated call's reason rather than its indexes; bit32's range made LSL's, not for an & with a flag nor a truth"); const ALLSLToSLua::Result r = convert("integer sayIt() { llOwnerSay(\"x\"); return 1; }\n" "default { changed(integer what) {\n" " if (what & CHANGED_LINK && llGetLinkNumber() == 0) llDie();\n" @@ -771,8 +773,8 @@ namespace tut " llOwnerSay(llList2String(l, what) + (string)m);\n" " if ((what & 3) && sayIt()) llDie();\n" "} }\n"); - ensure("bit32's range where it shows: " + r.text, - count(r, "-- LSL: bit32 answers") == 1 && has(r, "-- LSL: bit32 answers 0 to 4294967295; LSL's integers were signed, from -2147483648.\n local m")); + ensure("bit32's range made LSL's where it shows: " + r.text, has(r, "local m = int32(bit32.bor(what, ") && !has(r, "-- LSL: bit32")); + ensure("not for a flag's truth: " + r.text, has(r, "if bit32.band(what, CHANGED_LINK) ~= 0")); ensure("&& said once, over a call that does something: " + r.text, count(r, "leaves its right side unrun") == 1 && has(r, "LSL ran both sides, the right one first.\n if bit32.band(what, 3) ~= 0 and sayIt()")); ensure("the deprecated call's reason only: " + r.text, has(r, "-- LSL: SLua deprecates ll.List2String") && !has(r, "takes indexes from 0")); @@ -1111,4 +1113,46 @@ namespace tut ensure("the other built from it: " + r.text, has(r, "table.insert(allParts, line)") && has(r, "local all = table.concat(allParts)")); checksClean(r); } + + template<> template<> + void allsltoslua_object::test<36>() + { + set_test_name("bit32's answers made LSL's signed integers where they can pass 2147483647 -- a message number with its top bit set, the mask " + "that finds it, ~, a shift -- and not where bit32 takes them again, only truth is asked, or an & is with a constant not below nought"); + // A protocol of link messages whose numbers have their top bit set, + // as a set of scripts may share: before, the mask's answer was never + // the message's number, and no message was heard. + const ALLSLToSLua::Result r = convert("integer MASK = 0xFFFF0000;\n" + "integer MSG = 0xB3470000;\n" + "integer FN_MASK = 0x0000FF00;\n" + "integer FN_RESET = 0x0500;\n" + "default {\n" + " state_entry() {\n" + " llMessageLinked(LINK_SET, MSG | FN_RESET, \"\", \"\");\n" + " }\n" + " link_message(integer sender, integer num, string text, key id) {\n" + " if ((num & MASK) == MSG) {\n" + " integer fn = num & FN_MASK;\n" + " if (fn == FN_RESET) llOwnerSay(\"reset\");\n" + " }\n" + " integer inverse = ~num;\n" + " integer high = num << 4;\n" + " integer low = num >> 2;\n" + " integer part = FN_MASK >> 8;\n" + " if (~llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) llOwnerSay(\"ask\");\n" + " llOwnerSay((string)(inverse + high + low + part));\n" + " }\n" + "}\n"); + ensure("the mask's answer signed: " + r.text, has(r, "if int32(bit32.band(num, MASK)) == MSG then")); + ensure("the number sent signed: " + r.text, has(r, "ll.MessageLinked(LINK_SET, int32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); + ensure("an & with a constant not below nought as it is: " + r.text, has(r, "local fn = bit32.band(num, FN_MASK)\n")); + ensure("~ signed: " + r.text, has(r, "local inverse = int32(bit32.bnot(num))")); + ensure("<< signed: " + r.text, has(r, "local high = int32(bit32.lshift(num, 4))")); + ensure(">> of what may be below nought signed: " + r.text, has(r, "local low = int32(bit32.arshift(num, 2))")); + ensure(">> of a constant not below nought as it is: " + r.text, has(r, "local part = bit32.arshift(FN_MASK, 8)\n")); + ensure("a truth, and what bit32 takes again, as they are: " + r.text, + has(r, "if bit32.band(bit32.bnot(ll.GetPermissions()), PERMISSION_TRIGGER_ANIMATION) ~= 0 then")); + ensure("the helper, once: " + r.text, count(r, "local function int32(n: number): number") == 1 && !has(r, "-- LSL: bit32")); + checksClean(r); + } } diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 7594732f6f..c7c74d7989 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -1003,7 +1003,6 @@ If you feel this is an error, please contact support@secondlife.com llcompat.[1] answers 1 or 0, as LSL did; ll.[1] answers true or false. llcompat.[1]'s list has 1 or 0 where LSL's did; ll.[1]'s has true or false there. LSL ran the right side of this before the left, and Luau runs the left first: one side changes what the other reads. - bit32 answers 0 to 4294967295; LSL's integers were signed, from -2147483648. // rounds down, and LSL's integer / rounded toward zero: they differ where the answer is negative. Luau's % takes the divisor's sign, and LSL's took the dividend's: they differ where one is negative. LSL compares lists by their lengths alone, and != answers the difference of them. From 040bb77a07df707447716e3085206beb1a3f001e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 03:44:14 -0400 Subject: [PATCH 06/59] Convert to SLua carries the script's comments over 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 --- indra/alscript/allsltoslua.cpp | 439 +++++++++++++++++++++- indra/alscript/allsltoslua.h | 3 + indra/alscript/tests/allsltoslua_test.cpp | 71 ++++ 3 files changed, 511 insertions(+), 2 deletions(-) diff --git a/indra/alscript/allsltoslua.cpp b/indra/alscript/allsltoslua.cpp index be8ba8bb6d..9cba109b65 100644 --- a/indra/alscript/allsltoslua.cpp +++ b/indra/alscript/allsltoslua.cpp @@ -309,7 +309,10 @@ namespace class Writer { public: - Writer(LSLScript* script, const ALLSLToSLua::Options& options) : mScript(script), mOptions(options), mEffects(script) {} + Writer(LSLScript* script, std::string_view source, const ALLSLToSLua::Options& options) + : mScript(script), mSource(source), mOptions(options), mEffects(script) + { + } std::string write(); ALScriptProblems& notes() { return mNotes; } @@ -338,6 +341,40 @@ namespace // --- the text --------------------------------------------------------------- void line(const std::string& text); + + // --- the script's own comments ----------------------------------------------- + + // Where a comment begins, or a node begins or ends, by line and + // column from one; a node's end is one past it. + struct Pos + { + S32 line = 0; + S32 column = 0; + bool operator<(const Pos& o) const { return line != o.line ? line < o.line : column < o.column; } + }; + struct Comment + { + Pos at; + S32 endLine = 0; + // As SLua writes it: a block comment's lines past its first as + // they were. + std::string text; + // A blank line over it in the LSL, which it keeps. + bool apart = false; + bool written = false; + }; + // Each comment in the LSL, given to the node it is written over -- + // a global, a function, a state, a handler, a statement -- or to + // the end of what holds it, where nothing after it in there does. + void findComments(); + void place(size_t k, const std::vector& children, LSLASTNode* holder); + bool placeInside(size_t k, LSLASTNode* node); + void placeIn(size_t k, LSLASTNode* compound); + // Those given to a node, written over it; those at a holder's end, + // with any of what it held that nothing wrote, at its end. + void commentsBefore(LSLASTNode* node); + void commentsAtEnd(LSLASTNode* holder); + void writeComment(Comment& c); std::string indent() const { return std::string(static_cast(mDepth) * 4, ' '); } // --- expressions ------------------------------------------------------------ @@ -607,6 +644,7 @@ namespace void helpers(std::string& out); LSLScript* mScript; + std::string_view mSource; const ALLSLToSLua::Options& mOptions; // What each part of the script may change, for LSL's order. ALLSLEffects mEffects; @@ -618,6 +656,12 @@ namespace boost::unordered_flat_map mNames; boost::unordered_flat_set mTaken; // What the text needs of its own, defined once over it. + // The script's comments; those over its first global, written at the + // top; and by what each is given to. + std::vector mComments; + std::vector mLeadComments; + boost::unordered_flat_map> mCommentsBefore; + boost::unordered_flat_map> mCommentsWithin; // The globals the script never sets, declared a number not below // nought. boost::unordered_flat_set mSteadyNonNegative; @@ -799,6 +843,368 @@ namespace mText += text.empty() ? std::string("\n") : indent() + text + "\n"; } + // --- the script's own comments ----------------------------------------------------- + + void Writer::findComments() + { + if (!mOptions.keepComments) + { + return; + } + // Each comment, past strings, where it begins. + const std::string_view src = mSource; + Pos at = { 1, 1 }; + const auto step = [&](char c) { + if (c == '\n') + { + ++at.line; + at.column = 1; + } + else + { + ++at.column; + } + }; + for (size_t i = 0; i < src.size();) + { + const char c = src[i]; + if (c == '"') + { + step(src[i++]); + while (i < src.size() && src[i] != '"') + { + if (src[i] == '\\' && i + 1 < src.size()) + { + step(src[i++]); + } + step(src[i++]); + } + if (i < src.size()) + { + step(src[i++]); + } + continue; + } + if (c != '/' || i + 1 >= src.size() || (src[i + 1] != '/' && src[i + 1] != '*')) + { + step(src[i++]); + continue; + } + Comment k; + k.at = at; + size_t end; + if (src[i + 1] == '/') + { + end = std::min(src.find('\n', i), src.size()); + std::string body = std::string(src.substr(i + 2, end - i - 2)); + while (!body.empty() && (body.back() == '\r' || body.back() == ' ' || body.back() == '\t')) + { + body.pop_back(); + } + // Not a block comment of Luau's, which --[[ would begin. + if (!body.empty() && body[0] == '[') + { + body = " " + body; + } + k.text = "--" + body; + } + else + { + const size_t close = src.find("*/", i + 2); + end = close == std::string_view::npos ? src.size() : close + 2; + std::string body = std::string(src.substr(i + 2, (close == std::string_view::npos ? src.size() : close) - i - 2)); + // Brackets of a level nothing in it closes. + std::string level; + while (body.find("]" + level + "]") != std::string::npos) + { + level += "="; + } + if (!body.empty() && body.back() == ']') + { + body += " "; + } + k.text = "--[" + level + "[" + body + "]" + level + "]"; + } + // A comment that reads as one of the notes this writes is not one. + if (k.text.rfind(NOTE_MARK, 0) == 0) + { + k.text.insert(2, " "); + } + while (i < end) + { + step(src[i++]); + } + k.endLine = at.line; + mComments.push_back(std::move(k)); + } + // Whether a line of the LSL, from one, is blank. + std::vector starts{ 0 }; + for (size_t i = 0; i < src.size(); ++i) + { + if (src[i] == '\n') + { + starts.push_back(i + 1); + } + } + const auto blank = [&](S32 line) { + if (line < 1 || static_cast(line) > starts.size()) + { + return false; + } + const size_t from = starts[static_cast(line) - 1]; + const size_t to = std::min(src.find('\n', from), src.size()); + return src.substr(from, to - from).find_first_not_of(" \t\r") == std::string_view::npos; + }; + for (Comment& c : mComments) + { + c.apart = blank(c.at.line - 1); + } + if (mComments.empty()) + { + return; + } + std::vector top; + for (LSLASTNode* g = mScript->getGlobals()->getChild(0); g; g = g->getNext()) + { + top.push_back(g); + } + for (LSLASTNode* st = mScript->getStates()->getChild(0); st; st = st->getNext()) + { + top.push_back(st); + } + // Over the first global, those with no blank line between them and it + // are about it; those before, the script's own, go at the top. + size_t before = 0; + while (!top.empty() && before < mComments.size() && + mComments[before].at < Pos{ top.front()->getLoc()->first_line, top.front()->getLoc()->first_column }) + { + ++before; + } + size_t about = before; + S32 next = top.empty() ? 0 : top.front()->getLoc()->first_line; + while (about > 0 && mComments[about - 1].endLine + 1 >= next && !blank(next - 1)) + { + --about; + next = mComments[about].at.line; + } + for (size_t k = 0; k < mComments.size(); ++k) + { + if (k < about) + { + // Written at the top as the text is put together. + mLeadComments.push_back(k); + mComments[k].written = true; + continue; + } + place(k, top, nullptr); + } + } + + namespace + { + // Whether what a node ends with is a statement of its own, not a + // block's brace: a comment after it on its line is about it. + bool endsBare(LSLASTNode* node) + { + switch (node->getNodeType()) + { + case NODE_GLOBAL_VARIABLE: return true; + case NODE_STATEMENT: break; + default: return false; + } + switch (node->getNodeSubType()) + { + case NODE_COMPOUND_STATEMENT: return false; + case NODE_IF_STATEMENT: + { + LSLASTNode* last = node->getChild(2); + return endsBare(isNull(last) ? node->getChild(1) : last); + } + case NODE_WHILE_STATEMENT: return endsBare(node->getChild(1)); + case NODE_FOR_STATEMENT: return endsBare(node->getChild(3)); + default: return true; + } + } + } + + void Writer::place(size_t k, const std::vector& children, LSLASTNode* holder) + { + const Pos at = mComments[k].at; + LSLASTNode* prev = nullptr; + mCommentsWithin[holder].push_back(k); + for (LSLASTNode* child : children) + { + const Pos begins = { child->getLoc()->first_line, child->getLoc()->first_column }; + const Pos ends = { child->getLoc()->last_line, child->getLoc()->last_column }; + if (!(at < ends)) + { + prev = child; + continue; + } + if (!(at < begins)) + { + // Inside it: in what it holds, or over it. + if (!placeInside(k, child)) + { + mCommentsBefore[child].push_back(k); + } + return; + } + break; + } + // On the line a bare statement ends, after it: about that statement. + if (prev && endsBare(prev) && at.line == prev->getLoc()->last_line) + { + mCommentsBefore[prev].push_back(k); + return; + } + // Over what follows it in here; or, where nothing does, at the end. + for (LSLASTNode* child : children) + { + if (at < Pos{ child->getLoc()->first_line, child->getLoc()->first_column }) + { + mCommentsBefore[child].push_back(k); + return; + } + } + } + + void Writer::placeIn(size_t k, LSLASTNode* compound) + { + std::vector children; + for (LSLASTNode* child = compound->getChild(0); child; child = child->getNext()) + { + children.push_back(child); + } + place(k, children, compound); + } + + bool Writer::placeInside(size_t k, LSLASTNode* node) + { + const Pos at = mComments[k].at; + const auto inside = [&at](LSLASTNode* n) { + return !isNull(n) && !(at < Pos{ n->getLoc()->first_line, n->getLoc()->first_column }) && + at < Pos{ n->getLoc()->last_line, n->getLoc()->last_column }; + }; + // A statement standing where one goes -- a body, a branch -- in what + // it holds, or over it. + const auto within = [&](LSLASTNode* body) { + if (!inside(body)) + { + return false; + } + if (body->getNodeSubType() == NODE_COMPOUND_STATEMENT) + { + // On the line its brace opens, before anything in it: about + // what the block is the body of. + LSLASTNode* first = body->getChild(0); + if (at.line == body->getLoc()->first_line && + (!first || at < Pos{ first->getLoc()->first_line, first->getLoc()->first_column })) + { + return false; + } + placeIn(k, body); + } + else if (!placeInside(k, body)) + { + mCommentsBefore[body].push_back(k); + } + return true; + }; + switch (node->getNodeType()) + { + case NODE_GLOBAL_FUNCTION: + return within(static_cast(node)->getStatements()); + case NODE_EVENT_HANDLER: + return within(static_cast(node)->getStatements()); + case NODE_STATE: + { + std::vector handlers; + for (LSLASTNode* h = static_cast(node)->getEventHandlers()->getChild(0); h; h = h->getNext()) + { + handlers.push_back(h); + } + place(k, handlers, node); + return true; + } + case NODE_STATEMENT: + switch (node->getNodeSubType()) + { + case NODE_COMPOUND_STATEMENT: placeIn(k, node); return true; + case NODE_IF_STATEMENT: + { + if (within(node->getChild(1)) || within(node->getChild(2))) + { + return true; + } + // Between the branches: over what the else runs -- an + // elseif's line, or the else's first statement. + LSLASTNode* no = node->getChild(2); + if (isNull(no) || !(at < Pos{ no->getLoc()->first_line, no->getLoc()->first_column }) || + at < Pos{ node->getChild(1)->getLoc()->last_line, node->getChild(1)->getLoc()->last_column }) + { + return false; + } + if (no->getNodeSubType() == NODE_COMPOUND_STATEMENT) + { + placeIn(k, no); + } + else + { + mCommentsBefore[no].push_back(k); + } + return true; + } + case NODE_WHILE_STATEMENT: return within(node->getChild(1)); + case NODE_DO_STATEMENT: return within(node->getChild(0)); + case NODE_FOR_STATEMENT: return within(node->getChild(3)); + default: return false; + } + default: + return false; + } + } + + void Writer::writeComment(Comment& c) + { + if (c.written) + { + return; + } + c.written = true; + if (c.apart && !mText.empty() && mText.compare(mText.size() - std::min(mText.size(), 2), 2, "\n\n") != 0) + { + mText += "\n"; + } + mText += indent() + c.text + "\n"; + } + + void Writer::commentsBefore(LSLASTNode* node) + { + const auto found = mCommentsBefore.find(node); + if (found == mCommentsBefore.end()) + { + return; + } + for (size_t k : found->second) + { + writeComment(mComments[k]); + } + } + + void Writer::commentsAtEnd(LSLASTNode* holder) + { + const auto found = mCommentsWithin.find(holder); + if (found == mCommentsWithin.end()) + { + return; + } + for (size_t k : found->second) + { + writeComment(mComments[k]); + } + } + // --- expressions ------------------------------------------------------------------ Expr Writer::constant(LSLConstant* c) @@ -2202,6 +2608,7 @@ namespace { statement(child, !child->getNext()); } + commentsAtEnd(s); return; } statement(s, true); @@ -2213,6 +2620,7 @@ namespace { return; } + commentsBefore(s); if (mBuilds.contains(s)) { buildingLoop(s, last); @@ -2307,6 +2715,7 @@ namespace if (otherwise->getNodeSubType() == NODE_IF_STATEMENT) { i = static_cast(otherwise); + commentsBefore(i); line("elseif " + condition(i->getCheckExpr()).text + " then"); continue; } @@ -4454,6 +4863,7 @@ namespace auto* global = static_cast(g); LSLIdentifier* id = global->getIdentifier(); LSLExpression* init = global->getInitializer(); + commentsBefore(global); if (boolean(id->getSymbol())) { line("local " + nameOf(id) + (mOptions.types ? ": boolean" : "") + " = " + (isNull(init) ? std::string("false") : truthOf(init))); @@ -4520,6 +4930,7 @@ namespace (boolean(param) ? std::string(mOptions.types ? ": boolean" : "") : typed(varType(param, p->getIType()))); } mFunction = f->getSymbol(); + commentsBefore(f); line(std::string(forward ? "function " : "local function ") + nameOf(f->getIdentifier()) + "(" + params + ")" + (boolean(mFunction) ? std::string(mOptions.types ? ": boolean" : "") : typed(f->getIdentifier()->getIType()))); ++mDepth; @@ -4565,6 +4976,7 @@ namespace void Writer::singleState(LSLState* state) { LSLEventHandler* entry = nullptr; + commentsBefore(state); for (LSLASTNode* h = state->getEventHandlers()->getChild(0); h; h = h->getNext()) { auto* handler = static_cast(h); @@ -4574,6 +4986,7 @@ namespace entry = handler; continue; } + commentsBefore(handler); if (event == "state_exit") { // Not an event SLua has, and one that never came: said, over @@ -4610,11 +5023,13 @@ namespace { // What LSL ran as the script started, run as SLua runs a script: // once, from the top. + commentsBefore(entry); line("-- state_entry"); mTopLevel = true; handlerBody(entry); mTopLevel = false; } + commentsAtEnd(state); } void Writer::statesPreamble() @@ -4701,6 +5116,7 @@ namespace { auto* state = static_cast(s); const std::string name = state->getIdentifier()->getName(); + commentsBefore(state); line("states" + stateKey(name) + " = {"); ++mDepth; for (LSLASTNode* h = state->getEventHandlers()->getChild(0); h; h = h->getNext()) @@ -4709,6 +5125,7 @@ namespace const std::string event = handler->getIdentifier()->getName(); std::string lead; const std::string params = handlerParams(handler, lead); + commentsBefore(handler); if (event == "timer" && !mTimers) { noteOnce(handler, "SluaTimerStates", @@ -4725,6 +5142,7 @@ namespace --mDepth; line("end,"); } + commentsAtEnd(state); --mDepth; line("}"); line(""); @@ -4834,6 +5252,7 @@ end { timersPreamble(); } + findComments(); // The globals never set, declared a number not below nought: what an & // with one of them gives needs no sign. const ALLSLEffects::Writes written = mEffects.of(mScript); @@ -4849,6 +5268,12 @@ end globals(); functions(); states(); + // Those after everything, and any of what nothing wrote, last. + commentsAtEnd(nullptr); + for (Comment& c : mComments) + { + writeComment(c); + } // Each of its lines a comment, whatever language the studio says // it in. const std::string head = mOptions.comments @@ -4866,6 +5291,16 @@ end from = cut + 1; } out += "\n"; + // The script's own first comments, over everything: what it is, and + // whose. + if (!mLeadComments.empty()) + { + for (size_t k : mLeadComments) + { + out += mComments[k].text + "\n"; + } + out += "\n"; + } helpers(out); return out + mText; } @@ -5051,7 +5486,7 @@ ALLSLToSLua::Result ALLSLToSLua::convert(std::string_view lsl, const Options& op } return; } - Writer writer(script, options); + Writer writer(script, lsl, options); result.text = writer.write(); result.notes = std::move(writer.notes()); result.converted = true; diff --git a/indra/alscript/allsltoslua.h b/indra/alscript/allsltoslua.h index ffc64de4fd..81375bb9bb 100644 --- a/indra/alscript/allsltoslua.h +++ b/indra/alscript/allsltoslua.h @@ -82,6 +82,9 @@ class ALLSLToSLua // Each place noted as a "-- LSL:" comment over its line, as well as // in the notes. bool comments = true; + // The script's own comments, each over what it stood over, or at the + // end of what held it: // as --, and /* */ as --[[ ]]. + bool keepComments = true; // What a note says, by its key -- the studio's own words for it, // in its skin's language -- where given; else the English, its // marks filled from the args. diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index 32d65fbf61..2040bf85e1 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -1155,4 +1155,75 @@ namespace tut ensure("the helper, once: " + r.text, count(r, "local function int32(n: number): number") == 1 && !has(r, "-- LSL: bit32")); checksClean(r); } + + template<> template<> + void allsltoslua_object::test<37>() + { + set_test_name("the script's comments carried over, each over what it stood over: the script's own at the top, a global's, those inside a list, " + "a trailing one, a brace's, between branches, at a block's end; // as --, /* */ as --[[ ]] at a level nothing in it closes"); + const std::string lsl = "// Door script by Someone.\n" + "// Do as you like with it.\n" + "\n" + "// How far it swings, in degrees\n" + "float SWING = 90.0; // a right angle\n" + "list NAMES = [\n" + " \"open\", // when it is open\n" + " \"closed\" // when it is shut\n" + "];\n" + "/* old code:\n" + "integer unused() { return 1; }\n" + "*/\n" + "\n" + "// Turns the door.\n" + "swing(float by) { // by degrees\n" + " // the rotation it turns by\n" + " rotation r = llEuler2Rot(<0, 0, by * DEG_TO_RAD>);\n" + " llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_ROT_LOCAL, r * llGetLocalRot()]); // turn\n" + " if (by > 0) {\n" + " llOwnerSay(\"opening\");\n" + " }\n" + " // when it shuts\n" + " else if (by < 0) {\n" + " llOwnerSay(\"closing\");\n" + " }\n" + " if (FALSE) {\n" + " /* left off */\n" + " } else if (by == 0) {\n" + " llOwnerSay(\"still\");\n" + " }\n" + " // done\n" + "}\n" + "\n" + "default {\n" + " // on a touch\n" + " touch_start(integer n) {\n" + " swing(SWING); //[[ not a block of Luau's ]]\n" + " /* it ends ]] here */\n" + " }\n" + "}\n"; + const ALLSLToSLua::Result r = convert(lsl); + const auto at = [&r](const std::string& text) { return r.text.find(text); }; + ensure("the script's own at the top: " + r.text, has(r, "-- Door script by Someone.\n-- Do as you like with it.\n\n")); + ensure("over everything, once: " + r.text, at("-- Door script by Someone.") < at("local SWING") && count(r, "Door script") == 1); + ensure("a global's, with what trailed it: " + r.text, has(r, "-- How far it swings, in degrees\n-- a right angle\nlocal SWING = 90.0\n")); + ensure("those inside a list, over it: " + r.text, has(r, "-- when it is open\n-- when it is shut\nlocal NAMES = {\"open\", \"closed\"}\n")); + ensure("a block comment as Luau's: " + r.text, has(r, "--[[ old code:\ninteger unused() { return 1; }\n]]\n")); + ensure("over the function it stood over: " + r.text, at("--[[ old code:") > at("local NAMES") && at("--[[ old code:") < at("-- Turns the door.")); + ensure("a brace's, over what it opens: " + r.text, has(r, "-- Turns the door.\n-- by degrees\nlocal function swing(by)\n")); + ensure("over a statement: " + r.text, has(r, " -- the rotation it turns by\n local r = ")); + ensure("a trailing one, over its statement: " + r.text, has(r, " -- turn\n ll.SetLinkPrimitiveParamsFast(")); + ensure("between branches, over the elseif: " + r.text, has(r, " -- when it shuts\n elseif by < 0 then\n")); + ensure("an empty branch's, in it: " + r.text, has(r, " if false then\n --[[ left off ]]\n elseif by == 0 then\n")); + ensure("at a block's end: " + r.text, has(r, " -- done\nend\n")); + ensure("a state's, over its handler: " + r.text, has(r, "-- on a touch\nLLEvents:on(\"touch_start\"")); + ensure("not a block of Luau's: " + r.text, has(r, " -- [[ not a block of Luau's ]]\n swing(SWING)\n")); + ensure("a level nothing in it closes: " + r.text, has(r, " --[=[ it ends ]] here ]=]\nend)")); + checksClean(r); + + ALLSLToSLua::Options none; + none.keepComments = false; + const ALLSLToSLua::Result bare = ALLSLToSLua::convert(lsl, none); + ensure("none where asked: " + bare.text, bare.converted && bare.text.find("Door script") == std::string::npos && + bare.text.find("degrees") == std::string::npos && bare.text.find("--[[") == std::string::npos); + } } From 817b3cf32f4eb7c1f55302d35c2cd628759cd9f8 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:19:30 -0400 Subject: [PATCH 07/59] The optimizer's float folding is built without the viewer's fast-math 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 --- indra/alscript/CMakeLists.txt | 24 ++++++++++++ indra/alscript/allsloptimizer.cpp | 41 +++++++++++++++++--- indra/alscript/tests/allsloptimizer_test.cpp | 30 ++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/indra/alscript/CMakeLists.txt b/indra/alscript/CMakeLists.txt index 4c35d94137..c71d482eaf 100644 --- a/indra/alscript/CMakeLists.txt +++ b/indra/alscript/CMakeLists.txt @@ -101,6 +101,30 @@ target_sources( target_link_libraries(alscript PRIVATE al::flags ll::slua ll::tailslide llmath PUBLIC llcommon) target_precompile_headers(alscript REUSE_FROM llprecompiled) +# The optimizer folds LSL's floats to what the VM gives and prints them so +# that they read back as themselves: IEEE's arithmetic a step at a time, and +# a zero's sign kept. The viewer's float flags (00-Common.cmake) let the +# compiler reassociate, fuse a product into a sum, take a reciprocal for a +# division and forget a zero's sign -- a vector normalised as a product with +# 1 / mag where the code divides -- so these are built without them, and +# without the precompiled header those flags were part of. +if(WINDOWS) + set(alscript_ieee_options /fp:precise) +else() + set( + alscript_ieee_options + -ffp-contract=off + -fno-associative-math + -fno-reciprocal-math + -fsigned-zeros + ) +endif() +set_source_files_properties( + allslvalues.cpp + allsloptimizer.cpp + PROPERTIES COMPILE_OPTIONS "${alscript_ieee_options}" SKIP_PRECOMPILE_HEADERS TRUE +) + # tests if(AL_BUILD_TESTS) # The tests check real scripts against the grid's own definitions, which diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index 40ee24052d..61d29d9d55 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -1029,6 +1029,25 @@ namespace return std::string(); } + // A vector's length. On LSO and Mono in singles a step at a time, as + // LLVector3 works it out, and as a double rounded once to a single: + // whether the two agree, and what they come to where they do. On Luau, + // whose numbers are doubles, the double's. + bool magnitude(const Ctx& c, float x, float y, float z, double& out) + { + const double once = std::sqrt(double(x) * x + double(y) * y + double(z) * z); + if (c.target == ALLSLOptimizer::Target::Luau) + { + out = once; + return std::isfinite(once); + } + const float xx = x * x, yy = y * y, zz = z * z; + const float sum = (xx + yy) + zz; + const float stepped = std::sqrt(sum); + out = stepped; + return std::isfinite(stepped) && static_cast(static_cast(once)) == static_cast(stepped); + } + typedef std::function Evaluator; const boost::unordered_flat_map>& evaluators() @@ -1118,25 +1137,37 @@ namespace } return c.integer(static_cast(result)); } }, + // A vector's length as LLVector3 has it, in singles a step at a + // time, and as a double rounded once: which the VM does is not + // known, so folded only where the two agree (magnitude). And a + // vector normalised by dividing each part by the length, and by + // the product with 1 / length that LLVector3 makes of a division, + // likewise. { "llVecMag", [](Ctx& c, const Args& a) -> LSLConstant* { Vector3 v; - return argVector(a, 0, v) ? c.number(std::sqrt(double(v.x) * v.x + double(v.y) * v.y + double(v.z) * v.z)) : nullptr; + double mag = 0.0; + return argVector(a, 0, v) && magnitude(c, v.x, v.y, v.z, mag) ? c.number(mag) : nullptr; } }, { "llVecDist", [](Ctx& c, const Args& a) -> LSLConstant* { Vector3 p, q; + double mag = 0.0; if (!argVector(a, 0, p) || !argVector(a, 1, q)) return nullptr; const float dx = p.x - q.x, dy = p.y - q.y, dz = p.z - q.z; - return c.number(std::sqrt(double(dx) * dx + double(dy) * dy + double(dz) * dz)); + return magnitude(c, dx, dy, dz, mag) ? c.number(mag) : nullptr; } }, { "llVecNorm", [](Ctx& c, const Args& a) -> LSLConstant* { Vector3 v; - if (!argVector(a, 0, v)) return nullptr; - const float mag = static_cast(std::sqrt(double(v.x) * v.x + double(v.y) * v.y + double(v.z) * v.z)); + double length = 0.0; + if (!argVector(a, 0, v) || !magnitude(c, v.x, v.y, v.z, length)) return nullptr; + const float mag = static_cast(length); if (mag == 0.0f) return c.vector(0, 0, 0); - return c.vector(v.x / mag, v.y / mag, v.z / mag); + const float x = v.x / mag, y = v.y / mag, z = v.z / mag; + const float over = 1.0f / mag; + if (c.target != ALLSLOptimizer::Target::Luau && (x != v.x * over || y != v.y * over || z != v.z * over)) return nullptr; + return c.vector(x, y, z); } }, // The rotations, by the viewer's own quaternion, which is the // lineage of the simulator's: Euler angles through the diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index c4b7e4c4bc..c9e7150126 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -2090,4 +2090,34 @@ namespace tut ensure("noted: " + notes(r), has(r, "settled llGetListLength(l) < 0 to 0") || has(r, "settled (l != []) < 0 to 0") || has(r, "settled l != [] < 0 to 0")); } } + template<> template<> + void allsloptimizer_object::test<55>() + { + set_test_name("the folding arithmetic is IEEE's whatever the build's float flags: llVecNorm folds where dividing by the length and " + "multiplying by its reciprocal agree, as LLVector3 does, and not where they differ; a negative zero keeps its sign"); + // Each may be set again before it is read, so that its fold shows + // in its declaration. + const std::string source = wrap("", " vector a = llVecNorm(<0.75, 3, 1>);\n vector b = llVecNorm(<0, 3, 4>);\n" + " float m = llVecMag(<0, 3, 4>);\n list l = [-0.0];\n" + " if (llFrand(1) < 0.5)\n {\n a = b = ZERO_VECTOR;\n m = 0;\n l = [];\n }\n" + " llSay(0, (string)a + (string)b + (string)m + llList2CSV(l));\n"); + for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::LSO }) + { + ALLSLOptimizer::Options o = options(); + o.target = target; + const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); + ensure("optimized: " + notes(r), r.optimized); + // 0.75 / 3.25 is 0.230769232 as a single, 0.75 * (1 / 3.25) + // 0.230769247: which the VM gives is not known, so it is left. + // Built with the viewer's float flags, the compiler made the + // division a product with the reciprocal, and this folded. + ensure("left where the two differ: " + r.text, r.text.find("vector a = llVecNorm(<0.75, ") != std::string::npos); + const std::vector b = numbers(r.text, "vector b"); + ensure("folded where they agree: " + r.text, b.size() == 3 && b[0] == 0.0f && b[1] == 3.0f / 5.0f && b[2] == 4.0f / 5.0f); + ensure("a length both ways the same: " + r.text, numbers(r.text, "float m") == std::vector{ 5.0f }); + const size_t at = r.text.find("list l = "); + ensure("a negative zero keeps its sign: " + r.text, + at != std::string::npos && r.text.substr(at, r.text.find('\n', at) - at).find("-0.0") != std::string::npos); + } + } } // namespace tut From df255ac717295a312060d6816717944444179796 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:23:17 -0400 Subject: [PATCH 08/59] The registry's baseline takes Tailslide's vector products and slua's 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 --- indra/alscript/allslvalues.cpp | 13 +++++++------ indra/vcpkg-configuration.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/indra/alscript/allslvalues.cpp b/indra/alscript/allslvalues.cpp index 5c019a6ee9..57502e5dd4 100644 --- a/indra/alscript/allslvalues.cpp +++ b/indra/alscript/allslvalues.cpp @@ -106,12 +106,13 @@ LSLConstant* ALLSLArithmetic::operation(LSLOperator op, LSLConstant* cv, LSLCons break; } } - // Two vectors' dot and cross products, which Tailslide's arithmetic has - // wrong: its dot takes each part by the other's opposite part, x by z, - // and its cross is a product less a product that a compiler may fuse, - // where the VM rounds each. Worked out as a single does it, a step at a - // time, and as a double rounded once, and folded only where the two - // agree. + // Two vectors' dot and cross products, worked out as a single does it, a + // step at a time, and as a double rounded once, and folded only where the + // two agree: LSO works in singles, and Mono's sums are not known to. The + // tailslide port's arithmetic has them as singles now (its + // vector-products.patch: the dot took x by the other's z, and the cross + // let a compiler fuse each product into its difference); that alone says + // nothing of where the two VMs part. if (other && cv->getIType() == LST_VECTOR && other->getIType() == LST_VECTOR && (op == OP_MUL || op == OP_MOD)) { const Vector3* u = static_cast(cv)->getValue(); diff --git a/indra/vcpkg-configuration.json b/indra/vcpkg-configuration.json index 284bbb79a7..fa9bd51eda 100644 --- a/indra/vcpkg-configuration.json +++ b/indra/vcpkg-configuration.json @@ -2,7 +2,7 @@ "registries": [ { "kind": "git", - "baseline": "697db3f88df7938a7b25ab27f9f03268f959aed6", + "baseline": "4dc44e2940f5bd94f3ef1d85e757da532549ca27", "reference": "main", "repository": "https://github.com/AlchemyViewer/alchemy-registry", "packages": [ From b389ff70b489fda7bcccc2f048f83fbdbffd79d7 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:32:00 -0400 Subject: [PATCH 09/59] Bit tests of one value merge into one (L18) 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 --- indra/alscript/allslcosts.cpp | 3 + indra/alscript/allslcosts.h | 4 + indra/alscript/allsloptimizer.cpp | 196 +++++++++++++++++++ indra/alscript/tests/allslcosts_test.cpp | 10 + indra/alscript/tests/allsloptimizer_test.cpp | 37 ++++ 5 files changed, 250 insertions(+) diff --git a/indra/alscript/allslcosts.cpp b/indra/alscript/allslcosts.cpp index e658b78941..b6eb71d81f 100644 --- a/indra/alscript/allslcosts.cpp +++ b/indra/alscript/allslcosts.cpp @@ -53,6 +53,7 @@ namespace c.castForDump = true; c.castForDetail = true; c.castForWholeFloat = false; + c.bitTestsMerged = true; c.local = 11; c.jump = 5; @@ -106,6 +107,7 @@ namespace // script may not have. c.castForDetail = false; c.castForWholeFloat = true; + c.bitTestsMerged = true; c.local = 6; c.jump = 5; @@ -160,6 +162,7 @@ namespace c.castForDump = false; c.castForDetail = false; c.castForWholeFloat = false; + c.bitTestsMerged = true; c.local = 4; c.jump = 93; diff --git a/indra/alscript/allslcosts.h b/indra/alscript/allslcosts.h index f509abd9b1..a249f81f5a 100644 --- a/indra/alscript/allslcosts.h +++ b/indra/alscript/allslcosts.h @@ -100,6 +100,10 @@ struct ALLSLCosts // A float whose value is whole, where LSL converts nothing for it -- a // list's element, what is cast -- as `((float)2)` rather than `2.0`. bool castForWholeFloat = false; + // Bit tests of one value as one: `(x & 4) && (x & 8)` as + // `!~(x | -13)`, `!(x & 4) && !(x & 8)` as `!(x & 12)`, `(x & 4) | (x & 8)` + // as `x & 12`, and an || of them as that where only truth counts. + bool bitTestsMerged = false; // ---- functions, for whether putting one in place saves diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index 61d29d9d55..23b659b31d 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -3650,6 +3650,10 @@ namespace return false; } visitChildren(expr); + if (bitTests(expr)) + { + return false; + } integers(expr); return false; } @@ -3908,6 +3912,194 @@ namespace // What an integer comparison or sum comes to in fewer bytes, as a // value: x == -1 as !~x, x < 0 as !~x of a find, x + 1 as -~x, // x - 1 as ~-x, and two either way. + // Bit tests of one value merged into one (L18), each form smaller on + // every target (ALLSLCosts::bitTestsMerged): a chain of `x & c` one + // bit each, all asked, as `!~(x | ~mask)`; of `!(x & c)` as + // `!(x & mask)`; and an | of `x & c` as `x & mask` -- and, where only + // whether it is true counts, an || of them (anyBit). Each form is + // one this makes, so that a chain merges a link at a time. x is read + // once where it was read at each test: only what is the same at any + // time, an integer. + enum class Test : U8 + { + Set, // x & c, or a !~(x | ~mask) this made: the bits all set + Clear, // !(x & c): the bits all clear + Masked, // x & c, for an |: the bits as they are + }; + std::optional> bitTest(LSLExpression* e, Test kind) const + { + e = bare(e); + if (!e) + { + return std::nullopt; + } + if (kind == Test::Clear || kind == Test::Set) + { + // A ! over the rest: !(x & c), or !~(x | k). + if (e->getNodeSubType() != NODE_UNARY_EXPRESSION || e->getOperation() != OP_BOOLEAN_NOT) + { + if (kind == Test::Clear) + { + return std::nullopt; + } + } + else + { + LSLExpression* inner = bare(static_cast(e)->getChildExpr()); + if (kind == Test::Clear) + { + return bitTest(inner, Test::Masked); + } + if (!inner || inner->getNodeSubType() != NODE_UNARY_EXPRESSION || inner->getOperation() != OP_BIT_NOT) + { + return std::nullopt; + } + const auto set = operand(bare(static_cast(inner)->getChildExpr()), OP_BIT_OR); + return set ? std::optional>({ set->first, ~set->second }) : std::nullopt; + } + } + const auto masked = operand(e, OP_BIT_AND); + // One bit each, for a test that asks it is set. + if (masked && kind == Test::Set && (masked->second == 0 || (masked->second & (masked->second - 1)) != 0)) + { + return std::nullopt; + } + return masked; + } + + // `x op c` or `c op x`: x, an integer that is the same at any time, + // and the constant. + static std::optional> operand(LSLExpression* e, LSLOperator op) + { + if (!e || e->getNodeSubType() != NODE_BINARY_EXPRESSION || e->getOperation() != op || e->getIType() != LST_INTEGER) + { + return std::nullopt; + } + for (int slot = 0; slot < 2; ++slot) + { + LSLConstant* cv = e->getChild(slot)->getConstantValue(); + LSLExpression* x = static_cast(e->getChild(1 - slot)); + if (cv && cv->getNodeSubType() == NODE_INTEGER_CONSTANT && !x->getConstantValue() && x->getIType() == LST_INTEGER && sideEffectFree(x)) + { + return std::make_pair(x, static_cast(static_cast(cv)->getValue())); + } + } + return std::nullopt; + } + + // The tests a chain of `op` is made of, all of one x; their x and + // their bits together. + std::optional> chain(LSLBinaryExpression* expr, LSLOperator op, Test kind) const + { + std::vector leaves; + const std::function gather = [&](LSLExpression* e) { + LSLExpression* inner = bare(e); + if (inner && inner->getNodeSubType() == NODE_BINARY_EXPRESSION && inner->getOperation() == op) + { + gather(static_cast(inner)->getLHS()); + gather(static_cast(inner)->getRHS()); + return; + } + leaves.push_back(e); + }; + gather(expr->getLHS()); + gather(expr->getRHS()); + LSLExpression* x = nullptr; + std::string said; + U32 bits = 0; + for (LSLExpression* leaf : leaves) + { + const auto test = bitTest(leaf, kind); + if (!test) + { + return std::nullopt; + } + const std::string text = render(test->first); + if (x && text != said) + { + return std::nullopt; + } + x = test->first; + said = text; + bits |= test->second; + } + return x ? std::optional>({ x, bits }) : std::nullopt; + } + + // `x op mask`, x taken from where it stands. + LSLExpression* withMask(const Uncounted& uncounted, LSLExpression* x, LSLOperator op, U32 mask, LSLASTNode* at) + { + LSLASTNode* parent = x->getParent(); + parent->takeChild(x->getParentSlot()); + auto* made = uncounted.made(ctx.allocator->newTracked(x, op, constant(ctx.integer(static_cast(mask)), at))); + made->setType(TYPE(LST_INTEGER)); + made->setLoc(at->getLoc()); + return made; + } + + bool bitTests(LSLBinaryExpression* expr) + { + if (!mCosts.bitTestsMerged || inGlobal(expr)) + { + return false; + } + const LSLOperator op = expr->getOperation(); + std::optional> set, clear, masked; + if (op == OP_BOOLEAN_AND) + { + set = chain(expr, op, Test::Set); + clear = set ? std::nullopt : chain(expr, op, Test::Clear); + } + else if (op == OP_BIT_OR) + { + masked = chain(expr, op, Test::Masked); + } + if (!set && !clear && !masked) + { + return false; + } + const std::string before = report.wanted() ? render(expr) : std::string(); + LSLExpression* made = nullptr; + { + const Uncounted uncounted(*ctx.context); + if (set) + { + made = unary(uncounted, OP_BOOLEAN_NOT, unary(uncounted, OP_BIT_NOT, withMask(uncounted, set->first, OP_BIT_OR, ~set->second, expr))); + } + else if (clear) + { + made = unary(uncounted, OP_BOOLEAN_NOT, withMask(uncounted, clear->first, OP_BIT_AND, clear->second, expr)); + } + else + { + made = withMask(uncounted, masked->first, OP_BIT_AND, masked->second, expr); + } + } + made->setLoc(expr->getLoc()); + putInPlace(expr, made, ctx.allocator); + wrote(expr, made, before); + return true; + } + + // An || of `x & c` where only whether it is true counts: `x & mask`. + bool anyBit(LSLBinaryExpression* expr) + { + const auto any = mCosts.bitTestsMerged ? chain(expr, OP_BOOLEAN_OR, Test::Masked) : std::nullopt; + if (!any) + { + return false; + } + const std::string before = report.wanted() ? render(expr) : std::string(); + LSLExpression* made = nullptr; + { + const Uncounted uncounted(*ctx.context); + made = withMask(uncounted, any->first, OP_BIT_AND, any->second, expr); + } + putInPlace(expr, made, ctx.allocator); + wrote(expr, made, before); + return true; + } + void integers(LSLBinaryExpression* expr) { strict(expr); @@ -4013,6 +4205,10 @@ namespace auto* expr = static_cast(inner); LSLExpression* left = expr->getLHS(); LSLExpression* right = expr->getRHS(); + if (expr->getOperation() == OP_BOOLEAN_OR && anyBit(expr)) + { + return; + } if (expr->getOperation() == OP_BOOLEAN_AND || expr->getOperation() == OP_BOOLEAN_OR) { truth(expr, 0); diff --git a/indra/alscript/tests/allslcosts_test.cpp b/indra/alscript/tests/allslcosts_test.cpp index 1ebd41ee35..cdd0cfb6c7 100644 --- a/indra/alscript/tests/allslcosts_test.cpp +++ b/indra/alscript/tests/allslcosts_test.cpp @@ -248,6 +248,16 @@ namespace tut { "a whole float in a list as ((float)2)", { "", "l = [2.0];" }, { "", "l = [(float)2];" }, "><=", &ALLSLCosts::castForWholeFloat }, { "a whole float cast as ((float)2)", { "", "s = (string)2.0;" }, { "", "s = (string)((float)2);" }, "><=", &ALLSLCosts::castForWholeFloat }, // To come. + // Bit tests of one value as one. + { "(a & 4) && (a & 8) as !~(a | -13)", { "", "if ((j & 4) && (j & 8)) i = 2;" }, { "", "if (!~(j | -13)) i = 2;" }, "<<<", + &ALLSLCosts::bitTestsMerged }, + { "the same, a value", { "", "i = (j & 4) && (j & 8);" }, { "", "i = !~(j | -13);" }, "<<<", &ALLSLCosts::bitTestsMerged }, + { "three bits", { "", "if ((j & 4) && (j & 8) && (j & 1)) i = 2;" }, { "", "if (!~(j | -14)) i = 2;" }, "<<<", &ALLSLCosts::bitTestsMerged }, + { "!(a & 4) && !(a & 8) as !(a & 12)", { "", "if (!(j & 4) && !(j & 8)) i = 2;" }, { "", "if (!(j & 12)) i = 2;" }, "<<<", + &ALLSLCosts::bitTestsMerged }, + { "(a & 4) || (a & 8) as a & 12, only truth asked", { "", "if ((j & 4) || (j & 8)) i = 2;" }, { "", "if (j & 12) i = 2;" }, "<<<", + &ALLSLCosts::bitTestsMerged }, + { "(a & 4) | (a & 8) as a & 12", { "", "i = (j & 4) | (j & 8);" }, { "", "i = j & 12;" }, "<<<", &ALLSLCosts::bitTestsMerged }, { "i = i + 1 as ++i", { "", "i = i + 1;" }, { "", "++i;" }, "===", nullptr }, { "-5 as ((integer)-5)", { "", "i = -5;" }, { "", "i = ((integer)-5);" }, "===", nullptr }, { "-5.5 as ((float)-5.5)", { "", "f = -5.5;" }, { "", "f = ((float)-5.5);" }, "=<>", nullptr }, diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index c9e7150126..28a6dd752d 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -2120,4 +2120,41 @@ namespace tut at != std::string::npos && r.text.substr(at, r.text.find('\n', at) - at).find("-0.0") != std::string::npos); } } + template<> template<> + void allsloptimizer_object::test<56>() + { + set_test_name("bit tests of one value merged (L18): one-bit tests all asked as !~(x | ~mask), all clear as !(x & mask), an | of them as " + "x & mask, an || of them so where only truth counts; not of two values, of a test of more than one bit, nor where x changes"); + const std::string source = "integer said() { llOwnerSay(\"said\"); return 4; }\n" + "default\n{\n touch_start(integer n)\n {\n" + " integer m = llGetLinkNumber();\n" + " if ((n & 4) && (n & 8)) llSay(0, \"a\");\n" + " integer v = (n & 4) && (n & 8) && (n & 1);\n" + " if (!(n & 4) && !(n & 8)) llSay(0, \"b\");\n" + " if ((n & 4) || (n & 16)) llSay(0, \"c\");\n" + " integer w = (n & 3) | (n & 12);\n" + " if ((n & 4) && (m & 8)) llSay(0, \"d\");\n" + " if ((n & 6) && (n & 8)) llSay(0, \"e\");\n" + " if ((said() & 4) && (said() & 8)) llSay(0, \"f\");\n" + " llSay(v, (string)w);\n" + " }\n}\n"; + for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::LSO, ALLSLOptimizer::Target::Luau }) + { + ALLSLOptimizer::Options o = options(); + o.target = target; + const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); + ensure("optimized: " + notes(r), r.optimized); + const auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; + ensure("all set: " + r.text, has("if (!~(n | -13))")); + // v and w are read once, and their values go where they are read. + ensure("three, a value: " + r.text, has("llSay(!~(n | -14), ")); + ensure("all clear: " + r.text, has("if (!(n & 12))")); + ensure("any, where only truth counts: " + r.text, has("if (n & 20)")); + ensure("an | of them: " + r.text, has("(string)(n & 15)")); + ensure("two values left: " + r.text, has("(n & 4)") && has("(m & 8)")); + ensure("more than one bit left: " + r.text, has("(n & 6)") && has("(n & 8)")); + ensure("what changes left: " + r.text, has("(said() & 4)") && has("(said() & 8)")); + ensure("noted: " + notes(r), allsloptimizer_data::has(r, "wrote (n & 4) && (n & 8) as !~(n | -13)")); + } + } } // namespace tut From 8f07605ec364caa682ad93a32f220f3a138d4b32 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:34:46 -0400 Subject: [PATCH 10/59] The column selection's commands are registered as ones no menu item gives 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 --- indra/newview/alfloaterscriptstudio.cpp | 48 ++++++++++++------- indra/newview/alfloaterscriptstudio.h | 4 ++ scripts/content_tools/check_script_strings.py | 9 ++-- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/indra/newview/alfloaterscriptstudio.cpp b/indra/newview/alfloaterscriptstudio.cpp index 2c1c043e03..ffbeb9f631 100644 --- a/indra/newview/alfloaterscriptstudio.cpp +++ b/indra/newview/alfloaterscriptstudio.cpp @@ -7893,22 +7893,38 @@ void ALFloaterScriptStudio::addCommands() } void ALFloaterScriptStudio::addEditorCommand(const std::string& name, ALEditorCommand command, bool changes) +{ + editorCommand(name, command, changes, true); +} + +void ALFloaterScriptStudio::addUnlistedEditorCommand(const std::string& name, ALEditorCommand command, bool changes) +{ + editorCommand(name, command, changes, false); +} + +void ALFloaterScriptStudio::editorCommand(const std::string& name, ALEditorCommand command, bool changes, bool listed) { // The view in front's, as every command of the text's own is: the // expansion being read says it cannot, where the source out of // sight would have done it unseen. - mCommands.add( - name, - [this, command]() { - if (Doc* doc = active()) - { - doc->shownText()->perform(command); - } - }, - [this, command, changes]() { - Doc* doc = active(); - return doc && (!changes || doc->modifiable) && doc->shownText()->canPerform(command); - }); + const ALScriptStudioCommands::run_t run = [this, command]() { + if (Doc* doc = active()) + { + doc->shownText()->perform(command); + } + }; + const ALScriptStudioCommands::test_t enabled = [this, command, changes]() { + Doc* doc = active(); + return doc && (!changes || doc->modifiable) && doc->shownText()->canPerform(command); + }; + if (listed) + { + mCommands.add(name, run, enabled); + } + else + { + mCommands.addUnlisted(name, run, enabled); + } } void ALFloaterScriptStudio::addFileCommands() @@ -8134,10 +8150,10 @@ void ALFloaterScriptStudio::addEditCommands() addEditorCommand("add_caret_below", ALEditorCommand::AddCaretBelow, false); // Keys a step at a time rather than menu items: Shift-Alt-drag puts a // column with the mouse. - addEditorCommand("column_select_left", ALEditorCommand::ColumnSelectLeft, false); - addEditorCommand("column_select_right", ALEditorCommand::ColumnSelectRight, false); - addEditorCommand("column_select_up", ALEditorCommand::ColumnSelectUp, false); - addEditorCommand("column_select_down", ALEditorCommand::ColumnSelectDown, false); + addUnlistedEditorCommand("column_select_left", ALEditorCommand::ColumnSelectLeft, false); + addUnlistedEditorCommand("column_select_right", ALEditorCommand::ColumnSelectRight, false); + addUnlistedEditorCommand("column_select_up", ALEditorCommand::ColumnSelectUp, false); + addUnlistedEditorCommand("column_select_down", ALEditorCommand::ColumnSelectDown, false); mCommands.add( "convert_slua", [this]() { diff --git a/indra/newview/alfloaterscriptstudio.h b/indra/newview/alfloaterscriptstudio.h index f8e2321d99..9e7c6a6d55 100644 --- a/indra/newview/alfloaterscriptstudio.h +++ b/indra/newview/alfloaterscriptstudio.h @@ -636,6 +636,10 @@ class ALFloaterScriptStudio final : public ALStudioFloater, public ALScriptStudi void addBuildCommands(); void addHelpCommands(); void addEditorCommand(const std::string& name, ALEditorCommand command, bool changes); + // One of the text's own that no item of the menus gives: reached by its + // keys alone. + void addUnlistedEditorCommand(const std::string& name, ALEditorCommand command, bool changes); + void editorCommand(const std::string& name, ALEditorCommand command, bool changes, bool listed); void onCompileTarget(); // A notecard's grammar picked from the strip. void onNotecardGrammar(); diff --git a/scripts/content_tools/check_script_strings.py b/scripts/content_tools/check_script_strings.py index 80c82b9009..8929430183 100644 --- a/scripts/content_tools/check_script_strings.py +++ b/scripts/content_tools/check_script_strings.py @@ -187,14 +187,15 @@ def read_key_commands(): def read_studio_commands(): """The names the studio's command table is given, as the code gives - them: the first argument of an `add`, `addUnlisted` or - `addEditorCommand` call on the table; and, inside a function named + them: the first argument of an `add`, `addUnlisted`, + `addEditorCommand` or `addUnlistedEditorCommand` call on the table; + and, inside a function named add...Commands, the name that begins each `std::pair{ "name", ... }` or `std::tuple{ "name", ... }` and each name of a `for (const char* x : { "a", "b" })` list, which the loops there register. Returns (listed, unlisted, twice).""" listed, unlisted, twice = [], [], [] - call = re.compile(r'(?:\bcommands\(\)|\bmCommands|\bcommands)\s*(?:\.|->)\s*add(Unlisted)?\(\s*"([a-z_]+)"|\baddEditorCommand\(\s*"([a-z_]+)"') + call = re.compile(r'(?:\bcommands\(\)|\bmCommands|\bcommands)\s*(?:\.|->)\s*add(Unlisted)?\(\s*"([a-z_]+)"|\badd(Unlisted)?EditorCommand\(\s*"([a-z_]+)"') body_start = re.compile(r'\b\w+::add\w*Commands\(\)\s*\{') for path in sorted(glob.glob(os.path.join(ROOT, "indra", "newview", "*.cpp"))): text = open(path, encoding="utf-8", errors="replace").read() @@ -202,7 +203,7 @@ def read_studio_commands(): if m.group(2): (unlisted if m.group(1) else listed).append(m.group(2)) else: - listed.append(m.group(3)) + (unlisted if m.group(3) else listed).append(m.group(4)) for m in body_start.finditer(text): depth, i = 1, m.end() while depth and i < len(text): From 3bdffa59639ae17d2f520e7fd111f53f0ab5d506 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:36:33 -0400 Subject: [PATCH 11/59] Convert to SLua's carrying of the script's comments has a preference 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 --- indra/newview/alfloaterscriptpreferences.cpp | 2 +- indra/newview/alfloaterscriptstudio.cpp | 1 + indra/newview/app_settings/settings_alchemy.xml | 11 +++++++++++ .../default/xui/en/floater_script_studio_prefs.xml | 11 +++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/indra/newview/alfloaterscriptpreferences.cpp b/indra/newview/alfloaterscriptpreferences.cpp index 17b8ca1be8..1d6cd1a5a0 100644 --- a/indra/newview/alfloaterscriptpreferences.cpp +++ b/indra/newview/alfloaterscriptpreferences.cpp @@ -90,7 +90,7 @@ namespace "ALScriptFormatBlankLines", "ALScriptFormatSpacing", "ALScriptFormatOnSave", "ALScriptTrimOnSave", "ALScriptFixOnSave", "ALScriptTemplateLSL", "ALScriptTemplateSLua", "ALScriptPreprocDefines", "ExternalEditor", "ALScriptConvertLLTimers", "ALScriptConvertDetectedTable", "ALScriptConvertSLuaCalls", "ALScriptConvertIdioms", - "ALScriptConvertHandlerFields", "ALScriptConvertTypes", "ALScriptConvertComments", + "ALScriptConvertHandlerFields", "ALScriptConvertTypes", "ALScriptConvertComments", "ALScriptConvertKeepComments", }; // What each preset of Convert to SLua sets: SLua's own ways, or as diff --git a/indra/newview/alfloaterscriptstudio.cpp b/indra/newview/alfloaterscriptstudio.cpp index ffbeb9f631..a6330eda9a 100644 --- a/indra/newview/alfloaterscriptstudio.cpp +++ b/indra/newview/alfloaterscriptstudio.cpp @@ -7603,6 +7603,7 @@ void ALFloaterScriptStudio::convertToSLua(Doc& doc) options.handlers = gSavedSettings.getBOOL("ALScriptConvertHandlerFields") ? ALLSLToSLua::Options::Handlers::Field : ALLSLToSLua::Options::Handlers::On; options.types = gSavedSettings.getBOOL("ALScriptConvertTypes"); options.comments = gSavedSettings.getBOOL("ALScriptConvertComments"); + options.keepComments = gSavedSettings.getBOOL("ALScriptConvertKeepComments"); // Its notes, and its comments, in the skin's words. options.words = alScriptKeyedWords; const std::string source = doc.editor->wholeText(); diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index d77039277a..fca978f53b 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -651,6 +651,17 @@ Value 1 + ALScriptConvertKeepComments + + Comment + Convert to SLua carries the script's own comments over, each over what it stood over: // as --, and /* */ as --[[ ]]. + Persist + 1 + Type + Boolean + Value + 1 + ALScriptStudioAutoClose Comment diff --git a/indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml b/indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml index bb03d33eb1..c032dd5d3c 100644 --- a/indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml +++ b/indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml @@ -1669,6 +1669,17 @@ end tool_tip="Each place SLua means something else than LSL did, or has a way of its own, said over its line. Off: said only in Output." top_pad="4" width="700" /> + Date: Sun, 4 Oct 2026 04:44:05 -0400 Subject: [PATCH 12/59] More of the library folds where the two VMs cannot part (L17) 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 --- indra/alscript/allsloptimizer.cpp | 430 +++++++++++++++++++ indra/alscript/tests/allsloptimizer_test.cpp | 52 +++ 2 files changed, 482 insertions(+) diff --git a/indra/alscript/allsloptimizer.cpp b/indra/alscript/allsloptimizer.cpp index 23b659b31d..9fc614d80b 100644 --- a/indra/alscript/allsloptimizer.cpp +++ b/indra/alscript/allsloptimizer.cpp @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -1029,6 +1030,285 @@ namespace return std::string(); } + // Rotations worked out more than one way, where the VM's own way is not + // known: LLQuaternion's of today and of 2007, in singles a step at a + // time and in doubles. What every way comes to as singles, where they + // all come to the same; nothing where they part. + template struct Quat + { + T x, y, z, s; + }; + + std::optional> agreed(std::initializer_list>> ways) + { + std::optional> out; + for (const std::optional>& way : ways) + { + if (!way) + { + return std::nullopt; + } + const std::array q = { static_cast(way->x), static_cast(way->y), static_cast(way->z), static_cast(way->s) }; + for (float part : q) + { + if (!std::isfinite(part)) + { + return std::nullopt; + } + } + if (out && *out != q) + { + return std::nullopt; + } + out = q; + } + return out; + } + + template std::optional> widened(const std::optional>& q) + { + if (!q) + { + return std::nullopt; + } + return Quat{ double(q->x), double(q->y), double(q->z), double(q->s) }; + } + + // A quaternion made of unit length: today's, always; 2007's only where + // it is more than a part in a million away. + template Quat unitQuat(Quat q, bool nearlyLeft) + { + const T mag = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.s * q.s); + if (mag > T(0.0000001) && (!nearlyLeft || std::fabs(mag - T(1)) > T(0.000001))) + { + const T over = T(1) / mag; + q = { q.x * over, q.y * over, q.z * over, q.s * over }; + } + return q; + } + + // llRotBetween as LLQuaternion::shortestArc has it today: for vectors + // neither parallel nor opposed. + template std::optional> arcToday(const Vector3& u, const Vector3& v) + { + const T ax = u.x, ay = u.y, az = u.z, bx = v.x, by = v.y, bz = v.z; + const T ab = ax * bx + ay * by + az * bz; + const T cx = ay * bz - az * by, cy = az * bx - ax * bz, cz = ax * by - ay * bx; + const T cc = cx * cx + cy * cy + cz * cz; + if (!(ab * ab + cc > T(0)) || !(cc > T(0))) + { + return std::nullopt; + } + const T sum = std::sqrt(ab * ab + cc) + ab; + const T over = T(1) / std::sqrt(cc + sum * sum); + return Quat{ cx * over, cy * over, cz * over, sum * over }; + } + + // And as it had it in 2007: each vector made unit, then the angle from + // its cosine about the cross product made unit. + template std::optional> arc2007(const Vector3& u, const Vector3& v) + { + const auto unit = [](T& x, T& y, T& z) { + const T mag = std::sqrt(x * x + y * y + z * z); + if (mag > T(0.0000001)) + { + const T over = T(1) / mag; + x *= over; + y *= over; + z *= over; + } + return mag; + }; + T ax = u.x, ay = u.y, az = u.z, bx = v.x, by = v.y, bz = v.z; + if (unit(ax, ay, az) < T(0.00001) || unit(bx, by, bz) < T(0.00001)) + { + return std::nullopt; + } + T cx = ay * bz - az * by, cy = az * bx - ax * bz, cz = ax * by - ay * bx; + const T cosine = ax * bx + ay * by + az * bz; + if (cosine > T(1.0 - 0.00001) || cosine < T(-1.0 + 0.00001)) + { + return std::nullopt; + } + const T theta = static_cast(std::acos(double(cosine))); + unit(cx, cy, cz); + const T half = theta * T(0.5); + const T c = std::cos(half), sn = std::sin(half); + return unitQuat(Quat{ cx * sn, cy * sn, cz * sn, c }, true); + } + + // llAngleBetween: twice the arccosine of the rotations' dot product over + // their lengths; and, a way apart, twice the angle of the rotation from + // one to the other, by its arctangent. + template std::optional angleByCosine(const Quaternion& p, const Quaternion& q) + { + const T ax = p.x, ay = p.y, az = p.z, as = p.s, bx = q.x, by = q.y, bz = q.z, bs = q.s; + const T aa = ax * ax + ay * ay + az * az + as * as, bb = bx * bx + by * by + bz * bz + bs * bs; + const T aabb = std::sqrt(aa * bb); + if (!(aabb > T(0))) + { + return std::nullopt; + } + T ab = std::fabs((ax * bx + ay * by + az * bz + as * bs) / aabb); + if (ab > T(1)) + { + ab = T(1); + } + return double(T(2) * std::acos(ab)); + } + + std::optional angleByTangent(const Quaternion& p, const Quaternion& q) + { + const double ax = p.x, ay = p.y, az = p.z, as = p.s, bx = q.x, by = q.y, bz = q.z, bs = q.s; + const double w = as * bs + ax * bx + ay * by + az * bz; + const double vx = as * bx - bs * ax - (ay * bz - az * by); + const double vy = as * by - bs * ay - (az * bx - ax * bz); + const double vz = as * bz - bs * az - (ax * by - ay * bx); + const double v = std::sqrt(vx * vx + vy * vy + vz * vz); + if (!(v > 0.0 || w != 0.0)) + { + return std::nullopt; + } + return 2.0 * std::atan2(v, std::fabs(w)); + } + + // llAxes2Rot as LLMatrix3::quaternion has it where the trace is above + // nought, then made unit as today or as in 2007; and, a way apart, each + // part from its own square root, signed by the matrix. + template std::optional> axesByTrace(const Vector3& f, const Vector3& l, const Vector3& u, bool nearlyLeft) + { + const T m[3][3] = { { f.x, f.y, f.z }, { l.x, l.y, l.z }, { u.x, u.y, u.z } }; + const T tr = m[0][0] + m[1][1] + m[2][2]; + if (!(tr > T(0))) + { + return std::nullopt; + } + T root = std::sqrt(tr + T(1)); + const T w = root * T(0.5); + root = T(0.5) / root; + return unitQuat(Quat{ (m[1][2] - m[2][1]) * root, (m[2][0] - m[0][2]) * root, (m[0][1] - m[1][0]) * root, w }, nearlyLeft); + } + + std::optional> axesByParts(const Vector3& f, const Vector3& l, const Vector3& u) + { + const double m[3][3] = { { f.x, f.y, f.z }, { l.x, l.y, l.z }, { u.x, u.y, u.z } }; + const auto part = [](double square) { return 0.5 * std::sqrt(std::max(0.0, square)); }; + const double w = part(1.0 + m[0][0] + m[1][1] + m[2][2]); + if (!(w > 0.0)) + { + return std::nullopt; + } + return Quat{ std::copysign(part(1.0 + m[0][0] - m[1][1] - m[2][2]), m[1][2] - m[2][1]), + std::copysign(part(1.0 - m[0][0] + m[1][1] - m[2][2]), m[2][0] - m[0][2]), + std::copysign(part(1.0 - m[0][0] - m[1][1] + m[2][2]), m[0][1] - m[1][0]), w }; + } + + // A list's strings, where every element is one: what llParseString2List + // is given to split at. + std::optional> strings(LSLListConstant* list) + { + std::vector out; + for (LSLConstant* item : elements(list)) + { + if (item->getNodeSubType() != NODE_STRING_CONSTANT) + { + return std::nullopt; + } + out.emplace_back(static_cast(item)->getValue()); + } + return out; + } + + // llParseString2List's and llParseStringKeepNulls' splitting: at each + // place the separator or spacer found there, the separator dropped and + // the spacer kept; what is between, empty or not as `nulls` says. Only + // where which one is found cannot be in question -- at most eight of + // each, which is as many as the VM reads, none empty and none the start + // of another -- and where everything is ASCII. + std::optional> parsed(const std::string& src, const std::vector& separators, + const std::vector& spacers, bool nulls) + { + if (src.empty() || separators.size() > 8 || spacers.size() > 8 || !ascii(src.c_str())) + { + return std::nullopt; + } + std::vector> marks; // the mark, and whether it is kept + for (const std::string& sep : separators) + { + marks.emplace_back(sep, false); + } + for (const std::string& spacer : spacers) + { + marks.emplace_back(spacer, true); + } + for (const auto& [mark, kept] : marks) + { + if (mark.empty() || !ascii(mark.c_str())) + { + return std::nullopt; + } + for (const auto& [other, also] : marks) + { + if (&other != &mark && other.size() >= mark.size() && other.compare(0, mark.size(), mark) == 0) + { + return std::nullopt; + } + } + } + std::vector out; + const auto piece = [&](const std::string& text) { + if (nulls || !text.empty()) + { + out.push_back(text); + } + }; + size_t from = 0; + for (size_t at = 0; at < src.size();) + { + const auto found = std::find_if(marks.begin(), marks.end(), [&](const auto& m) { return src.compare(at, m.first.size(), m.first) == 0; }); + if (found == marks.end()) + { + ++at; + continue; + } + piece(src.substr(from, at - from)); + if (found->second) + { + out.push_back(found->first); + } + at += found->first.size(); + from = at; + } + piece(src.substr(from)); + return out; + } + + // Strings llListSort orders the same by their code points as by any + // culture's rules: letters and digits only, and the letters of all of + // them of one case -- "B" comes before "a" by code point, after it by a + // culture's. What letters a string has is told into `lower` and + // `upper`; false for one that has anything else. + bool plainSortKey(const char* s, bool& lower, bool& upper) + { + for (; *s; ++s) + { + const unsigned char ch = static_cast(*s); + if (ch >= 'a' && ch <= 'z') + { + lower = true; + } + else if (ch >= 'A' && ch <= 'Z') + { + upper = true; + } + else if (ch < '0' || ch > '9') + { + return false; + } + } + return !(lower && upper); + } + // A vector's length. On LSO and Mono in singles a step at a time, as // LLVector3 works it out, and as a double rounded once to a single: // whether the two agree, and what they come to where they do. On Luau, @@ -1224,6 +1504,42 @@ namespace const LLVector3 v = LLVector3(1.f, 0.f, 0.f) * q; return c.vector(v.mV[VX], v.mV[VY], v.mV[VZ]); } }, + // Three more by more than one way each (agreed): left where the + // ways part, which an answer near a parallel, a half turn or + // nothing does. + { "llRotBetween", + [](Ctx& c, const Args& a) -> LSLConstant* { + Vector3 u, v; + if (!argVector(a, 0, u) || !argVector(a, 1, v)) return nullptr; + const auto q = agreed({ widened(arcToday(u, v)), arcToday(u, v), widened(arc2007(u, v)), arc2007(u, v) }); + return q ? c.rotation((*q)[0], (*q)[1], (*q)[2], (*q)[3]) : nullptr; + } }, + { "llAngleBetween", + [](Ctx& c, const Args& a) -> LSLConstant* { + // Luau's answer is a double, by a way not known. + if (c.target == ALLSLOptimizer::Target::Luau || a.size() != 2 || a[0]->getNodeSubType() != NODE_QUATERNION_CONSTANT || + a[1]->getNodeSubType() != NODE_QUATERNION_CONSTANT) + { + return nullptr; + } + const Quaternion& p = *static_cast(a[0])->getValue(); + const Quaternion& q = *static_cast(a[1])->getValue(); + std::optional out; + for (const std::optional& way : { angleByCosine(p, q), angleByCosine(p, q), angleByTangent(p, q) }) + { + if (!way || !std::isfinite(static_cast(*way)) || (out && *out != static_cast(*way))) return nullptr; + out = static_cast(*way); + } + return c.number(*out); + } }, + { "llAxes2Rot", + [](Ctx& c, const Args& a) -> LSLConstant* { + Vector3 f, l, u; + if (!argVector(a, 0, f) || !argVector(a, 1, l) || !argVector(a, 2, u)) return nullptr; + const auto q = agreed({ widened(axesByTrace(f, l, u, false)), widened(axesByTrace(f, l, u, true)), + axesByTrace(f, l, u, false), axesByParts(f, l, u) }); + return q ? c.rotation((*q)[0], (*q)[1], (*q)[2], (*q)[3]) : nullptr; + } }, { "llRot2Left", [](Ctx& c, const Args& a) -> LSLConstant* { LLQuaternion q; @@ -1430,6 +1746,120 @@ namespace if (!argList(a, 0, l) || !argInt(a, 1, start) || !argInt(a, 2, end)) return nullptr; return listOf(c, subRange(elements(l), start, end)); } }, + // A list split, as LSL splits it, where nothing could make the + // two VMs split it otherwise (parsed): no spacers for the one + // that keeps what is empty, whose empties beside a spacer are + // not known. + { "llParseString2List", + [](Ctx& c, const Args& a) -> LSLConstant* { + std::string src; + LSLListConstant *seps, *spacers; + if (!argString(a, 0, src) || !argList(a, 1, seps) || !argList(a, 2, spacers)) return nullptr; + const auto sepText = strings(seps), spacerText = strings(spacers); + if (!sepText || !spacerText) return nullptr; + const auto pieces = parsed(src, *sepText, *spacerText, false); + if (!pieces) return nullptr; + std::vector out; + for (const std::string& piece : *pieces) + { + LSLConstant* item = c.string(piece); + if (!item) return nullptr; + out.push_back(item); + } + return listOf(c, out); + } }, + { "llParseStringKeepNulls", + [](Ctx& c, const Args& a) -> LSLConstant* { + std::string src; + LSLListConstant *seps, *spacers; + if (!argString(a, 0, src) || !argList(a, 1, seps) || !argList(a, 2, spacers) || spacers->getLength() != 0) return nullptr; + const auto sepText = strings(seps); + if (!sepText) return nullptr; + const auto pieces = parsed(src, *sepText, {}, true); + if (!pieces) return nullptr; + std::vector out; + for (const std::string& piece : *pieces) + { + LSLConstant* item = c.string(piece); + if (!item) return nullptr; + out.push_back(item); + } + return listOf(c, out); + } }, + // Comma-separated values as strings: only where no value is + // empty, starts or ends with a space, or holds the angle + // brackets that keep a vector's commas in -- what the two VMs + // are not known to treat alike. + { "llCSV2List", + [](Ctx& c, const Args& a) -> LSLConstant* { + std::string src; + if (!argString(a, 0, src) || src.empty() || src.find_first_of("<>") != std::string::npos) return nullptr; + const auto pieces = parsed(src, { "," }, {}, true); + if (!pieces) return nullptr; + std::vector out; + for (const std::string& piece : *pieces) + { + if (piece.empty() || std::isspace(static_cast(piece.front())) || + std::isspace(static_cast(piece.back()))) + { + return nullptr; + } + LSLConstant* item = c.string(piece); + if (!item) return nullptr; + out.push_back(item); + } + return listOf(c, out); + } }, + // A list sorted by every stride-th element, ascending or not: + // only where the order cannot be the sort's to choose -- the + // keys all of one type, integers, floats or strings a culture + // orders as their code points do (plainSortKey), and no two the + // same, so that a sort that is not stable sorts them alike -- + // and the stride divides the list. + { "llListSort", + [](Ctx& c, const Args& a) -> LSLConstant* { + LSLListConstant* l; + int stride, ascending; + if (!argList(a, 0, l) || !argInt(a, 1, stride) || !argInt(a, 2, ascending) || (ascending != 0 && ascending != 1)) return nullptr; + const std::vector items = elements(l); + if (stride < 1 || items.size() % static_cast(stride) != 0 || items.empty()) return nullptr; + const LSLNodeSubType kind = items.front()->getNodeSubType(); + if (kind != NODE_INTEGER_CONSTANT && kind != NODE_FLOAT_CONSTANT && kind != NODE_STRING_CONSTANT) return nullptr; + std::vector groups; + bool lower = false, upper = false; + for (size_t g = 0; g < items.size(); g += static_cast(stride)) + { + LSLConstant* key = items[g]; + if (key->getNodeSubType() != kind) return nullptr; + if (kind == NODE_FLOAT_CONSTANT && !std::isfinite(static_cast(key)->getValue())) return nullptr; + if (kind == NODE_STRING_CONSTANT && !plainSortKey(static_cast(key)->getValue(), lower, upper)) return nullptr; + groups.push_back(g); + } + const auto less = [&](size_t x, size_t y) { + LSLConstant* p = items[x]; + LSLConstant* q = items[y]; + switch (kind) + { + case NODE_INTEGER_CONSTANT: + return static_cast(p)->getValue() < static_cast(q)->getValue(); + case NODE_FLOAT_CONSTANT: + return static_cast(p)->getValue() < static_cast(q)->getValue(); + default: + return strcmp(static_cast(p)->getValue(), static_cast(q)->getValue()) < 0; + } + }; + std::sort(groups.begin(), groups.end(), [&](size_t x, size_t y) { return ascending ? less(x, y) : less(y, x); }); + for (size_t i = 1; i < groups.size(); ++i) + { + if (!less(groups[i - 1], groups[i]) && !less(groups[i], groups[i - 1])) return nullptr; + } + std::vector out; + for (size_t g : groups) + { + out.insert(out.end(), items.begin() + static_cast(g), items.begin() + static_cast(g) + stride); + } + return listOf(c, out); + } }, { "llDeleteSubList", [](Ctx& c, const Args& a) -> LSLConstant* { LSLListConstant* l; diff --git a/indra/alscript/tests/allsloptimizer_test.cpp b/indra/alscript/tests/allsloptimizer_test.cpp index 28a6dd752d..324456a787 100644 --- a/indra/alscript/tests/allsloptimizer_test.cpp +++ b/indra/alscript/tests/allsloptimizer_test.cpp @@ -2157,4 +2157,56 @@ namespace tut ensure("noted: " + notes(r), allsloptimizer_data::has(r, "wrote (n & 4) && (n & 8) as !~(n | -13)")); } } + template<> template<> + void allsloptimizer_object::test<57>() + { + set_test_name("more of the library folded where the two VMs cannot part (L17): parsing, CSV and sorting where no edge is in play; a " + "rotation between vectors, the angle between rotations and a rotation from axes where every way of working it out agrees"); + // Each may be set again before it is read, so that its fold shows + // in its declaration. + const std::string source = wrap("", " list p = llParseString2List(\"a,b;;c\", [\",\"], [\";\"]);\n" + " list k = llParseStringKeepNulls(\"a,,b,\", [\",\"], []);\n" + " list x = llParseString2List(\"ab\", [\"a\", \"ab\"], []);\n" + " list v = llCSV2List(\"a,b,c\");\n" + " list w = llCSV2List(\"a, b\");\n" + " list s = llListSort([3, \"c\", 1, \"a\", 2, \"b\"], 2, TRUE);\n" + " list t = llListSort([\"pear\", \"apple\", \"fig\"], 1, FALSE);\n" + " list u = llListSort([2, 1, 2], 1, TRUE);\n" + " list m = llListSort([\"b\", \"A\"], 1, TRUE);\n" + " rotation r = llRotBetween(<1, 0, 0>, <0, 1, 0>);\n" + " rotation q = llRotBetween(<1, 0, 0>, <1, 0, 0>);\n" + " rotation n = llRotBetween(<1, 0, 0>, <0, 0, 2>);\n" + " float h = llAngleBetween(<0, 0, 0, 1>, <0, 0, 0, 1>);\n" + " rotation z = llAxes2Rot(<1, 0, 0>, <0, 1, 0>, <0, 0, 1>);\n" + " rotation e = llAxes2Rot(<0, 1, 0>, <-1, 0, 0>, <0, 0, 1>);\n" + " if (llFrand(1) < 0.5)\n {\n p = k = x = v = w = s = t = u = m = [];\n" + " r = q = n = z = e = ZERO_ROTATION;\n h = 1;\n }\n" + " llOwnerSay(llList2CSV(p + k + x + v + w + s + t + u + m) + (string)[r, q, n, h, z, e]);\n"); + for (const ALLSLOptimizer::Target target : { ALLSLOptimizer::Target::Mono, ALLSLOptimizer::Target::LSO }) + { + ALLSLOptimizer::Options o = options(); + o.target = target; + const ALLSLOptimizer::Result r = ALLSLOptimizer::run(source, o); + ensure("optimized: " + notes(r), r.optimized); + const auto has = [&r](const char* text) { return r.text.find(text) != std::string::npos; }; + ensure("split, spacers kept: " + r.text, has("list p = [\"a\", \"b\", \";\", \";\", \"c\"];")); + ensure("split, empties kept: " + r.text, has("list k = [\"a\", \"\", \"b\", \"\"];")); + ensure("a separator the start of another: left: " + r.text, has("list x = llParseString2List(")); + ensure("CSV: " + r.text, has("list v = [\"a\", \"b\", \"c\"];")); + ensure("a value starting with a space: left: " + r.text, has("list w = llCSV2List(")); + ensure("sorted by stride: " + r.text, has("list s = [1, \"a\", 2, \"b\", 3, \"c\"];")); + ensure("sorted down: " + r.text, has("list t = [\"pear\", \"fig\", \"apple\"];")); + ensure("two keys the same: left: " + r.text, has("list u = llListSort(")); + ensure("keys of two cases: left: " + r.text, has("list m = llListSort(")); + const float half = static_cast(std::sqrt(0.5)); + ensure("a quarter turn between: " + r.text, numbers(r.text, "rotation r") == std::vector{ 0.f, 0.f, half, half }); + ensure("parallel: left: " + r.text, has("rotation q = llRotBetween(")); + ensure("a quarter turn the other way: " + r.text, numbers(r.text, "rotation n") == std::vector{ 0.f, -half, 0.f, half }); + ensure("no angle between the same: " + r.text, numbers(r.text, "float h") == std::vector{ 0.f }); + ensure("the axes themselves: " + r.text, numbers(r.text, "rotation z") == std::vector{ 0.f, 0.f, 0.f, 1.f }); + // Made unit, as LLQuaternion does today, a quarter turn is a bit + // larger in its last place than left as it was, as in 2007. + ensure("ways that part by a bit: left: " + r.text, has("rotation e = llAxes2Rot(")); + } + } } // namespace tut From 3dbbfc4403e0338d5b73a55cd2772bdec081a44e Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 04:45:05 -0400 Subject: [PATCH 13/59] A jump on LSL on Luau costs what it costs, every run 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 --- indra/alscript/allslcosts.cpp | 2 +- indra/alscript/tests/allslcosts_test.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/indra/alscript/allslcosts.cpp b/indra/alscript/allslcosts.cpp index b6eb71d81f..56dcb91b3d 100644 --- a/indra/alscript/allslcosts.cpp +++ b/indra/alscript/allslcosts.cpp @@ -165,7 +165,7 @@ namespace c.bitTestsMerged = true; c.local = 4; - c.jump = 93; + c.jump = 92; c.function = 37; c.functionChar = 1; c.parameter = 0; diff --git a/indra/alscript/tests/allslcosts_test.cpp b/indra/alscript/tests/allslcosts_test.cpp index cdd0cfb6c7..7efaf7a9ee 100644 --- a/indra/alscript/tests/allslcosts_test.cpp +++ b/indra/alscript/tests/allslcosts_test.cpp @@ -309,9 +309,7 @@ namespace tut const Delta jump = delta(t, { "", "jump L; @L;" }, { "", "" }); said("a jump", t, jump, -c.jump); - // What Luau's compiler makes of a jump is a byte more in some - // runs than in others, the same build and the same text. - ensure("a jump" + where + ": " + std::to_string(-jump.first), t == Target::Luau ? std::abs(-jump.first - c.jump) <= 1 : -jump.first == c.jump); + ensure("a jump" + where + ": " + std::to_string(-jump.first), -jump.first == c.jump); const Delta f2 = delta(t, none, two); const Delta f18 = delta(t, none, eighteen); From aade1e140377ce2983cfde8bdcf2ea5d20e8d716 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 05:01:43 -0400 Subject: [PATCH 14/59] Convert to SLua signs bit32's answers with SLua's own bit32.s32 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 --- indra/alscript/allsltoslua.cpp | 24 ++++++----------------- indra/alscript/tests/allsltoslua_test.cpp | 16 +++++++-------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/indra/alscript/allsltoslua.cpp b/indra/alscript/allsltoslua.cpp index 9cba109b65..7a304fb96b 100644 --- a/indra/alscript/allsltoslua.cpp +++ b/indra/alscript/allsltoslua.cpp @@ -179,7 +179,7 @@ namespace "torotation", "touuid", "ipairs", "pairs", "next", "select", "error", "assert", "pcall", "xpcall", "unpack", "rawget", "rawset", "rawequal", "rawlen", "setmetatable", "getmetatable", "require", "lljson", "llbase64", // What the text written here defines of its own. - "states", "currentState", "setState", "joinLists", "int32", "lslInteger", "lslFloat", "detected", "setTimer", "timerHandle", + "states", "currentState", "setState", "joinLists", "lslInteger", "lslFloat", "detected", "setTimer", "timerHandle", "timerHandler", }; return RESERVED.contains(name); @@ -666,7 +666,6 @@ namespace // nought. boost::unordered_flat_set mSteadyNonNegative; bool mJoinLists = false; - bool mInt32 = false; bool mLslInteger = false; bool mLslFloat = false; bool mManyStates = false; @@ -1914,8 +1913,7 @@ namespace { return { call }; } - mInt32 = true; - return { "int32(" + call + ")" }; + return { "bit32.s32(" + call + ")" }; } bool Writer::notBelowZero(LSLExpression* e) const @@ -2005,10 +2003,10 @@ namespace } const auto bit = [&](const char* fn) -> Expr { // bit32 answers 0 to 4294967295, where LSL's integers were signed: - // made LSL's (int32), but where the answer is the same number -- - // an & with a number not below nought, an | or ^ of two, a >> of - // one -- or where it is only asked whether it is nought, or - // bit32 takes it again. + // made LSL's by bit32.s32, but where the answer is the same + // number -- an & with a number not below nought, an | or ^ of + // two, a >> of one -- or where it is only asked whether it is + // nought, or bit32 takes it again. const bool same = (op == OP_BIT_AND && (notBelowZero(lhs) || notBelowZero(rhs))) || ((op == OP_BIT_OR || op == OP_BIT_XOR) && notBelowZero(lhs) && notBelowZero(rhs)) || (op == OP_SHIFT_RIGHT && notBelowZero(lhs)) || truthOnly(e) || intoBit32(e); @@ -5174,16 +5172,6 @@ local function joinLists(a: { any }, b: { any }): { any } return out end -)LUA"; - } - if (mInt32) - { - out += R"LUA(-- What bit32 answers, 0 to 4294967295, as LSL's integers were: signed, from --- -2147483648. -local function int32(n: number): number - return if n >= 0x80000000 then n - 0x100000000 else n -end - )LUA"; } if (mLslInteger) diff --git a/indra/alscript/tests/allsltoslua_test.cpp b/indra/alscript/tests/allsltoslua_test.cpp index 2040bf85e1..c9389d8cee 100644 --- a/indra/alscript/tests/allsltoslua_test.cpp +++ b/indra/alscript/tests/allsltoslua_test.cpp @@ -198,7 +198,7 @@ namespace tut ensure("lists compared by length: " + r.text, has(r, "#l == #{1}") && noted(r, "SluaListCompare")); // bit32 answers 0 to 4294967295: made LSL's signed integer, but where // bit32 takes it again. - ensure("bits: " + r.text, has(r, "int32(bit32.bor(bit32.band(a, b), 4))") && has(r, "local function int32")); + ensure("bits: " + r.text, has(r, "bit32.s32(bit32.bor(bit32.band(a, b), 4))") && !has(r, "local function int32")); ensure("a vector's part set: " + r.text, has(r, "v = vector(4, v.y, v.z)") && has(r, "v = vector(v.x, v.y, v.z + 1)")); ensure("a vector as LSL writes it: " + r.text, has(r, "ll.DumpList2String({v}, \"\")")); checksClean(r); @@ -773,7 +773,7 @@ namespace tut " llOwnerSay(llList2String(l, what) + (string)m);\n" " if ((what & 3) && sayIt()) llDie();\n" "} }\n"); - ensure("bit32's range made LSL's where it shows: " + r.text, has(r, "local m = int32(bit32.bor(what, ") && !has(r, "-- LSL: bit32")); + ensure("bit32's range made LSL's where it shows: " + r.text, has(r, "local m = bit32.s32(bit32.bor(what, ") && !has(r, "-- LSL: bit32")); ensure("not for a flag's truth: " + r.text, has(r, "if bit32.band(what, CHANGED_LINK) ~= 0")); ensure("&& said once, over a call that does something: " + r.text, count(r, "leaves its right side unrun") == 1 && has(r, "LSL ran both sides, the right one first.\n if bit32.band(what, 3) ~= 0 and sayIt()")); @@ -1143,16 +1143,16 @@ namespace tut " llOwnerSay((string)(inverse + high + low + part));\n" " }\n" "}\n"); - ensure("the mask's answer signed: " + r.text, has(r, "if int32(bit32.band(num, MASK)) == MSG then")); - ensure("the number sent signed: " + r.text, has(r, "ll.MessageLinked(LINK_SET, int32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); + ensure("the mask's answer signed: " + r.text, has(r, "if bit32.s32(bit32.band(num, MASK)) == MSG then")); + ensure("the number sent signed: " + r.text, has(r, "ll.MessageLinked(LINK_SET, bit32.s32(bit32.bor(MSG, FN_RESET)), \"\", \"\")")); ensure("an & with a constant not below nought as it is: " + r.text, has(r, "local fn = bit32.band(num, FN_MASK)\n")); - ensure("~ signed: " + r.text, has(r, "local inverse = int32(bit32.bnot(num))")); - ensure("<< signed: " + r.text, has(r, "local high = int32(bit32.lshift(num, 4))")); - ensure(">> of what may be below nought signed: " + r.text, has(r, "local low = int32(bit32.arshift(num, 2))")); + ensure("~ signed: " + r.text, has(r, "local inverse = bit32.s32(bit32.bnot(num))")); + ensure("<< signed: " + r.text, has(r, "local high = bit32.s32(bit32.lshift(num, 4))")); + ensure(">> of what may be below nought signed: " + r.text, has(r, "local low = bit32.s32(bit32.arshift(num, 2))")); ensure(">> of a constant not below nought as it is: " + r.text, has(r, "local part = bit32.arshift(FN_MASK, 8)\n")); ensure("a truth, and what bit32 takes again, as they are: " + r.text, has(r, "if bit32.band(bit32.bnot(ll.GetPermissions()), PERMISSION_TRIGGER_ANIMATION) ~= 0 then")); - ensure("the helper, once: " + r.text, count(r, "local function int32(n: number): number") == 1 && !has(r, "-- LSL: bit32")); + ensure("SLua's own bit32.s32, no helper: " + r.text, !has(r, "local function int32") && !has(r, "-- LSL: bit32")); checksClean(r); } From f644eaad361e9b4322b7d132b92d507ae10021e4 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 4 Oct 2026 05:03:47 -0400 Subject: [PATCH 15/59] LL's LSL compiler, restored as it was (L1) The files of LL's compiler as they stood before ca08bd5aba 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 ca08bd5aba^ -- 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 --- indra/lscript/CMakeLists.txt | 21 + indra/lscript/lscript_alloc.h | 293 + indra/lscript/lscript_byteconvert.h | 1170 ++ indra/lscript/lscript_byteformat.h | 566 + indra/lscript/lscript_compile/CMakeLists.txt | 157 + indra/lscript/lscript_compile/bison.bat | 12 + indra/lscript/lscript_compile/indra.l | 1013 ++ indra/lscript/lscript_compile/indra.y | 1791 +++ .../lscript/lscript_compile/lscript_alloc.cpp | 26 + .../lscript_compile/lscript_bytecode.cpp | 317 + .../lscript_compile/lscript_bytecode.h | 90 + .../lscript/lscript_compile/lscript_error.cpp | 103 + indra/lscript/lscript_compile/lscript_error.h | 152 + .../lscript/lscript_compile/lscript_heap.cpp | 67 + indra/lscript/lscript_compile/lscript_heap.h | 58 + .../lscript_compile/lscript_resource.cpp | 36 + .../lscript_compile/lscript_resource.h | 37 + .../lscript/lscript_compile/lscript_scope.cpp | 31 + indra/lscript/lscript_compile/lscript_scope.h | 401 + .../lscript/lscript_compile/lscript_tree.cpp | 10895 ++++++++++++++++ indra/lscript/lscript_compile/lscript_tree.h | 2325 ++++ .../lscript_compile/lscript_typecheck.cpp | 586 + .../lscript_compile/lscript_typecheck.h | 118 + .../lscript/lscript_compile/windows/unistd.h | 24 + indra/lscript/lscript_export.h | 34 + indra/lscript/lscript_http.h | 45 + indra/lscript/lscript_library.h | 427 + indra/lscript/lscript_library/CMakeLists.txt | 35 + .../lscript/lscript_library/lscript_alloc.cpp | 1136 ++ .../lscript_library/lscript_export.cpp | 26 + .../lscript_library/lscript_library.cpp | 582 + 31 files changed, 22574 insertions(+) create mode 100755 indra/lscript/CMakeLists.txt create mode 100755 indra/lscript/lscript_alloc.h create mode 100755 indra/lscript/lscript_byteconvert.h create mode 100755 indra/lscript/lscript_byteformat.h create mode 100755 indra/lscript/lscript_compile/CMakeLists.txt create mode 100644 indra/lscript/lscript_compile/bison.bat create mode 100755 indra/lscript/lscript_compile/indra.l create mode 100755 indra/lscript/lscript_compile/indra.y create mode 100755 indra/lscript/lscript_compile/lscript_alloc.cpp create mode 100755 indra/lscript/lscript_compile/lscript_bytecode.cpp create mode 100755 indra/lscript/lscript_compile/lscript_bytecode.h create mode 100755 indra/lscript/lscript_compile/lscript_error.cpp create mode 100755 indra/lscript/lscript_compile/lscript_error.h create mode 100755 indra/lscript/lscript_compile/lscript_heap.cpp create mode 100755 indra/lscript/lscript_compile/lscript_heap.h create mode 100755 indra/lscript/lscript_compile/lscript_resource.cpp create mode 100755 indra/lscript/lscript_compile/lscript_resource.h create mode 100755 indra/lscript/lscript_compile/lscript_scope.cpp create mode 100755 indra/lscript/lscript_compile/lscript_scope.h create mode 100755 indra/lscript/lscript_compile/lscript_tree.cpp create mode 100755 indra/lscript/lscript_compile/lscript_tree.h create mode 100755 indra/lscript/lscript_compile/lscript_typecheck.cpp create mode 100755 indra/lscript/lscript_compile/lscript_typecheck.h create mode 100755 indra/lscript/lscript_compile/windows/unistd.h create mode 100755 indra/lscript/lscript_export.h create mode 100755 indra/lscript/lscript_http.h create mode 100755 indra/lscript/lscript_library.h create mode 100755 indra/lscript/lscript_library/CMakeLists.txt create mode 100755 indra/lscript/lscript_library/lscript_alloc.cpp create mode 100755 indra/lscript/lscript_library/lscript_export.cpp create mode 100755 indra/lscript/lscript_library/lscript_library.cpp diff --git a/indra/lscript/CMakeLists.txt b/indra/lscript/CMakeLists.txt new file mode 100755 index 0000000000..937e2ec0dc --- /dev/null +++ b/indra/lscript/CMakeLists.txt @@ -0,0 +1,21 @@ +# -*- cmake -*- + +set(lscript_HEADER_FILES + llscriptresource.h + llscriptresourceconsumer.h + llscriptresourcepool.h + lscript_alloc.h + lscript_byteconvert.h + lscript_byteformat.h + lscript_execute.h + lscript_export.h + lscript_http.h + lscript_library.h + lscript_rt_interface.h + ) + +add_subdirectory(lscript_compile) +add_subdirectory(lscript_execute) + +add_subdirectory(lscript_library) + diff --git a/indra/lscript/lscript_alloc.h b/indra/lscript/lscript_alloc.h new file mode 100755 index 0000000000..f8a4e298d2 --- /dev/null +++ b/indra/lscript/lscript_alloc.h @@ -0,0 +1,293 @@ +/** + * @file lscript_alloc.h + * @brief General heap management for scripting system + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LSCRIPT_ALLOC_H +#define LL_LSCRIPT_ALLOC_H +// #define at top of file accelerates gcc compiles +// Under gcc 2.9, the manual is unclear if comments can appear above #ifndef +// Under gcc 3, the manual explicitly states comments can appear above the #ifndef + +#include "lscript_byteconvert.h" +#include "lscript_library.h" + +void reset_hp_to_safe_spot(const U8 *buffer); + + +// supported data types + +// basic types +// integer 4 bytes of integer data +// float 4 bytes of float data +// string data null terminated 1 byte string +// key data null terminated 1 byte string +// vector data 12 bytes of 3 floats +// quaternion data 16 bytes of 4 floats + +// list type +// list data 4 bytes of number of entries followed by followed by pointer + +// string pointer 4 bytes of address of string data on the heap (only used in list data) +// key pointer 4 bytes of address of key data on the heap (only used in list data) + +// heap format +// +// 4 byte offset to next block (in bytes) +// 1 byte of type of variable or empty +// 2 bytes of reference count +// nn bytes of data + +const S32 MAX_HEAP_SIZE = TOP_OF_MEMORY; + +class LLScriptAllocEntry +{ +public: + LLScriptAllocEntry() : mSize(0), mType(LST_NULL), mReferenceCount(0) {} + LLScriptAllocEntry(S32 offset, U8 type) : mSize(offset), mType(type), mReferenceCount(1) {} + friend std::ostream& operator<<(std::ostream& s, const LLScriptAllocEntry &a) + { + s << "Size: " << a.mSize << " Type: " << LSCRIPTTypeNames[a.mType] << " Count: " << a.mReferenceCount; + return s; + } + + S32 mSize; + U8 mType; + S16 mReferenceCount; +}; + +// this is only OK because we only load/save via accessors below +const S32 SIZEOF_SCRIPT_ALLOC_ENTRY = 7; + +inline void alloc_entry2bytestream(U8 *buffer, S32 &offset, const LLScriptAllocEntry &entry) +{ + if ( (offset < 0) + ||(offset > MAX_HEAP_SIZE)) + { + set_fault(buffer, LSRF_BOUND_CHECK_ERROR); + } + else + { + integer2bytestream(buffer, offset, entry.mSize); + byte2bytestream(buffer, offset, entry.mType); + s162bytestream(buffer, offset, entry.mReferenceCount); + } +} + +inline void bytestream2alloc_entry(LLScriptAllocEntry &entry, U8 *buffer, S32 &offset) +{ + if ( (offset < 0) + ||(offset > MAX_HEAP_SIZE)) + { + set_fault(buffer, LSRF_BOUND_CHECK_ERROR); + reset_hp_to_safe_spot(buffer); + } + else + { + entry.mSize = bytestream2integer(buffer, offset); + entry.mType = bytestream2byte(buffer, offset); + entry.mReferenceCount = bytestream2s16(buffer, offset); + } +} + +// create a heap from the HR to TM +BOOL lsa_create_heap(U8 *heap_start, S32 size); +void lsa_fprint_heap(U8 *buffer, LLFILE *fp); + +void lsa_print_heap(U8 *buffer); + +// adding to heap +// if block is empty +// if block is at least block size + 4 larger than data +// split block +// insert data into first part +// return address +// else +// insert data into block +// return address +// else +// if next block is >= SP +// set Stack-Heap collision +// return NULL +// if next block is empty +// merge next block with current block +// go to start of algorithm +// else +// move to next block +// go to start of algorithm + +S32 lsa_heap_add_data(U8 *buffer, LLScriptLibData *data, S32 heapsize, BOOL b_delete); + +S32 lsa_heap_top(U8 *heap_start, S32 maxsize); + +// split block +// set offset to point to new block +// set offset of new block to point to original offset - block size - data size +// set new block to empty +// set new block reference count to 0 +void lsa_split_block(U8 *buffer, S32 &offset, S32 size, LLScriptAllocEntry &entry); + +// insert data +// if data is non-list type +// set type to basic type, set reference count to 1, copy data, return address +// else +// set type to list data type, set reference count to 1 +// for each list entry +// insert data +// return address + +void lsa_insert_data(U8 *buffer, S32 &offset, LLScriptLibData *data, LLScriptAllocEntry &entry, S32 heapsize); + +S32 lsa_create_data_block(U8 **buffer, LLScriptLibData *data, S32 base_offset); + +// increase reference count +// increase reference count by 1 + +void lsa_increase_ref_count(U8 *buffer, S32 offset); + +// decrease reference count +// decrease reference count by 1 +// if reference count == 0 +// set type to empty + +void lsa_decrease_ref_count(U8 *buffer, S32 offset); + +inline S32 get_max_heap_size(U8 *buffer) +{ + return get_register(buffer, LREG_SP) - get_register(buffer, LREG_HR); +} + + +LLScriptLibData *lsa_get_data(U8 *buffer, S32 &offset, BOOL b_dec_ref); +LLScriptLibData *lsa_get_list_ptr(U8 *buffer, S32 &offset, BOOL b_dec_ref); + +S32 lsa_cat_strings(U8 *buffer, S32 offset1, S32 offset2, S32 heapsize); +S32 lsa_cmp_strings(U8 *buffer, S32 offset1, S32 offset2); + +S32 lsa_cat_lists(U8 *buffer, S32 offset1, S32 offset2, S32 heapsize); +S32 lsa_cmp_lists(U8 *buffer, S32 offset1, S32 offset2); +S32 lsa_preadd_lists(U8 *buffer, LLScriptLibData *data, S32 offset2, S32 heapsize); +S32 lsa_postadd_lists(U8 *buffer, S32 offset1, LLScriptLibData *data, S32 heapsize); + +// modifying a list +// insert new list that is modified +// store returned address in original list's variable +// decrease reference count on old list + +// list l1 = [10]; +// list l2 = l1; +// l1 = [11]; + +// we want l2 == [10]; + +// more complicated example: +// list l1 = [10, 11]; +// list l2 = l1; +// l1[0] = 12 + +// I think that we want l2 = [10, 11]; + +// one option would be to use syntax like: +// l1 = llSetList(l1, 0, 12); +// but this would require variable argument list matching +// which maybe is ok, but would be work +// the other option would be changes to lists that have multiple references causes a copy to occur + +// popl @l1, 0, integer, 12 +// +// would cause l1 to be copied, 12 to replace the 0th entry, and the address of the new list to be saved in l1 +// + +inline LLScriptLibData *lsa_bubble_sort(LLScriptLibData *src, S32 stride, S32 ascending) +{ + S32 number = src->getListLength(); + + if (number <= 0) + { + return NULL; + } + + if (stride <= 0) + { + stride = 1; + } + + S32 i = 0; + + if (number % stride) + { + LLScriptLibData *retval = src->mListp; + src->mListp = NULL; + return retval; + } + + LLScriptLibData **sortarray = new LLScriptLibData*[number]; + + LLScriptLibData *temp = src->mListp; + while (temp) + { + sortarray[i] = temp; + i++; + temp = temp->mListp; + } + + S32 j, s; + + for (i = 0; i < number; i += stride) + { + for (j = i; j < number; j += stride) + { + if ( ((*sortarray[i]) <= (*sortarray[j])) + != (ascending == TRUE)) + { + for (s = 0; s < stride; s++) + { + temp = sortarray[i + s]; + sortarray[i + s] = sortarray[j + s]; + sortarray[j + s] = temp; + } + } + } + } + + i = 1; + temp = sortarray[0]; + while (i < number) + { + temp->mListp = sortarray[i++]; + temp = temp->mListp; + } + temp->mListp = NULL; + + src->mListp = NULL; + + temp = sortarray[0]; + delete[] sortarray; + return temp; +} + + +LLScriptLibData* lsa_randomize(LLScriptLibData* src, S32 stride); + +#endif diff --git a/indra/lscript/lscript_byteconvert.h b/indra/lscript/lscript_byteconvert.h new file mode 100755 index 0000000000..5b08481d7d --- /dev/null +++ b/indra/lscript/lscript_byteconvert.h @@ -0,0 +1,1170 @@ +/** + * @file lscript_byteconvert.h + * @brief Shared code for compiler and assembler for LSL + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// data shared between compiler/assembler +// used to convert data between byte stream and outside data types + +#ifndef LL_LSCRIPT_BYTECONVERT_H +#define LL_LSCRIPT_BYTECONVERT_H + +#include "stdtypes.h" +#include "v3math.h" +#include "llquaternion.h" +#include "lscript_byteformat.h" +#include "lluuid.h" + +void reset_hp_to_safe_spot(const U8 *buffer); + +// remember that LScript byte stream is BigEndian +void set_fault(const U8 *stream, LSCRIPTRunTimeFaults fault); + +inline S32 bytestream2integer(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 4; + return (*stream<<24) | (*(stream + 1)<<16) | (*(stream + 2)<<8) | *(stream + 3); +} + +inline U32 bytestream2unsigned_integer(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 4; + return (*stream<<24) | (*(stream + 1)<<16) | (*(stream + 2)<<8) | *(stream + 3); +} + +inline U64 bytestream2u64(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 8; + return ((U64)(*stream)<<56)| ((U64)(*(stream + 1))<<48) | ((U64)(*(stream + 2))<<40) | ((U64)(*(stream + 3))<<32) | + ((U64)(*(stream + 4))<<24) | ((U64)(*(stream + 5))<<16) | ((U64)(*(stream + 6))<<8) | (U64)(*(stream + 7)); +} + +inline void integer2bytestream(U8 *stream, S32 &offset, S32 integer) +{ + stream += offset; + offset += 4; + *(stream) = (integer >> 24); + *(stream + 1) = (integer >> 16) & 0xff; + *(stream + 2) = (integer >> 8) & 0xff; + *(stream + 3) = (integer) & 0xff; +} + +inline void unsigned_integer2bytestream(U8 *stream, S32 &offset, U32 integer) +{ + stream += offset; + offset += 4; + *(stream) = (integer >> 24); + *(stream + 1) = (integer >> 16) & 0xff; + *(stream + 2) = (integer >> 8) & 0xff; + *(stream + 3) = (integer) & 0xff; +} +inline void u642bytestream(U8 *stream, S32 &offset, U64 integer) +{ + stream += offset; + offset += 8; + *(stream) = (U8)(integer >> 56); + *(stream + 1) = (U8)((integer >> 48) & 0xff); + *(stream + 2) = (U8)((integer >> 40) & 0xff); + *(stream + 3) = (U8)((integer >> 32) & 0xff); + *(stream + 4) = (U8)((integer >> 24) & 0xff); + *(stream + 5) = (U8)((integer >> 16) & 0xff); + *(stream + 6) = (U8)((integer >> 8) & 0xff); + *(stream + 7) = (U8)((integer) & 0xff); +} + +inline S16 bytestream2s16(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 2; + return (*stream<<8) | *(stream + 1); +} + +inline void s162bytestream(U8 *stream, S32 &offset, S16 integer) +{ + stream += offset; + offset += 2; + *(stream) = (integer >> 8); + *(stream + 1) = (integer) & 0xff; +} + +inline U16 bytestream2u16(const U8 *stream, S32 &offset) +{ + stream += offset; + offset += 2; + return (*stream<<8) | *(stream + 1); +} + +inline void u162bytestream(U8 *stream, S32 &offset, U16 integer) +{ + stream += offset; + offset += 2; + *(stream) = (integer >> 8); + *(stream + 1) = (integer) & 0xff; +} + +inline F32 bytestream2float(const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + F32 fpvalue = *(F32 *)&value; + if (!llfinite(fpvalue)) + { + fpvalue = 0; + set_fault(stream, LSRF_MATH); + } + return fpvalue; +} + +inline void float2bytestream(U8 *stream, S32 &offset, F32 floatingpoint) +{ + S32 value = *(S32 *)&floatingpoint; + integer2bytestream(stream, offset, value); +} + +inline void bytestream_int2float(U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + offset -= 4; + F32 fpvalue = (F32)value; + if (!llfinite(fpvalue)) + { + fpvalue = 0; + set_fault(stream, LSRF_MATH); + } + float2bytestream(stream, offset, fpvalue); +} + +// Returns true on success, return false and clip copy on buffer overflow +inline bool bytestream2char(char *buffer, const U8 *stream, S32 &offset, S32 buffsize) +{ + S32 source_len = strlen( (const char *)stream+offset ); + S32 copy_len = buffsize - 1; + if( copy_len > source_len ) + { + copy_len = source_len; + } + + // strncpy without \0 padding overhead + memcpy( buffer, stream+offset, copy_len ); + buffer[copy_len] = 0; + + offset += source_len + 1; // advance past source string, include terminating '\0' + + return source_len < buffsize; +} + +inline void char2bytestream(U8 *stream, S32 &offset, const char *buffer) +{ + while ((*(stream + offset++) = *buffer++)) + ; +} + +inline U8 bytestream2byte(const U8 *stream, S32 &offset) +{ + return *(stream + offset++); +} + +inline void byte2bytestream(U8 *stream, S32 &offset, U8 byte) +{ + *(stream + offset++) = byte; +} + +inline void bytestream2bytestream(U8 *dest, S32 &dest_offset, const U8 *src, S32 &src_offset, S32 count) +{ + while (count) + { + (*(dest + dest_offset++)) = (*(src + src_offset++)); + count--; + } +} + +inline void uuid2bytestream(U8 *stream, S32 &offset, const LLUUID &uuid) +{ + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + *(stream + offset++) = uuid.mData[i]; + } +} + +inline void bytestream2uuid(U8 *stream, S32 &offset, LLUUID &uuid) +{ + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + uuid.mData[i] = *(stream + offset++); + } +} + +// vectors and quaternions and encoded in backwards order to match the way in which they are stored on the stack +inline void bytestream2vector(LLVector3 &vector, const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + vector.mV[VZ] = *(F32 *)&value; + if (!llfinite(vector.mV[VZ])) + { + vector.mV[VZ] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + vector.mV[VY] = *(F32 *)&value; + if (!llfinite(vector.mV[VY])) + { + vector.mV[VY] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + vector.mV[VX] = *(F32 *)&value; + if (!llfinite(vector.mV[VX])) + { + vector.mV[VX] = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void vector2bytestream(U8 *stream, S32 &offset, const LLVector3 &vector) +{ + S32 value = *(S32 *)&vector.mV[VZ]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&vector.mV[VY]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&vector.mV[VX]; + integer2bytestream(stream, offset, value); +} + +inline void bytestream2quaternion(LLQuaternion &quat, const U8 *stream, S32 &offset) +{ + S32 value = bytestream2integer(stream, offset); + quat.mQ[VS] = *(F32 *)&value; + if (!llfinite(quat.mQ[VS])) + { + quat.mQ[VS] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VZ] = *(F32 *)&value; + if (!llfinite(quat.mQ[VZ])) + { + quat.mQ[VZ] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VY] = *(F32 *)&value; + if (!llfinite(quat.mQ[VY])) + { + quat.mQ[VY] = 0; + set_fault(stream, LSRF_MATH); + } + value = bytestream2integer(stream, offset); + quat.mQ[VX] = *(F32 *)&value; + if (!llfinite(quat.mQ[VX])) + { + quat.mQ[VX] = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void quaternion2bytestream(U8 *stream, S32 &offset, const LLQuaternion &quat) +{ + S32 value = *(S32 *)&quat.mQ[VS]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VZ]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VY]; + integer2bytestream(stream, offset, value); + value = *(S32 *)&quat.mQ[VX]; + integer2bytestream(stream, offset, value); +} + +inline S32 get_register(const U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return bytestream2integer(stream, offset); +} + +inline F32 get_register_fp(U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + F32 value = bytestream2float(stream, offset); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + return value; +} +inline U64 get_register_u64(U8 *stream, LSCRIPTRegisters reg) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return bytestream2u64(stream, offset); +} + +inline U64 get_event_register(U8 *stream, LSCRIPTRegisters reg, S32 major_version) +{ + if (major_version == 1) + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return (U64)bytestream2integer(stream, offset); + } + else if (major_version == 2) + { + S32 offset = gLSCRIPTRegisterAddresses[reg + (LREG_NCE - LREG_CE)]; + return bytestream2u64(stream, offset); + } + else + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + return (U64)bytestream2integer(stream, offset); + } +} + +inline void set_register(U8 *stream, LSCRIPTRegisters reg, S32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, value); +} + +inline void set_register_fp(U8 *stream, LSCRIPTRegisters reg, F32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + float2bytestream(stream, offset, value); +} + +inline void set_register_u64(U8 *stream, LSCRIPTRegisters reg, U64 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + u642bytestream(stream, offset, value); +} + +inline void set_event_register(U8 *stream, LSCRIPTRegisters reg, U64 value, S32 major_version) +{ + if (major_version == 1) + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, (S32)value); + } + else if (major_version == 2) + { + S32 offset = gLSCRIPTRegisterAddresses[reg + (LREG_NCE - LREG_CE)]; + u642bytestream(stream, offset, value); + } + else + { + S32 offset = gLSCRIPTRegisterAddresses[reg]; + integer2bytestream(stream, offset, (S32)value); + } +} + + +inline F32 add_register_fp(U8 *stream, LSCRIPTRegisters reg, F32 value) +{ + S32 offset = gLSCRIPTRegisterAddresses[reg]; + F32 newvalue = bytestream2float(stream, offset); + newvalue += value; + if (!llfinite(newvalue)) + { + newvalue = 0; + set_fault(stream, LSRF_MATH); + } + offset = gLSCRIPTRegisterAddresses[reg]; + float2bytestream(stream, offset, newvalue); + return newvalue; +} + +void lsa_print_heap(U8 *buffer); + + +inline void set_fault(const U8 *stream, LSCRIPTRunTimeFaults fault) +{ + S32 fr = get_register(stream, LREG_FR); + // record the first error + if (!fr) + { + if ( (fault == LSRF_HEAP_ERROR) + ||(fault == LSRF_STACK_HEAP_COLLISION) + ||(fault == LSRF_BOUND_CHECK_ERROR)) + { + reset_hp_to_safe_spot(stream); +// lsa_print_heap((U8 *)stream); + } + fr = fault; + set_register((U8 *)stream, LREG_FR, fr); + } +} + +inline BOOL set_ip(U8 *stream, S32 ip) +{ + // Verify that the Instruction Pointer is in a valid + // code area (between the Global Function Register + // and Heap Register). + S32 gfr = get_register(stream, LREG_GFR); + if (ip == 0) + { + set_register(stream, LREG_IP, ip); + return TRUE; + } + if (ip < gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 hr = get_register(stream, LREG_HR); + if (ip >= hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_IP, ip); + return TRUE; +} + +inline BOOL set_bp(U8 *stream, S32 bp) +{ + // Verify that the Base Pointer is in a valid + // data area (between the Heap Pointer and + // the Top of Memory, and below the + // Stack Pointer). + S32 hp = get_register(stream, LREG_HP); + if (bp <= hp) + { + set_fault(stream, LSRF_STACK_HEAP_COLLISION); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (bp >= tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 sp = get_register(stream, LREG_SP); + if (bp < sp) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_BP, bp); + return TRUE; +} + +inline BOOL set_sp(U8 *stream, S32 sp) +{ + // Verify that the Stack Pointer is in a valid + // data area (between the Heap Pointer and + // the Top of Memory). + S32 hp = get_register(stream, LREG_HP); + if (sp <= hp) + { + set_fault(stream, LSRF_STACK_HEAP_COLLISION); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (sp >= tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + set_register(stream, LREG_SP, sp); + return TRUE; +} + +inline void lscript_push(U8 *stream, U8 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= 1; + + if (set_sp(stream, sp)) + { + *(stream + sp) = value; + } +} + +inline void lscript_push(U8 *stream, S32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_INTEGER]; + + if (set_sp(stream, sp)) + { + integer2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, F32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_FLOATINGPOINT]; + + if (set_sp(stream, sp)) + { + float2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, const LLVector3 &value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_VECTOR]; + + if (set_sp(stream, sp)) + { + vector2bytestream(stream, sp, value); + } +} + +inline void lscript_push(U8 *stream, const LLQuaternion &value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= LSCRIPTDataSize[LST_QUATERNION]; + + if (set_sp(stream, sp)) + { + quaternion2bytestream(stream, sp, value); + } +} + +inline void lscript_pusharg(U8 *stream, S32 arg) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= arg; + + set_sp(stream, sp); +} + +inline void lscript_poparg(U8 *stream, S32 arg) +{ + S32 sp = get_register(stream, LREG_SP); + sp += arg; + + set_sp(stream, sp); +} + +inline U8 lscript_pop_char(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + U8 value = *(stream + sp++); + set_sp(stream, sp); + return value; +} + +inline S32 lscript_pop_int(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + S32 value = bytestream2integer(stream, sp); + set_sp(stream, sp); + return value; +} + +inline F32 lscript_pop_float(U8 *stream) +{ + S32 sp = get_register(stream, LREG_SP); + F32 value = bytestream2float(stream, sp); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + set_sp(stream, sp); + return value; +} + +inline void lscript_pop_vector(U8 *stream, LLVector3 &value) +{ + S32 sp = get_register(stream, LREG_SP); + bytestream2vector(value, stream, sp); + set_sp(stream, sp); +} + +inline void lscript_pop_quaternion(U8 *stream, LLQuaternion &value) +{ + S32 sp = get_register(stream, LREG_SP); + bytestream2quaternion(value, stream, sp); + set_sp(stream, sp); +} + +inline void lscript_pusharge(U8 *stream, S32 value) +{ + S32 sp = get_register(stream, LREG_SP); + sp -= value; + if (set_sp(stream, sp)) + { + S32 i; + for (i = 0; i < value; i++) + { + *(stream + sp++) = 0; + } + } +} + +inline BOOL lscript_check_local(U8 *stream, S32 &address, S32 size) +{ + S32 sp = get_register(stream, LREG_SP); + S32 bp = get_register(stream, LREG_BP); + + address += size; + address = bp - address; + + if (address < sp - size) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + S32 tm = get_register(stream, LREG_TM); + if (address + size > tm) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + return TRUE; +} + +inline BOOL lscript_check_global(U8 *stream, S32 &address, S32 size) +{ + S32 gvr = get_register(stream, LREG_GVR); + + // Possibility of overwriting registers? -- DK 09/07/04 + if (address < 0) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + + address += gvr; + S32 gfr = get_register(stream, LREG_GFR); + + if (address + size > gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + return TRUE; +} + +inline void lscript_local_store(U8 *stream, S32 address, S32 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_INTEGER])) + integer2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, F32 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + float2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, const LLVector3 value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_VECTOR])) + vector2bytestream(stream, address, value); +} + +inline void lscript_local_store(U8 *stream, S32 address, const LLQuaternion value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + quaternion2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, S32 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_INTEGER])) + integer2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, F32 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + float2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, const LLVector3 value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_VECTOR])) + vector2bytestream(stream, address, value); +} + +inline void lscript_global_store(U8 *stream, S32 address, const LLQuaternion value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + quaternion2bytestream(stream, address, value); +} + +inline S32 lscript_local_get(U8 *stream, S32 address) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_INTEGER])) + return bytestream2integer(stream, address); + return 0; +} + +inline void lscript_local_get(U8 *stream, S32 address, F32 &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + value = bytestream2float(stream, address); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void lscript_local_get(U8 *stream, S32 address, LLVector3 &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_VECTOR])) + bytestream2vector(value, stream, address); +} + +inline void lscript_local_get(U8 *stream, S32 address, LLQuaternion &value) +{ + if (lscript_check_local(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + bytestream2quaternion(value, stream, address); +} + +inline S32 lscript_global_get(U8 *stream, S32 address) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_INTEGER])) + return bytestream2integer(stream, address); + return 0; +} + +inline void lscript_global_get(U8 *stream, S32 address, F32 &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_FLOATINGPOINT])) + value = bytestream2float(stream, address); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } +} + +inline void lscript_global_get(U8 *stream, S32 address, LLVector3 &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_VECTOR])) + bytestream2vector(value, stream, address); +} + +inline void lscript_global_get(U8 *stream, S32 address, LLQuaternion &value) +{ + if (lscript_check_global(stream, address, LSCRIPTDataSize[LST_QUATERNION])) + bytestream2quaternion(value, stream, address); +} + + + +inline S32 get_state_event_opcoode_start(U8 *stream, S32 state, LSCRIPTStateEventType event) +{ + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get the position of the jump to the desired state + S32 value = get_register(stream, LREG_VN); + + S32 state_offset_offset = 0; + S32 major_version = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_ONE; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*2*state; + } + else if (value == LSL2_VERSION_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_TWO; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*3*state; + } + if ( state_offset_offset < 0 || state_offset_offset > TOP_OF_MEMORY ) + { + return -1; + } + + // get the actual position in memory of the desired state + S32 state_offset = sr + bytestream2integer(stream, state_offset_offset); + if ( state_offset < 0 || state_offset > TOP_OF_MEMORY ) + { + return -1; + } + + // save that value + S32 state_offset_base = state_offset; + + // jump past the state name + S32 event_jump_offset = state_offset_base + bytestream2integer(stream, state_offset); + + // get the location of the event offset + S32 event_offset = event_jump_offset + LSCRIPTDataSize[LST_INTEGER]*2*get_event_handler_jump_position(get_event_register(stream, LREG_ER, major_version), event); + if ( event_offset < 0 || event_offset > TOP_OF_MEMORY ) + { + return -1; + } + + // now, jump to the event + S32 event_start = bytestream2integer(stream, event_offset); + if ( event_start < 0 || event_start > TOP_OF_MEMORY ) + { + return -1; + } + event_start += event_jump_offset; + + S32 event_start_original = event_start; + + // now skip past the parameters + S32 opcode_offset = bytestream2integer(stream, event_start); + if ( opcode_offset < 0 || opcode_offset > TOP_OF_MEMORY ) + { + return -1; + } + + return opcode_offset + event_start_original; +} + + +inline U64 get_handled_events(U8 *stream, S32 state) +{ + U64 retvalue = 0; + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get the position of the jump to the desired state + S32 value = get_register(stream, LREG_VN); + S32 state_handled_offset = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + state_handled_offset = sr + LSCRIPTDataSize[LST_INTEGER]*2*state + 2*LSCRIPTDataSize[LST_INTEGER]; + retvalue = bytestream2integer(stream, state_handled_offset); + } + else if (value == LSL2_VERSION_NUMBER) + { + state_handled_offset = sr + LSCRIPTDataSize[LST_INTEGER]*3*state + 2*LSCRIPTDataSize[LST_INTEGER]; + retvalue = bytestream2u64(stream, state_handled_offset); + } + + // get the handled events + return retvalue; +} + +// Returns -1 on error +inline S32 get_event_stack_size(U8 *stream, S32 state, LSCRIPTStateEventType event) +{ + // get the start of the state table + S32 sr = get_register(stream, LREG_SR); + + // get state offset + S32 value = get_register(stream, LREG_VN); + S32 state_offset_offset = 0; + S32 major_version = 0; + if (value == LSL2_VERSION1_END_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_ONE; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*2*state; + } + else if (value == LSL2_VERSION_NUMBER) + { + major_version = LSL2_MAJOR_VERSION_TWO; + state_offset_offset = sr + LSCRIPTDataSize[LST_INTEGER] + LSCRIPTDataSize[LST_INTEGER]*3*state; + } + + if ( state_offset_offset < 0 || state_offset_offset > TOP_OF_MEMORY ) + { + return -1; + } + + S32 state_offset = bytestream2integer(stream, state_offset_offset); + state_offset += sr; + + state_offset_offset = state_offset; + if ( state_offset_offset < 0 || state_offset_offset > TOP_OF_MEMORY ) + { + return -1; + } + + // skip to jump table + S32 jump_table = bytestream2integer(stream, state_offset_offset); + + jump_table += state_offset; + if ( jump_table < 0 || jump_table > TOP_OF_MEMORY ) + { + return -1; + } + + // get the position of the jump to the desired state + S32 stack_size_offset = jump_table + LSCRIPTDataSize[LST_INTEGER]*2*get_event_handler_jump_position(get_event_register(stream, LREG_ER, major_version), event) + LSCRIPTDataSize[LST_INTEGER]; + + // get the handled events + S32 stack_size = bytestream2integer(stream, stack_size_offset); + if ( stack_size < 0 || stack_size > TOP_OF_MEMORY ) + { + return -1; + } + + return stack_size; +} + +inline LSCRIPTStateEventType return_first_event(S32 event) +{ + S32 count = 1; + while (count < LSTT_EOF) + { + if (event & 0x1) + { + return (LSCRIPTStateEventType) count; + } + else + { + event >>= 1; + count++; + } + } + return LSTT_NULL; +} + + +// the safe instruction versions of these commands will only work if offset is between +// GFR and HR, meaning that it is an instruction (more or less) in global functions or event handlers + +inline BOOL safe_instruction_check_address(U8 *stream, S32 offset, S32 size) +{ + S32 gfr = get_register(stream, LREG_GFR); + if (offset < gfr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + S32 hr = get_register(stream, LREG_HR); + if (offset + size > hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + return TRUE; + } + } +} + +inline BOOL safe_heap_check_address(U8 *stream, S32 offset, S32 size) +{ + S32 hr = get_register(stream, LREG_HR); + if (offset < hr) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + S32 hp = get_register(stream, LREG_HP); + if (offset + size > hp) + { + set_fault(stream, LSRF_BOUND_CHECK_ERROR); + return FALSE; + } + else + { + return TRUE; + } + } +} + +inline U8 safe_instruction_bytestream2byte(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, 1)) + { + return *(stream + offset++); + } + else + { + return 0; + } +} + +inline void safe_instruction_byte2bytestream(U8 *stream, S32 &offset, U8 byte) +{ + if (safe_instruction_check_address(stream, offset, 1)) + { + *(stream + offset++) = byte; + } +} + +inline S32 safe_instruction_bytestream2integer(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + return (bytestream2integer(stream, offset)); + } + else + { + return 0; + } +} + +inline void safe_instruction_integer2bytestream(U8 *stream, S32 &offset, S32 value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + integer2bytestream(stream, offset, value); + } +} + +inline U16 safe_instruction_bytestream2u16(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, 2)) + { + return (bytestream2u16(stream, offset)); + } + else + { + return 0; + } +} + +inline void safe_instruction_u162bytestream(U8 *stream, S32 &offset, U16 value) +{ + if (safe_instruction_check_address(stream, offset, 2)) + { + u162bytestream(stream, offset, value); + } +} + +inline F32 safe_instruction_bytestream2float(U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_INTEGER])) + { + F32 value = bytestream2float(stream, offset); + if (!llfinite(value)) + { + value = 0; + set_fault(stream, LSRF_MATH); + } + return value; + } + else + { + return 0; + } +} + +inline void safe_instruction_float2bytestream(U8 *stream, S32 &offset, F32 value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_FLOATINGPOINT])) + { + float2bytestream(stream, offset, value); + } +} + +inline void safe_instruction_bytestream2char(char *buffer, U8 *stream, S32 &offset, S32 buffsize) +{ + // This varies from the old method. Previously, we would copy up until we got an error, + // then halt the script via safe_isntruction_check_address. Now we don't bother + // copying a thing if there's an error. + + if( safe_instruction_check_address(stream, offset, strlen( (const char *)stream + offset ) + 1 ) ) + { + // Takes the same parms as this function. Won't overread, per above check. + bytestream2char( buffer, stream, offset, buffsize ); + } + else + { + // Truncate - no point in copying + *buffer = 0; + } +} + +inline void safe_instruction_bytestream_count_char(U8 *stream, S32 &offset) +{ + while ( (safe_instruction_check_address(stream, offset, 1)) + &&(*(stream + offset++))) + ; +} + +inline void safe_heap_bytestream_count_char(U8 *stream, S32 &offset) +{ + while ( (safe_heap_check_address(stream, offset, 1)) + &&(*(stream + offset++))) + ; +} + +inline void safe_instruction_char2bytestream(U8 *stream, S32 &offset, const char* buffer) +{ + while ( (safe_instruction_check_address(stream, offset, 1)) + &&(*(stream + offset++) = *buffer++)) + ; +} + +inline void safe_instruction_bytestream2vector(LLVector3 &value, U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_VECTOR])) + { + bytestream2vector(value, stream, offset); + } +} + +inline void safe_instruction_vector2bytestream(U8 *stream, S32 &offset, const LLVector3 &value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_VECTOR])) + { + vector2bytestream(stream, offset, value); + } +} + +inline void safe_instruction_bytestream2quaternion(LLQuaternion &value, U8 *stream, S32 &offset) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_QUATERNION])) + { + bytestream2quaternion(value, stream, offset); + } +} + +inline void safe_instruction_quaternion2bytestream(U8 *stream, S32 &offset, const LLQuaternion &value) +{ + if (safe_instruction_check_address(stream, offset, LSCRIPTDataSize[LST_QUATERNION])) + { + quaternion2bytestream(stream, offset, value); + } +} + +static inline LSCRIPTType char2type(char type) +{ + switch(type) + { + case 'i': + return LST_INTEGER; + case 'f': + return LST_FLOATINGPOINT; + case 's': + return LST_STRING; + case 'k': + return LST_KEY; + case 'v': + return LST_VECTOR; + case 'q': + return LST_QUATERNION; + case 'l': + return LST_LIST; + default: + return LST_NULL; + } +} + +#endif diff --git a/indra/lscript/lscript_byteformat.h b/indra/lscript/lscript_byteformat.h new file mode 100755 index 0000000000..54031aaf05 --- /dev/null +++ b/indra/lscript/lscript_byteformat.h @@ -0,0 +1,566 @@ +/** + * @file lscript_byteformat.h + * @brief Shared code between compiler and assembler and LSL + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LSCRIPT_BYTEFORMAT_H +#define LL_LSCRIPT_BYTEFORMAT_H + +// Data shared between compiler/assembler and lscript execution code + +#include "stdtypes.h" + +const S32 LSL2_VERSION_NUMBER = 0x0200; +const S32 LSL2_VERSION1_END_NUMBER = 0x0101; +const S32 LSL2_VERSION2_START_NUMBER = 0x0200; + +const S32 LSL2_MAJOR_VERSION_ONE = 1; +const S32 LSL2_MAJOR_VERSION_TWO = 2; +const S32 LSL2_CURRENT_MAJOR_VERSION = LSL2_MAJOR_VERSION_TWO; + +const S32 TOP_OF_MEMORY = 16384; + +typedef enum e_lscript_registers +{ + LREG_INVALID, + LREG_IP, // instruction pointer + LREG_VN, // version number + LREG_BP, // base pointer - what local variables are referenced from + LREG_SP, // stack pointer - where the top of the stack is + LREG_HR, // heap register - where in memory does the heap start + LREG_HP, // heap pointer - where is the top of the heap? + LREG_CS, // current state - what state are we currently in? + LREG_NS, // next state - what state are we currently in? + LREG_CE, // current events - what events are waiting to be handled? + LREG_IE, // in event - which event handler are we currently in? + LREG_ER, // event register - what events do we have active handlers for? + LREG_FR, // fault register - which errors are currently active? + LREG_SLR, // sleep register - are we sleeping? + LREG_GVR, // global variable register - where do global variables start + LREG_GFR, // global function register - where do global functions start + LREG_SR, // state register - where do states start + LREG_TM, // top of memory - where is the top of memory + LREG_PR, // parameter register - data passed to script from launcher + LREG_ESR, // energy supply register - how much energy do we have on board? + LREG_NCE, // 64 bit current envents - what events are waiting to be handled? + LREG_NIE, // 64 bit in event - which event handler are we currently in? + LREG_NER, // 64 bit event register - what events do we have active handlers for? + LREG_EOF +} LSCRIPTRegisters; + +const S32 gLSCRIPTRegisterAddresses[LREG_EOF] = /* Flawfinder: ignore */ +{ + 0, // LREG_INVALID + 4, // LREG_IP + 8, // LREG_VN + 12, // LREG_BP + 16, // LREG_SP + 20, // LREG_HR + 24, // LREG_HP + 28, // LREG_CS + 32, // LREG_NS + 36, // LREG_CE + 40, // LREG_IE + 44, // LREG_ER + 48, // LREG_FR + 52, // LREG_SLR + 56, // LREG_GVR + 60, // LREG_GFR + 72, // LREG_SR + 0, // LREG_TM + 64, // LREG_PR + 68, // LREG_ESR + 76, // LREG_NCE + 84, // LREG_NIE + 92, // LREG_NER +}; + +const char * const gLSCRIPTRegisterNames[LREG_EOF] = +{ + "INVALID", // LREG_INVALID + "IP", // LREG_IP + "VN", // LREG_VN + "BP", // LREG_BP + "SP", // LREG_SP + "HR", // LREG_HR + "HP", // LREG_HP + "CS", // LREG_CS + "NS", // LREG_NS + "CE", // LREG_CE + "IE", // LREG_IE + "ER", // LREG_ER + "FR", // LREG_FR + "SLR", // LREG_SLR + "GVR", // LREG_GVR + "GFR", // LREG_GFR + "SR", // LREG_SR + "TM", // LREG_TM + "PR", // LREG_PR + "ESR", // LREG_ESR + "NCE", // LREG_NCE + "NIE", // LREG_NIE + "NER", // LREG_NER +}; + +typedef enum e_lscript_op_codes +{ + LOPC_INVALID, + LOPC_NOOP, + LOPC_POP, + LOPC_POPS, + LOPC_POPL, + LOPC_POPV, + LOPC_POPQ, + LOPC_POPARG, + LOPC_POPIP, + LOPC_POPBP, + LOPC_POPSP, + LOPC_POPSLR, + LOPC_DUP, + LOPC_DUPS, + LOPC_DUPL, + LOPC_DUPV, + LOPC_DUPQ, + LOPC_STORE, + LOPC_STORES, + LOPC_STOREL, + LOPC_STOREV, + LOPC_STOREQ, + LOPC_STOREG, + LOPC_STOREGS, + LOPC_STOREGL, + LOPC_STOREGV, + LOPC_STOREGQ, + LOPC_LOADP, + LOPC_LOADSP, + LOPC_LOADLP, + LOPC_LOADVP, + LOPC_LOADQP, + LOPC_LOADGP, + LOPC_LOADGLP, + LOPC_LOADGSP, + LOPC_LOADGVP, + LOPC_LOADGQP, + LOPC_PUSH, + LOPC_PUSHS, + LOPC_PUSHL, + LOPC_PUSHV, + LOPC_PUSHQ, + LOPC_PUSHG, + LOPC_PUSHGS, + LOPC_PUSHGL, + LOPC_PUSHGV, + LOPC_PUSHGQ, + LOPC_PUSHIP, + LOPC_PUSHBP, + LOPC_PUSHSP, + LOPC_PUSHARGB, + LOPC_PUSHARGI, + LOPC_PUSHARGF, + LOPC_PUSHARGS, + LOPC_PUSHARGV, + LOPC_PUSHARGQ, + LOPC_PUSHE, + LOPC_PUSHEV, + LOPC_PUSHEQ, + LOPC_PUSHARGE, + LOPC_ADD, + LOPC_SUB, + LOPC_MUL, + LOPC_DIV, + LOPC_MOD, + LOPC_EQ, + LOPC_NEQ, + LOPC_LEQ, + LOPC_GEQ, + LOPC_LESS, + LOPC_GREATER, + LOPC_BITAND, + LOPC_BITOR, + LOPC_BITXOR, + LOPC_BOOLAND, + LOPC_BOOLOR, + LOPC_NEG, + LOPC_BITNOT, + LOPC_BOOLNOT, + LOPC_JUMP, + LOPC_JUMPIF, + LOPC_JUMPNIF, + LOPC_STATE, + LOPC_CALL, + LOPC_RETURN, + LOPC_CAST, + LOPC_STACKTOS, + LOPC_STACKTOL, + LOPC_PRINT, + LOPC_CALLLIB, + LOPC_CALLLIB_TWO_BYTE, + LOPC_SHL, + LOPC_SHR, + LOPC_EOF +} LSCRIPTOpCodesEnum; + +const U8 LSCRIPTOpCodes[LOPC_EOF] = +{ + 0x00, // LOPC_INVALID + 0x00, // LOPC_NOOP + 0x01, // LOPC_POP + 0x02, // LOPC_POPS + 0x03, // LOPC_POPL + 0x04, // LOPC_POPV + 0x05, // LOPC_POPQ + 0x06, // LOPC_POPARG + 0x07, // LOPC_POPIP + 0x08, // LOPC_POPBP + 0x09, // LOPC_POPSP + 0x0a, // LOPC_POPSLR + 0x20, // LOPC_DUP + 0x21, // LOPC_DUPS + 0x22, // LOPC_DUPL + 0x23, // LOPC_DUPV + 0x24, // LOPC_DUPQ + 0x30, // LOPC_STORE + 0x31, // LOPC_STORES + 0x32, // LOPC_STOREL + 0x33, // LOPC_STOREV + 0x34, // LOPC_STOREQ + 0x35, // LOPC_STOREG + 0x36, // LOPC_STOREGS + 0x37, // LOPC_STOREGL + 0x38, // LOPC_STOREGV + 0x39, // LOPC_STOREGQ + 0x3a, // LOPC_LOADP + 0x3b, // LOPC_LOADSP + 0x3c, // LOPC_LOADLP + 0x3d, // LOPC_LOADVP + 0x3e, // LOPC_LOADQP + 0x3f, // LOPC_LOADGP + 0x40, // LOPC_LOADGSP + 0x41, // LOPC_LOADGLP + 0x42, // LOPC_LOADGVP + 0x43, // LOPC_LOADGQP + 0x50, // LOPC_PUSH + 0x51, // LOPC_PUSHS + 0x52, // LOPC_PUSHL + 0x53, // LOPC_PUSHV + 0x54, // LOPC_PUSHQ + 0x55, // LOPC_PUSHG + 0x56, // LOPC_PUSHGS + 0x57, // LOPC_PUSHGL + 0x58, // LOPC_PUSHGV + 0x59, // LOPC_PUSHGQ + 0x5a, // LOPC_PUSHIP + 0x5b, // LOPC_PUSHBP + 0x5c, // LOPC_PUSHSP + 0x5d, // LOPC_PUSHARGB + 0x5e, // LOPC_PUSHARGI + 0x5f, // LOPC_PUSHARGF + 0x60, // LOPC_PUSHARGS + 0x61, // LOPC_PUSHARGV + 0x62, // LOPC_PUSHARGQ + 0x63, // LOPC_PUSHE + 0x64, // LOPC_PUSHEV + 0x65, // LOPC_PUSHEQ + 0x66, // LOPC_PUSHARGE + 0x70, // LOPC_ADD + 0x71, // LOPC_SUB + 0x72, // LOPC_MUL + 0x73, // LOPC_DIV + 0x74, // LOPC_MOD + 0x75, // LOPC_EQ + 0x76, // LOPC_NEQ + 0x77, // LOPC_LEQ + 0x78, // LOPC_GEQ + 0x79, // LOPC_LESS + 0x7a, // LOPC_GREATER + 0x7b, // LOPC_BITAND + 0x7c, // LOPC_BITOR + 0x7d, // LOPC_BITXOR + 0x7e, // LOPC_BOOLAND + 0x7f, // LOPC_BOOLOR + 0x80, // LOPC_NEG + 0x81, // LOPC_BITNOT + 0x82, // LOPC_BOOLNOT + 0x90, // LOPC_JUMP + 0x91, // LOPC_JUMPIF + 0x92, // LOPC_JUMPNIF + 0x93, // LOPC_STATE + 0x94, // LOPC_CALL + 0x95, // LOPC_RETURN + 0xa0, // LOPC_CAST + 0xb0, // LOPC_STACKTOS + 0xb1, // LOPC_STACKTOL + 0xc0, // LOPC_PRINT + 0xd0, // LOPC_CALLLIB + 0xd1, // LOPC_CALLLIB_TWO_BYTE + 0xe0, // LOPC_SHL + 0xe1 // LOPC_SHR +}; + +typedef enum e_lscript_state_event_type +{ + LSTT_NULL, + LSTT_STATE_ENTRY, + LSTT_STATE_EXIT, + LSTT_TOUCH_START, + LSTT_TOUCH, + LSTT_TOUCH_END, + LSTT_COLLISION_START, + LSTT_COLLISION, + LSTT_COLLISION_END, + LSTT_LAND_COLLISION_START, + LSTT_LAND_COLLISION, + LSTT_LAND_COLLISION_END, + LSTT_TIMER, + LSTT_CHAT, + LSTT_REZ, + LSTT_SENSOR, + LSTT_NO_SENSOR, + LSTT_CONTROL, + LSTT_MONEY, + LSTT_EMAIL, + LSTT_AT_TARGET, + LSTT_NOT_AT_TARGET, + LSTT_AT_ROT_TARGET, + LSTT_NOT_AT_ROT_TARGET, + LSTT_RTPERMISSIONS, + LSTT_INVENTORY, + LSTT_ATTACH, + LSTT_DATASERVER, + LSTT_LINK_MESSAGE, + LSTT_MOVING_START, + LSTT_MOVING_END, + LSTT_OBJECT_REZ, + LSTT_REMOTE_DATA, + LSTT_HTTP_RESPONSE, + LSTT_HTTP_REQUEST, + LSTT_EOF, + + LSTT_STATE_BEGIN = LSTT_STATE_ENTRY, + LSTT_STATE_END = LSTT_EOF +} LSCRIPTStateEventType; + +const U64 LSCRIPTStateBitField[LSTT_EOF] = +{ + 0x0000000000000000, // LSTT_NULL + 0x0000000000000001, // LSTT_STATE_ENTRY + 0x0000000000000002, // LSTT_STATE_EXIT + 0x0000000000000004, // LSTT_TOUCH_START + 0x0000000000000008, // LSTT_TOUCH + 0x0000000000000010, // LSTT_TOUCH_END + 0x0000000000000020, // LSTT_COLLISION_START + 0x0000000000000040, // LSTT_COLLISION + 0x0000000000000080, // LSTT_COLLISION_END + 0x0000000000000100, // LSTT_LAND_COLLISION_START + 0x0000000000000200, // LSTT_LAND_COLLISION + 0x0000000000000400, // LSTT_LAND_COLLISION_END + 0x0000000000000800, // LSTT_TIMER + 0x0000000000001000, // LSTT_CHAT + 0x0000000000002000, // LSTT_REZ + 0x0000000000004000, // LSTT_SENSOR + 0x0000000000008000, // LSTT_NO_SENSOR + 0x0000000000010000, // LSTT_CONTROL + 0x0000000000020000, // LSTT_MONEY + 0x0000000000040000, // LSTT_EMAIL + 0x0000000000080000, // LSTT_AT_TARGET + 0x0000000000100000, // LSTT_NOT_AT_TARGET + 0x0000000000200000, // LSTT_AT_ROT_TARGET + 0x0000000000400000, // LSTT_NOT_AT_ROT_TARGET + 0x0000000000800000, // LSTT_RTPERMISSIONS + 0x0000000001000000, // LSTT_INVENTORY + 0x0000000002000000, // LSTT_ATTACH + 0x0000000004000000, // LSTT_DATASERVER + 0x0000000008000000, // LSTT_LINK_MESSAGE + 0x0000000010000000, // LSTT_MOVING_START + 0x0000000020000000, // LSTT_MOVING_END + 0x0000000040000000, // LSTT_OBJECT_REZ + 0x0000000080000000, // LSTT_REMOTE_DATA + 0x0000000100000000LL, // LSTT_HTTP_RESPOSE + 0x0000000200000000LL // LSTT_HTTP_REQUEST +}; + +inline S32 get_event_handler_jump_position(U64 bit_field, LSCRIPTStateEventType type) +{ + S32 count = 0, position = LSTT_STATE_ENTRY; + while (position < type) + { + if (bit_field & 0x1) + { + count++; + } + bit_field >>= 1; + position++; + } + return count; +} + +inline S32 get_number_of_event_handlers(U64 bit_field) +{ + S32 count = 0, position = 0; + while (position < LSTT_EOF) + { + if (bit_field & 0x1) + { + count++; + } + bit_field >>= 1; + position++; + } + return count; +} + +typedef enum e_lscript_types +{ + LST_NULL, + LST_INTEGER, + LST_FLOATINGPOINT, + LST_STRING, + LST_KEY, + LST_VECTOR, + LST_QUATERNION, + LST_LIST, + LST_UNDEFINED, + LST_EOF +} LSCRIPTType; + +const U8 LSCRIPTTypeByte[LST_EOF] = +{ + LST_NULL, + LST_INTEGER, + LST_FLOATINGPOINT, + LST_STRING, + LST_KEY, + LST_VECTOR, + LST_QUATERNION, + LST_LIST, + LST_NULL, +}; + +const U8 LSCRIPTTypeHi4Bits[LST_EOF] = +{ + LST_NULL, + LST_INTEGER << 4, + LST_FLOATINGPOINT << 4, + LST_STRING << 4, + LST_KEY << 4, + LST_VECTOR << 4, + LST_QUATERNION << 4, + LST_LIST << 4, + LST_UNDEFINED << 4, +}; + +const char * const LSCRIPTTypeNames[LST_EOF] = /*Flawfinder: ignore*/ +{ + "VOID", + "integer", + "float", + "string", + "key", + "vector", + "quaternion", + "list", + "invalid" +}; + +const S32 LSCRIPTDataSize[LST_EOF] = +{ + 0, // VOID + 4, // integer + 4, // float + 4, // string + 4, // key + 12, // vector + 16, // quaternion + 4, // list + 0 // invalid +}; + + +typedef enum e_lscript_runtime_faults +{ + LSRF_INVALID, + LSRF_MATH, + LSRF_STACK_HEAP_COLLISION, + LSRF_BOUND_CHECK_ERROR, + LSRF_HEAP_ERROR, + LSRF_VERSION_MISMATCH, + LSRF_MISSING_INVENTORY, + LSRF_SANDBOX, + LSRF_CHAT_OVERRUN, + LSRF_TOO_MANY_LISTENS, + LSRF_NESTING_LISTS, + LSRF_CLI, + LSRF_EOF +} LSCRIPTRunTimeFaults; + +extern const char* LSCRIPTRunTimeFaultStrings[LSRF_EOF]; /*Flawfinder: ignore*/ + +typedef enum e_lscript_runtime_permissions +{ + SCRIPT_PERMISSION_DEBIT, + SCRIPT_PERMISSION_TAKE_CONTROLS, + SCRIPT_PERMISSION_REMAP_CONTROLS, + SCRIPT_PERMISSION_TRIGGER_ANIMATION, + SCRIPT_PERMISSION_ATTACH, + SCRIPT_PERMISSION_RELEASE_OWNERSHIP, + SCRIPT_PERMISSION_CHANGE_LINKS, + SCRIPT_PERMISSION_CHANGE_JOINTS, + SCRIPT_PERMISSION_CHANGE_PERMISSIONS, + SCRIPT_PERMISSION_TRACK_CAMERA, + SCRIPT_PERMISSION_CONTROL_CAMERA, + SCRIPT_PERMISSION_TELEPORT, + SCRIPT_PERMISSION_EXPERIENCE, + SCRIPT_PERMISSION_SILENT_ESTATE_MANAGEMENT, + SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS, + SCRIPT_PERMISSION_RETURN_OBJECTS, + SCRIPT_PERMISSION_EOF +} LSCRIPTRunTimePermissions; + +const U32 LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_EOF] = +{ + (0x1 << 1), // SCRIPT_PERMISSION_DEBIT, + (0x1 << 2), // SCRIPT_PERMISSION_TAKE_CONTROLS, + (0x1 << 3), // SCRIPT_PERMISSION_REMAP_CONTROLS, + (0x1 << 4), // SCRIPT_PERMISSION_TRIGGER_ANIMATION, + (0x1 << 5), // SCRIPT_PERMISSION_ATTACH, + (0x1 << 6), // SCRIPT_PERMISSION_RELEASE_OWNERSHIP, + (0x1 << 7), // SCRIPT_PERMISSION_CHANGE_LINKS, + (0x1 << 8), // SCRIPT_PERMISSION_CHANGE_JOINTS, + (0x1 << 9), // SCRIPT_PERMISSION_CHANGE_PERMISSIONS + (0x1 << 10),// SCRIPT_PERMISSION_TRACK_CAMERA + (0x1 << 11),// SCRIPT_PERMISSION_CONTROL_CAMERA + (0x1 << 12),// SCRIPT_PERMISSION_TELEPORT + (0x1 << 13),// SCRIPT_PERMISSION_EXPERIENCE, + (0x1 << 14),// SCRIPT_PERMISSION_SILENT_ESTATE_MANAGEMENT, + (0x1 << 15),// SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS, + (0x1 << 16),// SCRIPT_PERMISSION_RETURN_OBJECTS, +}; + +// http_request string constants +extern const char* URL_REQUEST_GRANTED; +extern const char* URL_REQUEST_DENIED; +extern const U64 LSL_HTTP_REQUEST_TIMEOUT_USEC; + +#endif + diff --git a/indra/lscript/lscript_compile/CMakeLists.txt b/indra/lscript/lscript_compile/CMakeLists.txt new file mode 100755 index 0000000000..07662005b9 --- /dev/null +++ b/indra/lscript/lscript_compile/CMakeLists.txt @@ -0,0 +1,157 @@ +# -*- cmake -*- + +include(00-Common) +include(LLCommon) +include(LLMath) +include(LLMessage) +include(LLInventory) +include(LLPrimitive) +include(LScript) + +include(FindCygwin) + +find_program(FLEX flex + "C:/Program Files/GnuWin32/bin" + ${CYGWIN_INSTALL_PATH}/bin + /bin + /usr/bin + /usr/local/bin + ) +mark_as_advanced(FLEX) + +find_program(BISON bison + "C:/Program Files/GnuWin32/bin" + ${CYGWIN_INSTALL_PATH}/bin + /bin + /usr/bin + /usr/local/bin + ) +mark_as_advanced(BISON) + +find_program(M4 m4 + "C:/Program Files/GnuWin32/bin" + ${CYGWIN_INSTALL_PATH}/bin + /bin + /usr/bin + /usr/local/bin + ) +mark_as_advanced(M4) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLINVENTORY_INCLUDE_DIRS} + ${LLPRIMITIVE_INCLUDE_DIRS} + ${LSCRIPT_INCLUDE_DIRS} + ) +include_directories(SYSTEM + ${LLCOMMON_SYSTEM_INCLUDE_DIRS} + ) + +set(lscript_generated_SOURCE_FILES + indra.l.cpp + indra.y.cpp + ) + +set(lscript_compile_SOURCE_FILES + lscript_alloc.cpp + lscript_bytecode.cpp + lscript_error.cpp + lscript_heap.cpp + lscript_resource.cpp + lscript_scope.cpp + lscript_tree.cpp + lscript_typecheck.cpp + ) + +set(lscript_compile_HEADER_FILES + CMakeLists.txt + + indra.l + indra.y + + ../lscript_alloc.h + ../lscript_byteformat.h + ../lscript_byteconvert.h + ../lscript_http.h + + lscript_error.h + lscript_bytecode.h + lscript_heap.h + lscript_resource.h + lscript_scope.h + lscript_tree.h + lscript_typecheck.h + ) + +set_source_files_properties(${lscript_compile_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +set_source_files_properties(${lscript_generated_SOURCE_FILES} + PROPERTIES HEADER_FILE_ONLY FALSE GENERATED TRUE) + +list(APPEND lscript_compile_SOURCE_FILES ${lscript_generated_SOURCE_FILES} ${lscript_compile_HEADER_FILES}) + +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/indra.l.cpp + COMMAND ${FLEX} + ARGS + -P indra_ + -o${CMAKE_CURRENT_BINARY_DIR}/indra.l.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/indra.l + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/indra.l + ) + +if (WINDOWS) + set_source_files_properties(indra.l.cpp + PROPERTIES COMPILE_FLAGS /DYY_NO_UNISTD_H) +endif (WINDOWS) + +if (WINDOWS) + get_filename_component(M4_PATH ${M4} PATH) + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.cpp + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.hpp + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/bison.bat + ARGS + ${BISON} ${M4_PATH} + -p indra_ + -d -o ${CMAKE_CURRENT_BINARY_DIR}/indra.y.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/indra.y + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/bison.bat + ${CMAKE_CURRENT_SOURCE_DIR}/indra.y + ) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/windows) +else (WINDOWS) + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.cpp + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.hpp + COMMAND + ${BISON} + ARGS + -p indra_ + -d -o ${CMAKE_CURRENT_BINARY_DIR}/indra.y.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/indra.y + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/indra.y + ) +endif (WINDOWS) + +if (DARWIN) + # Mac OS X 10.4 compatibility + add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.hpp + COMMAND + mv + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.cpp.h + ${CMAKE_CURRENT_BINARY_DIR}/indra.y.hpp + ) +endif (DARWIN) + +add_library (lscript_compile ${lscript_compile_SOURCE_FILES}) diff --git a/indra/lscript/lscript_compile/bison.bat b/indra/lscript/lscript_compile/bison.bat new file mode 100644 index 0000000000..d40997225e --- /dev/null +++ b/indra/lscript/lscript_compile/bison.bat @@ -0,0 +1,12 @@ +@REM Run bison under Windows. This script is needed so that bison can +@REM find m4, even if neither program is present in PATH. + +@set bison=%1 +shift +set M4PATH=%1 +shift +set M4= + +set PATH=%M4PATH%;%PATH% +@REM %* does not work with shift... +%bison% %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/indra/lscript/lscript_compile/indra.l b/indra/lscript/lscript_compile/indra.l new file mode 100755 index 0000000000..7772c95609 --- /dev/null +++ b/indra/lscript/lscript_compile/indra.l @@ -0,0 +1,1013 @@ + +N [0-9] +L [a-zA-Z_] +H [a-fA-F0-9] +E [Ee][+-]?{N}+ +FS (f|F) +%e 10000 +%n 4000 +%p 5000 + +%top { + #include "linden_common.h" +} + +%{ +// Deal with the fact that lex/yacc generates unreachable code +#ifdef LL_WINDOWS +#pragma warning (disable : 4018) // warning C4018: signed/unsigned mismatch +#pragma warning (disable : 4702) // warning C4702: unreachable code +#endif // LL_WINDOWS +#include "llmath.h" +#include "lscript_tree.h" +#include "lscript_typecheck.h" +#include "lscript_resource.h" +#include "indra.y.hpp" +#include "lltimer.h" +#include "indra_constants.h" +#include "lllslconstants.h" +#include "lluuid.h" +#include "llassetstorage.h" +#include "llpartdata.h" +#include "llvehicleparams.h" +#include "llpermissionsflags.h" +#include "llfollowcamparams.h" +#include "llparcelflags.h" +#include "llregionflags.h" +#include "lscript_http.h" +#include "llclickaction.h" +#include "llmediaentry.h" + +void count(); +void line_comment(); +void block_comment(); +void parse_string(); + +#define YYLMAX 16384 +#define YY_NEVER_INTERACTIVE 1 /* stops flex from calling isatty() */ +#ifdef LL_WINDOWS +#define isatty(x) 0 /* hack for bug in cygwin flex 2.5.35 */ +#endif + +#ifdef ECHO +#undef ECHO +#endif + +#define ECHO do { } while (0) + +#define yyparse indra_parse +#define yyerror indra_error +#define yylval indra_lval +#define yy_create_buffer indra__create_buffer +#define yy_delete_buffer indra__delete_buffer +#define yy_flex_debug indra__flex_debug +#define yy_init_buffer indra__init_buffer +#define yy_flush_buffer indra__flush_buffer +#define yy_load_buffer_state indra__load_buffer_state +#define yy_switch_to_buffer indra__switch_to_buffer +#define yyin indra_in +#define yyleng indra_leng +#define yylex indra_lex +#define yylineno indra_lineno +#define yyout indra_out +#define yyrestart indra_restart +#define yytext indra_text +#define yywrap indra_wrap +#define yyalloc indra_alloc +#define yyrealloc indra_realloc +#define yyfree indra_free + + +int yyparse( void ); +int yylex( void ); +int yyerror(const char *fmt, ...); + +%} + +%% +"//" { gInternalLine++; gInternalColumn = 0; line_comment(); } +"/*" { block_comment(); } + +"integer" { count(); return(INTEGER); } +"float" { count(); return(FLOAT_TYPE); } +"string" { count(); return(STRING); } +"key" { count(); return(LLKEY); } +"vector" { count(); return(VECTOR); } +"quaternion" { count(); return(QUATERNION); } +"rotation" { count(); return(QUATERNION); } +"list" { count(); return(LIST); } + +"default" { count(); yylval.sval = new char[strlen(yytext) + 1]; strcpy(yylval.sval, yytext); return(STATE_DEFAULT); } +"state" { count(); return(STATE); } +"event" { count(); return(EVENT); } +"jump" { count(); return(JUMP); } +"return" { count(); return(RETURN); } +"if" { count(); return(IF); } +"else" { count(); return(ELSE); } +"for" { count(); return(FOR); } +"do" { count(); return(DO); } +"while" { count(); return(WHILE); } + +"state_entry" { count(); return(STATE_ENTRY); } +"state_exit" { count(); return(STATE_EXIT); } +"touch_start" { count(); return(TOUCH_START); } +"touch" { count(); return(TOUCH); } +"touch_end" { count(); return(TOUCH_END); } +"collision_start" { count(); return(COLLISION_START); } +"collision" { count(); return(COLLISION); } +"collision_end" { count(); return(COLLISION_END); } +"land_collision_start" { count(); return(LAND_COLLISION_START); } +"land_collision" { count(); return(LAND_COLLISION); } +"land_collision_end" { count(); return(LAND_COLLISION_END); } +"timer" { count(); return(TIMER); } +"listen" { count(); return(CHAT); } +"sensor" { count(); return(SENSOR); } +"no_sensor" { count(); return(NO_SENSOR); } +"control" { count(); return(CONTROL); } +"print" { count(); return(PRINT); } +"at_target" { count(); return(AT_TARGET); } +"not_at_target" { count(); return(NOT_AT_TARGET); } +"at_rot_target" { count(); return(AT_ROT_TARGET); } +"not_at_rot_target" { count(); return(NOT_AT_ROT_TARGET); } +"money" { count(); return(MONEY); } +"email" { count(); return(EMAIL); } +"run_time_permissions" { count(); return(RUN_TIME_PERMISSIONS); } +"changed" { count(); return(INVENTORY); } +"attach" { count(); return(ATTACH); } +"dataserver" { count(); return(DATASERVER); } +"moving_start" { count(); return(MOVING_START); } +"moving_end" { count(); return(MOVING_END); } +"link_message" { count(); return(LINK_MESSAGE); } +"on_rez" { count(); return(REZ); } +"object_rez" { count(); return(OBJECT_REZ); } +"remote_data" { count(); return(REMOTE_DATA); } +"http_response" { count(); return(HTTP_RESPONSE); } +"http_request" { count(); return(HTTP_REQUEST); } +"." { count(); return(PERIOD); } + + +0[xX]{H}+ { count(); yylval.ival = strtoul(yytext, NULL, 0); return(INTEGER_CONSTANT); } +{N}+ { count(); yylval.ival = strtoul(yytext, NULL, 10); return(INTEGER_CONSTANT); } +"TRUE" { count(); yylval.ival = 1; return(INTEGER_TRUE); } +"FALSE" { count(); yylval.ival = 0; return(INTEGER_FALSE); } +"STATUS_PHYSICS" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_X" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_Y" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"STATUS_ROTATE_Z" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } +"STATUS_PHANTOM" { count(); yylval.ival = 0x10; return(INTEGER_CONSTANT); } +"STATUS_SANDBOX" { count(); yylval.ival = 0x20; return(INTEGER_CONSTANT); } +"STATUS_BLOCK_GRAB" { count(); yylval.ival = 0x40; return(INTEGER_CONSTANT); } +"STATUS_DIE_AT_EDGE" { count(); yylval.ival = 0x80; return(INTEGER_CONSTANT); } +"STATUS_RETURN_AT_EDGE" { count(); yylval.ival = 0x100; return(INTEGER_CONSTANT); } +"STATUS_CAST_SHADOWS" { count(); yylval.ival = 0x200; return(INTEGER_CONSTANT); } +"STATUS_BLOCK_GRAB_OBJECT" { count(); yylval.ival = 0x400; return(INTEGER_CONSTANT); } + +"AGENT_FLYING" { count(); yylval.ival = AGENT_FLYING; return(INTEGER_CONSTANT); } +"AGENT_ATTACHMENTS" { count(); yylval.ival = AGENT_ATTACHMENTS; return(INTEGER_CONSTANT); } +"AGENT_SCRIPTED" { count(); yylval.ival = AGENT_SCRIPTED; return(INTEGER_CONSTANT); } +"AGENT_MOUSELOOK" { count(); yylval.ival = AGENT_MOUSELOOK; return(INTEGER_CONSTANT); } +"AGENT_SITTING" { count(); yylval.ival = AGENT_SITTING; return(INTEGER_CONSTANT); } +"AGENT_ON_OBJECT" { count(); yylval.ival = AGENT_ON_OBJECT; return(INTEGER_CONSTANT); } +"AGENT_AWAY" { count(); yylval.ival = AGENT_AWAY; return(INTEGER_CONSTANT); } +"AGENT_WALKING" { count(); yylval.ival = AGENT_WALKING; return(INTEGER_CONSTANT); } +"AGENT_IN_AIR" { count(); yylval.ival = AGENT_IN_AIR; return(INTEGER_CONSTANT); } +"AGENT_TYPING" { count(); yylval.ival = AGENT_TYPING; return(INTEGER_CONSTANT); } +"AGENT_CROUCHING" { count(); yylval.ival = AGENT_CROUCHING; return(INTEGER_CONSTANT); } +"AGENT_BUSY" { count(); yylval.ival = AGENT_BUSY; return(INTEGER_CONSTANT); } +"AGENT_ALWAYS_RUN" { count(); yylval.ival = AGENT_ALWAYS_RUN; return(INTEGER_CONSTANT); } +"AGENT_AUTOPILOT" { count(); yylval.ival = AGENT_AUTOPILOT; return(INTEGER_CONSTANT); } + +"CAMERA_PITCH" { count(); yylval.ival = FOLLOWCAM_PITCH; return(INTEGER_CONSTANT); } +"CAMERA_FOCUS_OFFSET" { count(); yylval.ival = FOLLOWCAM_FOCUS_OFFSET; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_LAG" { count(); yylval.ival = FOLLOWCAM_POSITION_LAG; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_LAG" { count(); yylval.ival = FOLLOWCAM_FOCUS_LAG; return (INTEGER_CONSTANT); } +"CAMERA_DISTANCE" { count(); yylval.ival = FOLLOWCAM_DISTANCE; return (INTEGER_CONSTANT); } +"CAMERA_BEHINDNESS_ANGLE" { count(); yylval.ival = FOLLOWCAM_BEHINDNESS_ANGLE; return (INTEGER_CONSTANT); } +"CAMERA_BEHINDNESS_LAG" { count(); yylval.ival = FOLLOWCAM_BEHINDNESS_LAG; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_THRESHOLD" { count(); yylval.ival = FOLLOWCAM_POSITION_THRESHOLD; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_THRESHOLD" { count(); yylval.ival = FOLLOWCAM_FOCUS_THRESHOLD; return (INTEGER_CONSTANT); } +"CAMERA_ACTIVE" { count(); yylval.ival = FOLLOWCAM_ACTIVE; return (INTEGER_CONSTANT); } +"CAMERA_POSITION" { count(); yylval.ival = FOLLOWCAM_POSITION; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS" { count(); yylval.ival = FOLLOWCAM_FOCUS; return (INTEGER_CONSTANT); } +"CAMERA_POSITION_LOCKED" { count(); yylval.ival = FOLLOWCAM_POSITION_LOCKED; return (INTEGER_CONSTANT); } +"CAMERA_FOCUS_LOCKED" { count(); yylval.ival = FOLLOWCAM_FOCUS_LOCKED; return (INTEGER_CONSTANT); } + +"ANIM_ON" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"LOOP" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"REVERSE" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"PING_PONG" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } +"SMOOTH" { count(); yylval.ival = 0x10; return(INTEGER_CONSTANT); } +"ROTATE" { count(); yylval.ival = 0x20; return(INTEGER_CONSTANT); } +"SCALE" { count(); yylval.ival = 0x40; return(INTEGER_CONSTANT); } + +"ALL_SIDES" { count(); yylval.ival = LSL_ALL_SIDES; return(INTEGER_CONSTANT); } +"LINK_ROOT" { count(); yylval.ival = LSL_LINK_ROOT; return(INTEGER_CONSTANT); } +"LINK_SET" { count(); yylval.ival = LSL_LINK_SET; return(INTEGER_CONSTANT); } +"LINK_ALL_OTHERS" { count(); yylval.ival = LSL_LINK_ALL_OTHERS; return(INTEGER_CONSTANT); } +"LINK_ALL_CHILDREN" { count(); yylval.ival = LSL_LINK_ALL_CHILDREN; return(INTEGER_CONSTANT); } +"LINK_THIS" { count(); yylval.ival = LSL_LINK_THIS; return(INTEGER_CONSTANT); } + +"AGENT" { count(); yylval.ival = 0x1; return(INTEGER_CONSTANT); } +"ACTIVE" { count(); yylval.ival = 0x2; return(INTEGER_CONSTANT); } +"PASSIVE" { count(); yylval.ival = 0x4; return(INTEGER_CONSTANT); } +"SCRIPTED" { count(); yylval.ival = 0x8; return(INTEGER_CONSTANT); } + +"CONTROL_FWD" { count(); yylval.ival = AGENT_CONTROL_AT_POS; return(INTEGER_CONSTANT); } +"CONTROL_BACK" { count(); yylval.ival = AGENT_CONTROL_AT_NEG; return(INTEGER_CONSTANT); } +"CONTROL_LEFT" { count(); yylval.ival = AGENT_CONTROL_LEFT_POS; return(INTEGER_CONSTANT); } +"CONTROL_RIGHT" { count(); yylval.ival = AGENT_CONTROL_LEFT_NEG; return(INTEGER_CONSTANT); } +"CONTROL_ROT_LEFT" { count(); yylval.ival = AGENT_CONTROL_YAW_POS; return(INTEGER_CONSTANT); } +"CONTROL_ROT_RIGHT" { count(); yylval.ival = AGENT_CONTROL_YAW_NEG; return(INTEGER_CONSTANT); } +"CONTROL_UP" { count(); yylval.ival = AGENT_CONTROL_UP_POS; return(INTEGER_CONSTANT); } +"CONTROL_DOWN" { count(); yylval.ival = AGENT_CONTROL_UP_NEG; return(INTEGER_CONSTANT); } +"CONTROL_LBUTTON" { count(); yylval.ival = AGENT_CONTROL_LBUTTON_DOWN; return(INTEGER_CONSTANT); } +"CONTROL_ML_LBUTTON" { count(); yylval.ival = AGENT_CONTROL_ML_LBUTTON_DOWN; return(INTEGER_CONSTANT); } + +"PERMISSION_DEBIT" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_DEBIT]; return(INTEGER_CONSTANT); } +"PERMISSION_TAKE_CONTROLS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TAKE_CONTROLS]; return(INTEGER_CONSTANT); } +"PERMISSION_REMAP_CONTROLS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_REMAP_CONTROLS]; return(INTEGER_CONSTANT); } +"PERMISSION_TRIGGER_ANIMATION" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TRIGGER_ANIMATION]; return(INTEGER_CONSTANT); } +"PERMISSION_ATTACH" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_ATTACH]; return(INTEGER_CONSTANT); } +"PERMISSION_RELEASE_OWNERSHIP" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_RELEASE_OWNERSHIP]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_LINKS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_LINKS]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_JOINTS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_JOINTS]; return(INTEGER_CONSTANT); } +"PERMISSION_CHANGE_PERMISSIONS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CHANGE_PERMISSIONS]; return(INTEGER_CONSTANT); } +"PERMISSION_TRACK_CAMERA" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TRACK_CAMERA]; return(INTEGER_CONSTANT); } +"PERMISSION_CONTROL_CAMERA" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_CONTROL_CAMERA]; return(INTEGER_CONSTANT); } +"PERMISSION_TELEPORT" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TELEPORT]; return(INTEGER_CONSTANT); } +"PERMISSION_SILENT_ESTATE_MANAGEMENT" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_SILENT_ESTATE_MANAGEMENT]; return(INTEGER_CONSTANT); } +"PERMISSION_OVERRIDE_ANIMATIONS" { count(); yylval.ival = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS]; return(INTEGER_CONSTANT); } + +"INVENTORY_TEXTURE" { count(); yylval.ival = LLAssetType::AT_TEXTURE; return(INTEGER_CONSTANT); } +"INVENTORY_SOUND" { count(); yylval.ival = LLAssetType::AT_SOUND; return(INTEGER_CONSTANT); } +"INVENTORY_OBJECT" { count(); yylval.ival = LLAssetType::AT_OBJECT; return(INTEGER_CONSTANT); } +"INVENTORY_SCRIPT" { count(); yylval.ival = LLAssetType::AT_LSL_TEXT; return(INTEGER_CONSTANT); } +"INVENTORY_LANDMARK" { count(); yylval.ival = LLAssetType::AT_LANDMARK; return(INTEGER_CONSTANT); } +"INVENTORY_CLOTHING" { count(); yylval.ival = LLAssetType::AT_CLOTHING; return(INTEGER_CONSTANT); } +"INVENTORY_NOTECARD" { count(); yylval.ival = LLAssetType::AT_NOTECARD; return(INTEGER_CONSTANT); } +"INVENTORY_BODYPART" { count(); yylval.ival = LLAssetType::AT_BODYPART; return(INTEGER_CONSTANT); } +"INVENTORY_ANIMATION" { count(); yylval.ival = LLAssetType::AT_ANIMATION; return(INTEGER_CONSTANT); } +"INVENTORY_GESTURE" { count(); yylval.ival = LLAssetType::AT_GESTURE; return(INTEGER_CONSTANT); } +"INVENTORY_ALL" { count(); yylval.ival = LLAssetType::AT_NONE; return(INTEGER_CONSTANT); } +"INVENTORY_NONE" { count(); yylval.ival = LLAssetType::AT_NONE; return(INTEGER_CONSTANT); } + +"CHANGED_INVENTORY" { count(); yylval.ival = CHANGED_INVENTORY; return(INTEGER_CONSTANT); } +"CHANGED_COLOR" { count(); yylval.ival = CHANGED_COLOR; return(INTEGER_CONSTANT); } +"CHANGED_SHAPE" { count(); yylval.ival = CHANGED_SHAPE; return(INTEGER_CONSTANT); } +"CHANGED_SCALE" { count(); yylval.ival = CHANGED_SCALE; return(INTEGER_CONSTANT); } +"CHANGED_TEXTURE" { count(); yylval.ival = CHANGED_TEXTURE; return(INTEGER_CONSTANT); } +"CHANGED_LINK" { count(); yylval.ival = CHANGED_LINK; return(INTEGER_CONSTANT); } +"CHANGED_ALLOWED_DROP" { count(); yylval.ival = CHANGED_ALLOWED_DROP; return(INTEGER_CONSTANT); } +"CHANGED_OWNER" { count(); yylval.ival = CHANGED_OWNER; return(INTEGER_CONSTANT); } +"CHANGED_REGION" { count(); yylval.ival = CHANGED_REGION; return(INTEGER_CONSTANT); } +"CHANGED_TELEPORT" { count(); yylval.ival = CHANGED_TELEPORT; return(INTEGER_CONSTANT); } +"CHANGED_REGION_START" { count(); yylval.ival = CHANGED_REGION_START; return(INTEGER_CONSTANT); } +"CHANGED_MEDIA" { count(); yylval.ival = CHANGED_MEDIA; return(INTEGER_CONSTANT); } + +"OBJECT_UNKNOWN_DETAIL" { count(); yylval.ival = OBJECT_UNKNOWN_DETAIL; return(INTEGER_CONSTANT); } +"OBJECT_NAME" { count(); yylval.ival = OBJECT_NAME; return(INTEGER_CONSTANT); } +"OBJECT_DESC" { count(); yylval.ival = OBJECT_DESC; return(INTEGER_CONSTANT); } +"OBJECT_POS" { count(); yylval.ival = OBJECT_POS; return(INTEGER_CONSTANT); } +"OBJECT_ROT" { count(); yylval.ival = OBJECT_ROT; return(INTEGER_CONSTANT); } +"OBJECT_VELOCITY" { count(); yylval.ival = OBJECT_VELOCITY; return(INTEGER_CONSTANT); } +"OBJECT_OWNER" { count(); yylval.ival = OBJECT_OWNER; return(INTEGER_CONSTANT); } +"OBJECT_GROUP" { count(); yylval.ival = OBJECT_GROUP; return(INTEGER_CONSTANT); } +"OBJECT_CREATOR" { count(); yylval.ival = OBJECT_CREATOR; return(INTEGER_CONSTANT); } +"OBJECT_RUNNING_SCRIPT_COUNT" { count(); yylval.ival = OBJECT_RUNNING_SCRIPT_COUNT; return(INTEGER_CONSTANT); } +"OBJECT_TOTAL_SCRIPT_COUNT" { count(); yylval.ival = OBJECT_TOTAL_SCRIPT_COUNT; return(INTEGER_CONSTANT); } +"OBJECT_SCRIPT_MEMORY" { count(); yylval.ival = OBJECT_SCRIPT_MEMORY; return(INTEGER_CONSTANT); } +"OBJECT_SCRIPT_TIME" { count(); yylval.ival = OBJECT_SCRIPT_TIME; return(INTEGER_CONSTANT); } +"OBJECT_PRIM_EQUIVALENCE" { count(); yylval.ival = OBJECT_PRIM_EQUIVALENCE; return(INTEGER_CONSTANT); } +"OBJECT_SERVER_COST" { count(); yylval.ival = OBJECT_SERVER_COST; return(INTEGER_CONSTANT); } +"OBJECT_STREAMING_COST" { count(); yylval.ival = OBJECT_STREAMING_COST; return(INTEGER_CONSTANT); } +"OBJECT_PHYSICS_COST" { count(); yylval.ival = OBJECT_PHYSICS_COST; return(INTEGER_CONSTANT); } +"OBJECT_CHARACTER_TIME" { count(); yylval.ival = OBJECT_CHARACTER_TIME; return(INTEGER_CONSTANT); } +"OBJECT_ROOT" { count(); yylval.ival = OBJECT_ROOT; return(INTEGER_CONSTANT); } +"OBJECT_ATTACHED_POINT" { count(); yylval.ival = OBJECT_ATTACHED_POINT; return(INTEGER_CONSTANT); } +"OBJECT_PATHFINDING_TYPE" { count(); yylval.ival = OBJECT_PATHFINDING_TYPE; return(INTEGER_CONSTANT); } +"OBJECT_PHYSICS" { count(); yylval.ival = OBJECT_PHYSICS; return(INTEGER_CONSTANT); } +"OBJECT_PHANTOM" { count(); yylval.ival = OBJECT_PHANTOM; return(INTEGER_CONSTANT); } +"OBJECT_TEMP_ON_REZ" { count(); yylval.ival = OBJECT_TEMP_ON_REZ; return(INTEGER_CONSTANT); } +"OBJECT_RENDER_WEIGHT" { count(); yylval.ival = OBJECT_RENDER_WEIGHT; return(INTEGER_CONSTANT); } + +"TYPE_INTEGER" { count(); yylval.ival = LST_INTEGER; return(INTEGER_CONSTANT); } +"TYPE_FLOAT" { count(); yylval.ival = LST_FLOATINGPOINT; return(INTEGER_CONSTANT); } +"TYPE_STRING" { count(); yylval.ival = LST_STRING; return(INTEGER_CONSTANT); } +"TYPE_KEY" { count(); yylval.ival = LST_KEY; return(INTEGER_CONSTANT); } +"TYPE_VECTOR" { count(); yylval.ival = LST_VECTOR; return(INTEGER_CONSTANT); } +"TYPE_ROTATION" { count(); yylval.ival = LST_QUATERNION; return(INTEGER_CONSTANT); } +"TYPE_INVALID" { count(); yylval.ival = LST_NULL; return(INTEGER_CONSTANT); } + +"NULL_KEY" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "00000000-0000-0000-0000-000000000000"); return(STRING_CONSTANT); } +"EOF" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "\n\n\n"); return(STRING_CONSTANT); } +"URL_REQUEST_GRANTED" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, URL_REQUEST_GRANTED); return(STRING_CONSTANT); } +"URL_REQUEST_DENIED" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, URL_REQUEST_DENIED); return(STRING_CONSTANT); } + +"PI" { count(); yylval.fval = F_PI; return(FP_CONSTANT); } +"TWO_PI" { count(); yylval.fval = F_TWO_PI; return(FP_CONSTANT); } +"PI_BY_TWO" { count(); yylval.fval = F_PI_BY_TWO; return(FP_CONSTANT); } +"DEG_TO_RAD" { count(); yylval.fval = DEG_TO_RAD; return(FP_CONSTANT); } +"RAD_TO_DEG" { count(); yylval.fval = RAD_TO_DEG; return(FP_CONSTANT); } +"SQRT2" { count(); yylval.fval = F_SQRT2; return(FP_CONSTANT); } + +"DEBUG_CHANNEL" { count(); yylval.ival = CHAT_CHANNEL_DEBUG; return(INTEGER_CONSTANT); } +"PUBLIC_CHANNEL" { count(); yylval.ival = 0; return(INTEGER_CONSTANT); } + +"ZERO_VECTOR" { count(); return(ZERO_VECTOR); } +"ZERO_ROTATION" { count(); return(ZERO_ROTATION); } + +"ATTACH_CHEST" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"ATTACH_HEAD" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"ATTACH_LSHOULDER" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"ATTACH_RSHOULDER" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } +"ATTACH_LHAND" { count(); yylval.ival = 5; return(INTEGER_CONSTANT); } +"ATTACH_RHAND" { count(); yylval.ival = 6; return(INTEGER_CONSTANT); } +"ATTACH_LFOOT" { count(); yylval.ival = 7; return(INTEGER_CONSTANT); } +"ATTACH_RFOOT" { count(); yylval.ival = 8; return(INTEGER_CONSTANT); } +"ATTACH_BACK" { count(); yylval.ival = 9; return(INTEGER_CONSTANT); } +"ATTACH_PELVIS" { count(); yylval.ival = 10; return(INTEGER_CONSTANT); } +"ATTACH_MOUTH" { count(); yylval.ival = 11; return(INTEGER_CONSTANT); } +"ATTACH_CHIN" { count(); yylval.ival = 12; return(INTEGER_CONSTANT); } +"ATTACH_LEAR" { count(); yylval.ival = 13; return(INTEGER_CONSTANT); } +"ATTACH_REAR" { count(); yylval.ival = 14; return(INTEGER_CONSTANT); } +"ATTACH_LEYE" { count(); yylval.ival = 15; return(INTEGER_CONSTANT); } +"ATTACH_REYE" { count(); yylval.ival = 16; return(INTEGER_CONSTANT); } +"ATTACH_NOSE" { count(); yylval.ival = 17; return(INTEGER_CONSTANT); } +"ATTACH_RUARM" { count(); yylval.ival = 18; return(INTEGER_CONSTANT); } +"ATTACH_RLARM" { count(); yylval.ival = 19; return(INTEGER_CONSTANT); } +"ATTACH_LUARM" { count(); yylval.ival = 20; return(INTEGER_CONSTANT); } +"ATTACH_LLARM" { count(); yylval.ival = 21; return(INTEGER_CONSTANT); } +"ATTACH_RHIP" { count(); yylval.ival = 22; return(INTEGER_CONSTANT); } +"ATTACH_RULEG" { count(); yylval.ival = 23; return(INTEGER_CONSTANT); } +"ATTACH_RLLEG" { count(); yylval.ival = 24; return(INTEGER_CONSTANT); } +"ATTACH_LHIP" { count(); yylval.ival = 25; return(INTEGER_CONSTANT); } +"ATTACH_LULEG" { count(); yylval.ival = 26; return(INTEGER_CONSTANT); } +"ATTACH_LLLEG" { count(); yylval.ival = 27; return(INTEGER_CONSTANT); } +"ATTACH_BELLY" { count(); yylval.ival = 28; return(INTEGER_CONSTANT); } +"ATTACH_RPEC" { count(); yylval.ival = 29; return(INTEGER_CONSTANT); } +"ATTACH_LPEC" { count(); yylval.ival = 30; return(INTEGER_CONSTANT); } +"ATTACH_HUD_CENTER_2" { count(); yylval.ival = 31; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_RIGHT" { count(); yylval.ival = 32; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_CENTER" { count(); yylval.ival = 33; return(INTEGER_CONSTANT); } +"ATTACH_HUD_TOP_LEFT" { count(); yylval.ival = 34; return(INTEGER_CONSTANT); } +"ATTACH_HUD_CENTER_1" { count(); yylval.ival = 35; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM_LEFT" { count(); yylval.ival = 36; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM" { count(); yylval.ival = 37; return(INTEGER_CONSTANT); } +"ATTACH_HUD_BOTTOM_RIGHT" { count(); yylval.ival = 38; return(INTEGER_CONSTANT); } +"ATTACH_NECK" { count(); yylval.ival = 39; return(INTEGER_CONSTANT); } +"ATTACH_AVATAR_CENTER" { count(); yylval.ival = 40; return(INTEGER_CONSTANT); } + +"LAND_LEVEL" { count(); yylval.ival = E_LANDBRUSH_LEVEL; return(INTEGER_CONSTANT); } +"LAND_RAISE" { count(); yylval.ival = E_LANDBRUSH_RAISE; return(INTEGER_CONSTANT); } +"LAND_LOWER" { count(); yylval.ival = E_LANDBRUSH_LOWER; return(INTEGER_CONSTANT); } +"LAND_SMOOTH" { count(); yylval.ival = E_LANDBRUSH_SMOOTH; return(INTEGER_CONSTANT); } +"LAND_NOISE" { count(); yylval.ival = E_LANDBRUSH_NOISE; return(INTEGER_CONSTANT); } +"LAND_REVERT" { count(); yylval.ival = E_LANDBRUSH_REVERT; return(INTEGER_CONSTANT); } + +"LAND_SMALL_BRUSH" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"LAND_MEDIUM_BRUSH" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"LAND_LARGE_BRUSH" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } + +"DATA_ONLINE" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"DATA_NAME" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"DATA_BORN" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"DATA_RATING" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } +"DATA_SIM_POS" { count(); yylval.ival = 5; return(INTEGER_CONSTANT); } +"DATA_SIM_STATUS" { count(); yylval.ival = 6; return(INTEGER_CONSTANT); } +"DATA_SIM_RATING" { count(); yylval.ival = 7; return(INTEGER_CONSTANT); } +"DATA_PAYINFO" { count(); yylval.ival = 8; return(INTEGER_CONSTANT); } + +"PAYMENT_INFO_ON_FILE" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"PAYMENT_INFO_USED" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } + +"REMOTE_DATA_CHANNEL" { count(); yylval.ival = LSL_REMOTE_DATA_CHANNEL; return(INTEGER_CONSTANT); } +"REMOTE_DATA_REQUEST" { count(); yylval.ival = LSL_REMOTE_DATA_REQUEST; return(INTEGER_CONSTANT); } +"REMOTE_DATA_REPLY" { count(); yylval.ival = LSL_REMOTE_DATA_REPLY; return(INTEGER_CONSTANT); } + + +"PSYS_PART_FLAGS" { count(); yylval.ival = LLPS_PART_FLAGS; return(INTEGER_CONSTANT); } +"PSYS_PART_START_COLOR" { count(); yylval.ival = LLPS_PART_START_COLOR; return (INTEGER_CONSTANT); } +"PSYS_PART_START_ALPHA" { count(); yylval.ival = LLPS_PART_START_ALPHA; return (INTEGER_CONSTANT); } +"PSYS_PART_START_SCALE" { count(); yylval.ival = LLPS_PART_START_SCALE; return (INTEGER_CONSTANT); } +"PSYS_PART_END_COLOR" { count(); yylval.ival = LLPS_PART_END_COLOR; return (INTEGER_CONSTANT); } +"PSYS_PART_END_ALPHA" { count(); yylval.ival = LLPS_PART_END_ALPHA; return (INTEGER_CONSTANT); } +"PSYS_PART_END_SCALE" { count(); yylval.ival = LLPS_PART_END_SCALE; return (INTEGER_CONSTANT); } +"PSYS_PART_MAX_AGE" { count(); yylval.ival = LLPS_PART_MAX_AGE; return (INTEGER_CONSTANT); } + +"PSYS_PART_BLEND_FUNC_SOURCE" { count(); yylval.ival = LLPS_PART_BLEND_FUNC_SOURCE; return (INTEGER_CONSTANT); } +"PSYS_PART_BLEND_FUNC_DEST" { count(); yylval.ival = LLPS_PART_BLEND_FUNC_DEST; return (INTEGER_CONSTANT); } +"PSYS_PART_START_GLOW" { count(); yylval.ival = LLPS_PART_START_GLOW; return (INTEGER_CONSTANT); } +"PSYS_PART_END_GLOW" { count(); yylval.ival = LLPS_PART_END_GLOW; return (INTEGER_CONSTANT); } + +"PSYS_PART_WIND_MASK" { count(); yylval.ival = LLPartData::LL_PART_WIND_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_INTERP_COLOR_MASK" { count(); yylval.ival = LLPartData::LL_PART_INTERP_COLOR_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_INTERP_SCALE_MASK" { count(); yylval.ival = LLPartData::LL_PART_INTERP_SCALE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_BOUNCE_MASK" { count(); yylval.ival = LLPartData::LL_PART_BOUNCE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_FOLLOW_SRC_MASK" { count(); yylval.ival = LLPartData::LL_PART_FOLLOW_SRC_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_FOLLOW_VELOCITY_MASK" { count(); yylval.ival = LLPartData::LL_PART_FOLLOW_VELOCITY_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_TARGET_POS_MASK" { count(); yylval.ival = LLPartData::LL_PART_TARGET_POS_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_EMISSIVE_MASK" { count(); yylval.ival = LLPartData::LL_PART_EMISSIVE_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_TARGET_LINEAR_MASK" { count(); yylval.ival = LLPartData::LL_PART_TARGET_LINEAR_MASK; return(INTEGER_CONSTANT); } +"PSYS_PART_RIBBON_MASK" { count(); yylval.ival = LLPartData::LL_PART_RIBBON_MASK; return(INTEGER_CONSTANT); } + +"PSYS_PART_BF_ONE" { count(); yylval.ival = LLPartData::LL_PART_BF_ONE; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_ZERO" { count(); yylval.ival = LLPartData::LL_PART_BF_ZERO; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_DEST_COLOR" { count(); yylval.ival = LLPartData::LL_PART_BF_DEST_COLOR; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_SOURCE_COLOR" { count(); yylval.ival = LLPartData::LL_PART_BF_SOURCE_COLOR; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_ONE_MINUS_DEST_COLOR" { count(); yylval.ival = LLPartData::LL_PART_BF_ONE_MINUS_DEST_COLOR; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_ONE_MINUS_SOURCE_COLOR" { count(); yylval.ival = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_COLOR; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_SOURCE_ALPHA" { count(); yylval.ival = LLPartData::LL_PART_BF_SOURCE_ALPHA; return(INTEGER_CONSTANT); } +"PSYS_PART_BF_ONE_MINUS_SOURCE_ALPHA" { count(); yylval.ival = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA; return(INTEGER_CONSTANT); } + + +"PSYS_SRC_MAX_AGE" { count(); yylval.ival = LLPS_SRC_MAX_AGE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN" { count(); yylval.ival = LLPS_SRC_PATTERN; return(INTEGER_CONSTANT); } +"PSYS_SRC_INNERANGLE" { count(); yylval.ival = LLPS_SRC_INNERANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_OUTERANGLE" { count(); yylval.ival = LLPS_SRC_OUTERANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_ANGLE_BEGIN" { count(); yylval.ival = LLPS_SRC_ANGLE_BEGIN; return(INTEGER_CONSTANT); } +"PSYS_SRC_ANGLE_END" { count(); yylval.ival = LLPS_SRC_ANGLE_END; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_RATE" { count(); yylval.ival = LLPS_SRC_BURST_RATE; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_PART_COUNT" { count(); yylval.ival = LLPS_SRC_BURST_PART_COUNT; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_RADIUS" { count(); yylval.ival = LLPS_SRC_BURST_RADIUS; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_SPEED_MIN" { count(); yylval.ival = LLPS_SRC_BURST_SPEED_MIN; return(INTEGER_CONSTANT); } +"PSYS_SRC_BURST_SPEED_MAX" { count(); yylval.ival = LLPS_SRC_BURST_SPEED_MAX; return(INTEGER_CONSTANT); } +"PSYS_SRC_ACCEL" { count(); yylval.ival = LLPS_SRC_ACCEL; return(INTEGER_CONSTANT); } +"PSYS_SRC_TEXTURE" { count(); yylval.ival = LLPS_SRC_TEXTURE; return(INTEGER_CONSTANT); } +"PSYS_SRC_TARGET_KEY" { count(); yylval.ival = LLPS_SRC_TARGET_UUID; return(INTEGER_CONSTANT); } +"PSYS_SRC_OMEGA" { count(); yylval.ival = LLPS_SRC_OMEGA; return(INTEGER_CONSTANT); } + +"PSYS_SRC_OBJ_REL_MASK" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_OBJ_REL_MASK; return(INTEGER_CONSTANT); } + +"PSYS_SRC_PATTERN_DROP" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_DROP; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_EXPLODE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE_CONE" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE; return(INTEGER_CONSTANT); } +"PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY" { count(); yylval.ival = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE_EMPTY; return(INTEGER_CONSTANT); } + + +"VEHICLE_TYPE_NONE" { count(); yylval.ival = VEHICLE_TYPE_NONE; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_SLED" { count(); yylval.ival = VEHICLE_TYPE_SLED; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_CAR" { count(); yylval.ival = VEHICLE_TYPE_CAR; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_BOAT" { count(); yylval.ival = VEHICLE_TYPE_BOAT; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_AIRPLANE" { count(); yylval.ival = VEHICLE_TYPE_AIRPLANE; return(INTEGER_CONSTANT); } +"VEHICLE_TYPE_BALLOON" { count(); yylval.ival = VEHICLE_TYPE_BALLOON; return(INTEGER_CONSTANT); } + +"VEHICLE_REFERENCE_FRAME" { count(); yylval.ival = VEHICLE_REFERENCE_FRAME; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_FRICTION_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_FRICTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_FRICTION_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_FRICTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_DIRECTION" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_DIRECTION; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_DIRECTION" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_DIRECTION; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_OFFSET" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_OFFSET; return(INTEGER_CONSTANT); } + + + +"VEHICLE_HOVER_HEIGHT" { count(); yylval.ival = VEHICLE_HOVER_HEIGHT; return(INTEGER_CONSTANT); } +"VEHICLE_HOVER_EFFICIENCY" { count(); yylval.ival = VEHICLE_HOVER_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_HOVER_TIMESCALE" { count(); yylval.ival = VEHICLE_HOVER_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_BUOYANCY" { count(); yylval.ival = VEHICLE_BUOYANCY; return(INTEGER_CONSTANT); } + +"VEHICLE_LINEAR_DEFLECTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_LINEAR_DEFLECTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_DEFLECTION_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_DEFLECTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE" { count(); yylval.ival = VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_DEFLECTION_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_DEFLECTION_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_TIMESCALE; return(INTEGER_CONSTANT); } +"VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE" { count(); yylval.ival = VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY" { count(); yylval.ival = VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_VERTICAL_ATTRACTION_TIMESCALE" { count(); yylval.ival = VEHICLE_VERTICAL_ATTRACTION_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_BANKING_EFFICIENCY" { count(); yylval.ival = VEHICLE_BANKING_EFFICIENCY; return(INTEGER_CONSTANT); } +"VEHICLE_BANKING_MIX" { count(); yylval.ival = VEHICLE_BANKING_MIX; return(INTEGER_CONSTANT); } +"VEHICLE_BANKING_TIMESCALE" { count(); yylval.ival = VEHICLE_BANKING_TIMESCALE; return(INTEGER_CONSTANT); } + +"VEHICLE_FLAG_NO_FLY_UP" { count(); yylval.ival = VEHICLE_FLAG_NO_DEFLECTION_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_NO_DEFLECTION_UP" { count(); yylval.ival = VEHICLE_FLAG_NO_DEFLECTION_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_LIMIT_ROLL_ONLY" { count(); yylval.ival = VEHICLE_FLAG_LIMIT_ROLL_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_WATER_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_WATER_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_TERRAIN_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_TERRAIN_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT" { count(); yylval.ival = VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_HOVER_UP_ONLY" { count(); yylval.ival = VEHICLE_FLAG_HOVER_UP_ONLY; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_LIMIT_MOTOR_UP" { count(); yylval.ival = VEHICLE_FLAG_LIMIT_MOTOR_UP; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_MOUSELOOK_STEER" { count(); yylval.ival = VEHICLE_FLAG_MOUSELOOK_STEER; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_MOUSELOOK_BANK" { count(); yylval.ival = VEHICLE_FLAG_MOUSELOOK_BANK; return(INTEGER_CONSTANT); } +"VEHICLE_FLAG_CAMERA_DECOUPLED" { count(); yylval.ival = VEHICLE_FLAG_CAMERA_DECOUPLED; return(INTEGER_CONSTANT); } + + + +"PRIM_TYPE" { count(); yylval.ival = LSL_PRIM_TYPE; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL" { count(); yylval.ival = LSL_PRIM_MATERIAL; return(INTEGER_CONSTANT); } +"PRIM_PHYSICS" { count(); yylval.ival = LSL_PRIM_PHYSICS; return(INTEGER_CONSTANT); } +"PRIM_FLEXIBLE" { count(); yylval.ival = LSL_PRIM_FLEXIBLE; return(INTEGER_CONSTANT); } +"PRIM_POINT_LIGHT" { count(); yylval.ival = LSL_PRIM_POINT_LIGHT; return(INTEGER_CONSTANT); } +"PRIM_TEMP_ON_REZ" { count(); yylval.ival = LSL_PRIM_TEMP_ON_REZ; return(INTEGER_CONSTANT); } +"PRIM_PHANTOM" { count(); yylval.ival = LSL_PRIM_PHANTOM; return(INTEGER_CONSTANT); } +"PRIM_CAST_SHADOWS" { count(); yylval.ival = LSL_PRIM_CAST_SHADOWS; return(INTEGER_CONSTANT); } +"PRIM_POSITION" { count(); yylval.ival = LSL_PRIM_POSITION; return(INTEGER_CONSTANT); } +"PRIM_SIZE" { count(); yylval.ival = LSL_PRIM_SIZE; return(INTEGER_CONSTANT); } +"PRIM_ROTATION" { count(); yylval.ival = LSL_PRIM_ROTATION; return(INTEGER_CONSTANT); } +"PRIM_TEXTURE" { count(); yylval.ival = LSL_PRIM_TEXTURE; return(INTEGER_CONSTANT); } +"PRIM_COLOR" { count(); yylval.ival = LSL_PRIM_COLOR; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SHINY" { count(); yylval.ival = LSL_PRIM_BUMP_SHINY; return(INTEGER_CONSTANT); } +"PRIM_FULLBRIGHT" { count(); yylval.ival = LSL_PRIM_FULLBRIGHT; return(INTEGER_CONSTANT); } +"PRIM_TEXGEN" { count(); yylval.ival = LSL_PRIM_TEXGEN; return(INTEGER_CONSTANT); } +"PRIM_GLOW" { count(); yylval.ival = LSL_PRIM_GLOW; return(INTEGER_CONSTANT); } +"PRIM_TEXT" { count(); yylval.ival = LSL_PRIM_TEXT; return(INTEGER_CONSTANT); } +"PRIM_NAME" { count(); yylval.ival = LSL_PRIM_NAME; return(INTEGER_CONSTANT); } +"PRIM_DESC" { count(); yylval.ival = LSL_PRIM_DESC; return(INTEGER_CONSTANT); } +"PRIM_OMEGA" { count(); yylval.ival = LSL_PRIM_OMEGA; return(INTEGER_CONSTANT); } +"PRIM_LINK_TARGET" { count(); yylval.ival = LSL_PRIM_LINK_TARGET; return(INTEGER_CONSTANT); } +"PRIM_SLICE" { count(); yylval.ival = LSL_PRIM_SLICE; return(INTEGER_CONSTANT); } + +"PRIM_PHYSICS_SHAPE_PRIM" { count(); yylval.ival = LSL_PRIM_PHYSICS_SHAPE_PRIM; return(INTEGER_CONSTANT); } +"PRIM_PHYSICS_SHAPE_NONE" { count(); yylval.ival = LSL_PRIM_PHYSICS_SHAPE_NONE; return(INTEGER_CONSTANT); } +"PRIM_PHYSICS_SHAPE_CONVEX" { count(); yylval.ival = LSL_PRIM_PHYSICS_SHAPE_CONVEX; return(INTEGER_CONSTANT); } + +"PRIM_TYPE_BOX" { count(); yylval.ival = LSL_PRIM_TYPE_BOX; return(INTEGER_CONSTANT); } +"PRIM_TYPE_CYLINDER" { count(); yylval.ival = LSL_PRIM_TYPE_CYLINDER; return(INTEGER_CONSTANT); } +"PRIM_TYPE_PRISM" { count(); yylval.ival = LSL_PRIM_TYPE_PRISM; return(INTEGER_CONSTANT); } +"PRIM_TYPE_SPHERE" { count(); yylval.ival = LSL_PRIM_TYPE_SPHERE; return(INTEGER_CONSTANT); } +"PRIM_TYPE_TORUS" { count(); yylval.ival = LSL_PRIM_TYPE_TORUS; return(INTEGER_CONSTANT); } +"PRIM_TYPE_TUBE" { count(); yylval.ival = LSL_PRIM_TYPE_TUBE; return(INTEGER_CONSTANT); } +"PRIM_TYPE_RING" { count(); yylval.ival = LSL_PRIM_TYPE_RING; return(INTEGER_CONSTANT); } +"PRIM_TYPE_SCULPT" { count(); yylval.ival = LSL_PRIM_TYPE_SCULPT; return(INTEGER_CONSTANT); } + +"PRIM_HOLE_DEFAULT" { count(); yylval.ival = LSL_PRIM_HOLE_DEFAULT; return(INTEGER_CONSTANT); } +"PRIM_HOLE_CIRCLE" { count(); yylval.ival = LSL_PRIM_HOLE_CIRCLE; return(INTEGER_CONSTANT); } +"PRIM_HOLE_SQUARE" { count(); yylval.ival = LSL_PRIM_HOLE_SQUARE; return(INTEGER_CONSTANT); } +"PRIM_HOLE_TRIANGLE" { count(); yylval.ival = LSL_PRIM_HOLE_TRIANGLE; return(INTEGER_CONSTANT); } + +"PRIM_MATERIAL_STONE" { count(); yylval.ival = LSL_PRIM_MATERIAL_STONE; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_METAL" { count(); yylval.ival = LSL_PRIM_MATERIAL_METAL; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_GLASS" { count(); yylval.ival = LSL_PRIM_MATERIAL_GLASS; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_WOOD" { count(); yylval.ival = LSL_PRIM_MATERIAL_WOOD; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_FLESH" { count(); yylval.ival = LSL_PRIM_MATERIAL_FLESH; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_PLASTIC" { count(); yylval.ival = LSL_PRIM_MATERIAL_PLASTIC; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_RUBBER" { count(); yylval.ival = LSL_PRIM_MATERIAL_RUBBER; return(INTEGER_CONSTANT); } +"PRIM_MATERIAL_LIGHT" { count(); yylval.ival = LSL_PRIM_MATERIAL_LIGHT; return(INTEGER_CONSTANT); } + +"PRIM_SHINY_NONE" { count(); yylval.ival = LSL_PRIM_SHINY_NONE; return(INTEGER_CONSTANT); } +"PRIM_SHINY_LOW" { count(); yylval.ival = LSL_PRIM_SHINY_LOW; return(INTEGER_CONSTANT); } +"PRIM_SHINY_MEDIUM" { count(); yylval.ival = LSL_PRIM_SHINY_MEDIUM; return(INTEGER_CONSTANT); } +"PRIM_SHINY_HIGH" { count(); yylval.ival = LSL_PRIM_SHINY_HIGH; return(INTEGER_CONSTANT); } + +"PRIM_BUMP_NONE" { count(); yylval.ival = LSL_PRIM_BUMP_NONE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BRIGHT" { count(); yylval.ival = LSL_PRIM_BUMP_BRIGHT; return(INTEGER_CONSTANT); } +"PRIM_BUMP_DARK" { count(); yylval.ival = LSL_PRIM_BUMP_DARK; return(INTEGER_CONSTANT); } +"PRIM_BUMP_WOOD" { count(); yylval.ival = LSL_PRIM_BUMP_WOOD; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BARK" { count(); yylval.ival = LSL_PRIM_BUMP_BARK; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BRICKS" { count(); yylval.ival = LSL_PRIM_BUMP_BRICKS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_CHECKER" { count(); yylval.ival = LSL_PRIM_BUMP_CHECKER; return(INTEGER_CONSTANT); } +"PRIM_BUMP_CONCRETE" { count(); yylval.ival = LSL_PRIM_BUMP_CONCRETE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_TILE" { count(); yylval.ival = LSL_PRIM_BUMP_TILE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_STONE" { count(); yylval.ival = LSL_PRIM_BUMP_STONE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_DISKS" { count(); yylval.ival = LSL_PRIM_BUMP_DISKS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_GRAVEL" { count(); yylval.ival = LSL_PRIM_BUMP_GRAVEL; return(INTEGER_CONSTANT); } +"PRIM_BUMP_BLOBS" { count(); yylval.ival = LSL_PRIM_BUMP_BLOBS; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SIDING" { count(); yylval.ival = LSL_PRIM_BUMP_SIDING; return(INTEGER_CONSTANT); } +"PRIM_BUMP_LARGETILE" { count(); yylval.ival = LSL_PRIM_BUMP_LARGETILE; return(INTEGER_CONSTANT); } +"PRIM_BUMP_STUCCO" { count(); yylval.ival = LSL_PRIM_BUMP_STUCCO; return(INTEGER_CONSTANT); } +"PRIM_BUMP_SUCTION" { count(); yylval.ival = LSL_PRIM_BUMP_SUCTION; return(INTEGER_CONSTANT); } +"PRIM_BUMP_WEAVE" { count(); yylval.ival = LSL_PRIM_BUMP_WEAVE; return(INTEGER_CONSTANT); } + +"PRIM_TEXGEN_DEFAULT" { count(); yylval.ival = LSL_PRIM_TEXGEN_DEFAULT; return(INTEGER_CONSTANT); } +"PRIM_TEXGEN_PLANAR" { count(); yylval.ival = LSL_PRIM_TEXGEN_PLANAR; return(INTEGER_CONSTANT); } + +"PRIM_SCULPT_TYPE_SPHERE" { count(); yylval.ival = LSL_PRIM_SCULPT_TYPE_SPHERE; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_TYPE_TORUS" { count(); yylval.ival = LSL_PRIM_SCULPT_TYPE_TORUS; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_TYPE_PLANE" { count(); yylval.ival = LSL_PRIM_SCULPT_TYPE_PLANE; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_TYPE_CYLINDER" { count(); yylval.ival = LSL_PRIM_SCULPT_TYPE_CYLINDER; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_TYPE_MASK" { count(); yylval.ival = LSL_PRIM_SCULPT_TYPE_MASK; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_FLAG_MIRROR" { count(); yylval.ival = LSL_PRIM_SCULPT_FLAG_MIRROR; return(INTEGER_CONSTANT); } +"PRIM_SCULPT_FLAG_INVERT" { count(); yylval.ival = LSL_PRIM_SCULPT_FLAG_INVERT; return(INTEGER_CONSTANT); } + +"MASK_BASE" { count(); yylval.ival = 0; return(INTEGER_CONSTANT); } +"MASK_OWNER" { count(); yylval.ival = 1; return(INTEGER_CONSTANT); } +"MASK_GROUP" { count(); yylval.ival = 2; return(INTEGER_CONSTANT); } +"MASK_EVERYONE" { count(); yylval.ival = 3; return(INTEGER_CONSTANT); } +"MASK_NEXT" { count(); yylval.ival = 4; return(INTEGER_CONSTANT); } + +"PERM_TRANSFER" { count(); yylval.ival = PERM_TRANSFER; return(INTEGER_CONSTANT); } +"PERM_MODIFY" { count(); yylval.ival = PERM_MODIFY; return(INTEGER_CONSTANT); } +"PERM_COPY" { count(); yylval.ival = PERM_COPY; return(INTEGER_CONSTANT); } +"PERM_MOVE" { count(); yylval.ival = PERM_MOVE; return(INTEGER_CONSTANT); } +"PERM_ALL" { count(); yylval.ival = PERM_ALL; return(INTEGER_CONSTANT); } + +"PARCEL_MEDIA_COMMAND_STOP" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_STOP; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_PAUSE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_PAUSE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_PLAY" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_PLAY; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_LOOP" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_LOOP; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_TEXTURE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_TEXTURE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_URL" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_URL; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_TIME" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_TIME; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_AGENT" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_AGENT; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_UNLOAD" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_UNLOAD; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_AUTO_ALIGN" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_AUTO_ALIGN; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_TYPE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_TYPE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_SIZE" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_SIZE; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_DESC" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_DESC; return(INTEGER_CONSTANT); } +"PARCEL_MEDIA_COMMAND_LOOP_SET" { count(); yylval.ival = PARCEL_MEDIA_COMMAND_LOOP_SET; return(INTEGER_CONSTANT); } + +"LIST_STAT_MAX" { count(); yylval.ival = LIST_STAT_MAX; return(INTEGER_CONSTANT); } +"LIST_STAT_MIN" { count(); yylval.ival = LIST_STAT_MIN; return(INTEGER_CONSTANT); } +"LIST_STAT_MEAN" { count(); yylval.ival = LIST_STAT_MEAN; return(INTEGER_CONSTANT); } +"LIST_STAT_MEDIAN" { count(); yylval.ival = LIST_STAT_MEDIAN; return(INTEGER_CONSTANT); } +"LIST_STAT_STD_DEV" { count(); yylval.ival = LIST_STAT_STD_DEV; return(INTEGER_CONSTANT); } +"LIST_STAT_SUM" { count(); yylval.ival = LIST_STAT_SUM; return(INTEGER_CONSTANT); } +"LIST_STAT_SUM_SQUARES" { count(); yylval.ival = LIST_STAT_SUM_SQUARES; return(INTEGER_CONSTANT); } +"LIST_STAT_NUM_COUNT" { count(); yylval.ival = LIST_STAT_NUM_COUNT; return(INTEGER_CONSTANT); } +"LIST_STAT_GEOMETRIC_MEAN" { count(); yylval.ival = LIST_STAT_GEO_MEAN; return(INTEGER_CONSTANT); } +"LIST_STAT_RANGE" { count(); yylval.ival = LIST_STAT_RANGE; return(INTEGER_CONSTANT); } + +"PAY_HIDE" { count(); yylval.ival = PAY_PRICE_HIDE; return(INTEGER_CONSTANT); } +"PAY_DEFAULT" { count(); yylval.ival = PAY_PRICE_DEFAULT; return(INTEGER_CONSTANT); } + +"PARCEL_FLAG_ALLOW_FLY" { count(); yylval.ival = PF_ALLOW_FLY; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_GROUP_SCRIPTS" { count(); yylval.ival = PF_ALLOW_GROUP_SCRIPTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_SCRIPTS" { count(); yylval.ival = PF_ALLOW_OTHER_SCRIPTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_LANDMARK" { count(); yylval.ival = PF_ALLOW_LANDMARK; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_TERRAFORM" { count(); yylval.ival = PF_ALLOW_TERRAFORM; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_DAMAGE" { count(); yylval.ival = PF_ALLOW_DAMAGE; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_CREATE_OBJECTS" { count(); yylval.ival = PF_CREATE_OBJECTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS" { count(); yylval.ival = PF_CREATE_GROUP_OBJECTS; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_ACCESS_GROUP" { count(); yylval.ival = PF_USE_ACCESS_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_ACCESS_LIST" { count(); yylval.ival = PF_USE_ACCESS_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_BAN_LIST" { count(); yylval.ival = PF_USE_BAN_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_USE_LAND_PASS_LIST" { count(); yylval.ival = PF_USE_PASS_LIST; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_LOCAL_SOUND_ONLY" { count(); yylval.ival = PF_SOUND_LOCAL; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_RESTRICT_PUSHOBJECT" { count(); yylval.ival = PF_RESTRICT_PUSHOBJECT; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_GROUP_OBJECT_ENTRY" { count(); yylval.ival = PF_ALLOW_GROUP_OBJECT_ENTRY; return(INTEGER_CONSTANT); } +"PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY" { count(); yylval.ival = PF_ALLOW_ALL_OBJECT_ENTRY; return(INTEGER_CONSTANT); } + +"REGION_FLAG_ALLOW_DAMAGE" { count(); yylval.ival = REGION_FLAGS_ALLOW_DAMAGE; return(INTEGER_CONSTANT); } +"REGION_FLAG_FIXED_SUN" { count(); yylval.ival = REGION_FLAGS_SUN_FIXED; return(INTEGER_CONSTANT); } +"REGION_FLAG_BLOCK_TERRAFORM" { count(); yylval.ival = REGION_FLAGS_BLOCK_TERRAFORM; return(INTEGER_CONSTANT); } +"REGION_FLAG_SANDBOX" { count(); yylval.ival = REGION_FLAGS_SANDBOX; return(INTEGER_CONSTANT); } +"REGION_FLAG_DISABLE_COLLISIONS" { count(); yylval.ival = REGION_FLAGS_SKIP_COLLISIONS; return(INTEGER_CONSTANT); } +"REGION_FLAG_DISABLE_PHYSICS" { count(); yylval.ival = REGION_FLAGS_SKIP_PHYSICS; return(INTEGER_CONSTANT); } +"REGION_FLAG_BLOCK_FLY" { count(); yylval.ival = REGION_FLAGS_BLOCK_FLY; return(INTEGER_CONSTANT); } +"REGION_FLAG_BLOCK_FLYOVER" { count(); yylval.ival = REGION_FLAGS_BLOCK_FLYOVER; return(INTEGER_CONSTANT); } +"REGION_FLAG_ALLOW_DIRECT_TELEPORT" { count(); yylval.ival = REGION_FLAGS_ALLOW_DIRECT_TELEPORT; return(INTEGER_CONSTANT); } +"REGION_FLAG_RESTRICT_PUSHOBJECT" { count(); yylval.ival = REGION_FLAGS_RESTRICT_PUSHOBJECT; return(INTEGER_CONSTANT); } + +"HTTP_METHOD" { count(); yylval.ival = HTTP_METHOD; return(INTEGER_CONSTANT); } +"HTTP_MIMETYPE" { count(); yylval.ival = HTTP_MIMETYPE; return(INTEGER_CONSTANT); } +"HTTP_BODY_MAXLENGTH" { count(); yylval.ival = HTTP_BODY_MAXLENGTH; return(INTEGER_CONSTANT); } +"HTTP_BODY_TRUNCATED" { count(); yylval.ival = HTTP_BODY_TRUNCATED; return(INTEGER_CONSTANT); } +"HTTP_VERIFY_CERT" { count(); yylval.ival = HTTP_VERIFY_CERT; return(INTEGER_CONSTANT); } + +"PARCEL_COUNT_TOTAL" { count(); yylval.ival = OC_TOTAL; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_OWNER" { count(); yylval.ival = OC_OWNER; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_GROUP" { count(); yylval.ival = OC_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_OTHER" { count(); yylval.ival = OC_OTHER; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_SELECTED" { count(); yylval.ival = OC_SELECTED; return(INTEGER_CONSTANT); } +"PARCEL_COUNT_TEMP" { count(); yylval.ival = OC_TEMP; return(INTEGER_CONSTANT); } + +"PARCEL_DETAILS_NAME" { count(); yylval.ival = PARCEL_DETAILS_NAME; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_DESC" { count(); yylval.ival = PARCEL_DETAILS_DESC; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_OWNER" { count(); yylval.ival = PARCEL_DETAILS_OWNER; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_GROUP" { count(); yylval.ival = PARCEL_DETAILS_GROUP; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_AREA" { count(); yylval.ival = PARCEL_DETAILS_AREA; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_ID" { count(); yylval.ival = PARCEL_DETAILS_ID; return(INTEGER_CONSTANT); } +"PARCEL_DETAILS_SEE_AVATARS" { count(); yylval.ival = PARCEL_DETAILS_SEE_AVATARS; return(INTEGER_CONSTANT); } + +"STRING_TRIM_HEAD" { count(); yylval.ival = STRING_TRIM_HEAD; return(INTEGER_CONSTANT); } +"STRING_TRIM_TAIL" { count(); yylval.ival = STRING_TRIM_TAIL; return(INTEGER_CONSTANT); } +"STRING_TRIM" { count(); yylval.ival = STRING_TRIM; return(INTEGER_CONSTANT); } + +"CLICK_ACTION_NONE" { count(); yylval.ival = CLICK_ACTION_NONE; return(INTEGER_CONSTANT); } +"CLICK_ACTION_TOUCH" { count(); yylval.ival = CLICK_ACTION_TOUCH; return(INTEGER_CONSTANT); } +"CLICK_ACTION_SIT" { count(); yylval.ival = CLICK_ACTION_SIT; return(INTEGER_CONSTANT); } +"CLICK_ACTION_BUY" { count(); yylval.ival = CLICK_ACTION_BUY; return(INTEGER_CONSTANT); } +"CLICK_ACTION_PAY" { count(); yylval.ival = CLICK_ACTION_PAY; return(INTEGER_CONSTANT); } +"CLICK_ACTION_OPEN" { count(); yylval.ival = CLICK_ACTION_OPEN; return(INTEGER_CONSTANT); } +"CLICK_ACTION_PLAY" { count(); yylval.ival = CLICK_ACTION_PLAY; return(INTEGER_CONSTANT); } +"CLICK_ACTION_OPEN_MEDIA" { count(); yylval.ival = CLICK_ACTION_OPEN_MEDIA; return(INTEGER_CONSTANT); } +"CLICK_ACTION_ZOOM" { count(); yylval.ival = CLICK_ACTION_ZOOM; return(INTEGER_CONSTANT); } + +"TEXTURE_BLANK" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "5748decc-f629-461c-9a36-a35a221fe21f"); return(STRING_CONSTANT); } +"TEXTURE_DEFAULT" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "89556747-24cb-43ed-920b-47caed15465f"); return(STRING_CONSTANT); } +"TEXTURE_MEDIA" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "8b5fec65-8d8d-9dc5-cda8-8fdf2716e361"); return(STRING_CONSTANT); } +"TEXTURE_PLYWOOD" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "89556747-24cb-43ed-920b-47caed15465f"); return(STRING_CONSTANT); } +"TEXTURE_TRANSPARENT" { yylval.sval = new char[UUID_STR_LENGTH]; strcpy(yylval.sval, "8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"); return(STRING_CONSTANT); } + +"TOUCH_INVALID_FACE" { count(); yylval.ival = -1; return(INTEGER_CONSTANT); } +"TOUCH_INVALID_VECTOR" { count(); return(TOUCH_INVALID_VECTOR); } +"TOUCH_INVALID_TEXCOORD" { count(); return(TOUCH_INVALID_TEXCOORD); } + +"PRIM_MEDIA_ALT_IMAGE_ENABLE" { count(); yylval.ival = LLMediaEntry::ALT_IMAGE_ENABLE_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_CONTROLS" { count(); yylval.ival = LLMediaEntry::CONTROLS_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_CURRENT_URL" { count(); yylval.ival = LLMediaEntry::CURRENT_URL_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_HOME_URL" { count(); yylval.ival = LLMediaEntry::HOME_URL_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_AUTO_LOOP" { count(); yylval.ival = LLMediaEntry::AUTO_LOOP_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_AUTO_PLAY" { count(); yylval.ival = LLMediaEntry::AUTO_PLAY_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_AUTO_SCALE" { count(); yylval.ival = LLMediaEntry::AUTO_SCALE_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_AUTO_ZOOM" { count(); yylval.ival = LLMediaEntry::AUTO_ZOOM_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_FIRST_CLICK_INTERACT" { count(); yylval.ival = LLMediaEntry::FIRST_CLICK_INTERACT_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_WIDTH_PIXELS" { count(); yylval.ival = LLMediaEntry::WIDTH_PIXELS_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_HEIGHT_PIXELS" { count(); yylval.ival = LLMediaEntry::HEIGHT_PIXELS_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_WHITELIST_ENABLE" { count(); yylval.ival = LLMediaEntry::WHITELIST_ENABLE_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_WHITELIST" { count(); yylval.ival = LLMediaEntry::WHITELIST_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PERMS_INTERACT" { count(); yylval.ival = LLMediaEntry::PERMS_INTERACT_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PERMS_CONTROL" { count(); yylval.ival = LLMediaEntry::PERMS_CONTROL_ID; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PARAM_MAX" { count(); yylval.ival = LLMediaEntry::PARAM_MAX_ID; return(INTEGER_CONSTANT); } + +"PRIM_MEDIA_CONTROLS_STANDARD" { count(); yylval.ival = LLMediaEntry::STANDARD; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_CONTROLS_MINI" { count(); yylval.ival = LLMediaEntry::MINI; return(INTEGER_CONSTANT); } + +"PRIM_MEDIA_PERM_NONE" { count(); yylval.ival = LLMediaEntry::PERM_NONE; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PERM_OWNER" { count(); yylval.ival = LLMediaEntry::PERM_OWNER; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PERM_GROUP" { count(); yylval.ival = LLMediaEntry::PERM_GROUP; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_PERM_ANYONE" { count(); yylval.ival = LLMediaEntry::PERM_ANYONE; return(INTEGER_CONSTANT); } + +"PRIM_MEDIA_MAX_URL_LENGTH" { count(); yylval.ival = LLMediaEntry::MAX_URL_LENGTH; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_MAX_WHITELIST_SIZE" { count(); yylval.ival = LLMediaEntry::MAX_WHITELIST_SIZE; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_MAX_WHITELIST_COUNT" { count(); yylval.ival = LLMediaEntry::MAX_WHITELIST_COUNT; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_MAX_WIDTH_PIXELS" { count(); yylval.ival = LLMediaEntry::MAX_WIDTH_PIXELS; return(INTEGER_CONSTANT); } +"PRIM_MEDIA_MAX_HEIGHT_PIXELS" { count(); yylval.ival = LLMediaEntry::MAX_HEIGHT_PIXELS; return(INTEGER_CONSTANT); } + +"STATUS_OK" { count(); yylval.ival = LSL_STATUS_OK; return(INTEGER_CONSTANT); } +"STATUS_MALFORMED_PARAMS" { count(); yylval.ival = LSL_STATUS_MALFORMED_PARAMS; return(INTEGER_CONSTANT); } +"STATUS_TYPE_MISMATCH" { count(); yylval.ival = LSL_STATUS_TYPE_MISMATCH; return(INTEGER_CONSTANT); } +"STATUS_BOUNDS_ERROR" { count(); yylval.ival = LSL_STATUS_BOUNDS_ERROR; return(INTEGER_CONSTANT); } +"STATUS_NOT_FOUND" { count(); yylval.ival = LSL_STATUS_NOT_FOUND; return(INTEGER_CONSTANT); } +"STATUS_NOT_SUPPORTED" { count(); yylval.ival = LSL_STATUS_NOT_SUPPORTED; return(INTEGER_CONSTANT); } +"STATUS_INTERNAL_ERROR" { count(); yylval.ival = LSL_STATUS_INTERNAL_ERROR; return(INTEGER_CONSTANT); } +"STATUS_WHITELIST_FAILED" { count(); yylval.ival = LSL_STATUS_WHITELIST_FAILED; return(INTEGER_CONSTANT); } + +{L}({L}|{N})* { count(); yylval.sval = new char[strlen(yytext) + 1]; strcpy(yylval.sval, yytext); return(IDENTIFIER); } + +{N}+{E} { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } +{N}*"."{N}+({E})?{FS}? { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } +{N}+"."{N}*({E})?{FS}? { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } + +L?\"(\\.|[^\\"])*\" { parse_string(); count(); return(STRING_CONSTANT); } + +"++" { count(); return(INC_OP); } +"--" { count(); return(DEC_OP); } +"+=" { count(); return(ADD_ASSIGN); } +"-=" { count(); return(SUB_ASSIGN); } +"*=" { count(); return(MUL_ASSIGN); } +"/=" { count(); return(DIV_ASSIGN); } +"%=" { count(); return(MOD_ASSIGN); } +";" { count(); return(';'); } +"{" { count(); return('{'); } +"}" { count(); return('}'); } +"," { count(); return(','); } +"=" { count(); return('='); } +"(" { count(); return('('); } +")" { count(); return(')'); } +"-" { count(); return('-'); } +"+" { count(); return('+'); } +"*" { count(); return('*'); } +"/" { count(); return('/'); } +"%" { count(); return('%'); } +"@" { count(); return('@'); } +":" { count(); return(':'); } +">" { count(); return('>'); } +"<" { count(); return('<'); } +"]" { count(); return(']'); } +"[" { count(); return('['); } +"==" { count(); return(EQ); } +"!=" { count(); return(NEQ); } +">=" { count(); return(GEQ); } +"<=" { count(); return(LEQ); } +"&" { count(); return('&'); } +"|" { count(); return('|'); } +"^" { count(); return('^'); } +"~" { count(); return('~'); } +"!" { count(); return('!'); } +"&&" { count(); return(BOOLEAN_AND); } +"||" { count(); return(BOOLEAN_OR); } +"<<" { count(); return(SHIFT_LEFT); } +">>" { count(); return(SHIFT_RIGHT); } + +[ \t\v\n\f] { count(); } +. { /* ignore bad characters */ } + +%% + +LLScriptAllocationManager *gAllocationManager; +LLScriptScript *gScriptp; + +// Prototype for the yacc parser entry point +int yyparse(void); + +int yyerror(const char *fmt, ...) +{ + gErrorToText.writeError(yyout, gLine, gColumn, LSERROR_SYNTAX_ERROR); + return 0; +} + +//#define EMERGENCY_DEBUG_PRINTOUTS +//#define EMIT_CIL_ASSEMBLER + +BOOL lscript_compile(const char* src_filename, const char* dst_filename, + const char* err_filename, BOOL compile_to_mono, const char* class_name, BOOL is_god_like) +{ + BOOL b_parse_ok = FALSE; + BOOL b_dummy = FALSE; + U64 b_dummy_count = FALSE; + LSCRIPTType type = LST_NULL; + + gInternalColumn = 0; + gInternalLine = 0; + gScriptp = NULL; + + gErrorToText.init(); + init_supported_expressions(); + init_temp_jumps(); + gAllocationManager = new LLScriptAllocationManager(); + + yyin = LLFile::fopen(std::string(src_filename), "r"); + if (yyin) + { + yyout = LLFile::fopen(std::string(err_filename), "w"); + + // Reset the lexer's internal buffering. + + yyrestart(yyin); + + b_parse_ok = !yyparse(); + + if (b_parse_ok) + { +#ifdef EMERGENCY_DEBUG_PRINTOUTS + char compiled[256]; + sprintf(compiled, "%s.o", src_filename); + LLFILE* compfile; + compfile = LLFile::fopen(compiled, "w"); +#endif + + if(dst_filename) + { + gScriptp->setBytecodeDest(dst_filename); + } + + gScriptp->mGodLike = is_god_like; + + gScriptp->setClassName(class_name); + + gScopeStringTable = new LLStringTable(16384); +#ifdef EMERGENCY_DEBUG_PRINTOUTS + gScriptp->recurse(compfile, 0, 4, LSCP_PRETTY_PRINT, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#endif + gScriptp->recurse(yyout, 0, 0, LSCP_PRUNE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_SCOPE_PASS1, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_SCOPE_PASS2, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + gScriptp->recurse(yyout, 0, 0, LSCP_TYPE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + if (!gErrorToText.getErrors()) + { + gScriptp->recurse(yyout, 0, 0, LSCP_RESOURCE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#ifdef EMERGENCY_DEBUG_PRINTOUTS + gScriptp->recurse(yyout, 0, 0, LSCP_EMIT_ASSEMBLY, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); +#endif + if(TRUE == compile_to_mono) + { + gScriptp->recurse(yyout, 0, 0, LSCP_EMIT_CIL_ASSEMBLY, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + } + else + { + gScriptp->recurse(yyout, 0, 0, LSCP_EMIT_BYTE_CODE, LSPRUNE_INVALID, b_dummy, NULL, type, type, b_dummy_count, NULL, NULL, 0, NULL, 0, NULL); + } + } + delete gScopeStringTable; + gScopeStringTable = NULL; +#ifdef EMERGENCY_DEBUG_PRINTOUTS + fclose(compfile); +#endif + } + fclose(yyout); + fclose(yyin); + } + + delete gAllocationManager; + delete gScopeStringTable; + + return b_parse_ok && !gErrorToText.getErrors(); +} + + +BOOL lscript_compile(char *filename, BOOL compile_to_mono, BOOL is_god_like = FALSE) +{ + char src_filename[MAX_STRING]; + sprintf(src_filename, "%s.lsl", filename); + char err_filename[MAX_STRING]; + sprintf(err_filename, "%s.out", filename); + char class_name[MAX_STRING]; + sprintf(class_name, "%s", filename); + return lscript_compile(src_filename, NULL, err_filename, compile_to_mono, class_name, is_god_like); +} + + +S32 yywrap() +{ +#if defined(FLEX_SCANNER) && !defined(LL_WINDOWS) + // get gcc to stop complaining about lack of use of yyunput + (void) yyunput; +#endif + return(1); +} + +void line_comment() +{ + char c; + + while ((c = yyinput()) != '\n' && c != 0 && c != EOF) + ; +} + +void block_comment() +{ + char c1 = 0; + char c2 = yyinput(); + while (c2 != 0 && c2 != EOF && !(c1 == '*' && c2 == '/')) { + if (c2 == '\n') + { + gInternalLine++; + gInternalColumn = 0; + } + else if (c2 == '\t') + gInternalColumn += 4 - (gInternalColumn % 8); + else + gInternalColumn++; + c1 = c2; + c2 = yyinput(); + } +} + +void count() +{ + S32 i; + + gColumn = gInternalColumn; + gLine = gInternalLine; + + for (i = 0; yytext[i] != '\0'; i++) + if (yytext[i] == '\n') + { + gInternalLine++; + gInternalColumn = 0; + } + else if (yytext[i] == '\t') + gInternalColumn += 4 - (gInternalColumn % 8); + else + gInternalColumn++; +} + +void parse_string() +{ + S32 length = (S32)strlen(yytext); + length = length - 2; + char *temp = yytext + 1; + + S32 i; + S32 escapes = 0; + S32 tabs = 0; + for (i = 0; i < length; i++) + { + if (temp[i] == '\\') + { + escapes++; + i++; + if (temp[i] == 't') + tabs++; + } + } + + S32 newlength = length - escapes + tabs*3; + yylval.sval = new char[newlength + 1]; + + char *dest = yylval.sval; + + for (i = 0; i < length; i++) + { + if (temp[i] == '\\') + { + i++; + // linefeed + if (temp[i] == 'n') + { + *dest++ = 10; + } + else if (temp[i] == 't') + { + *dest++ = ' '; + *dest++ = ' '; + *dest++ = ' '; + *dest++ = ' '; + } + else + { + *dest++ = temp[i]; + } + } + else + { + *dest++ = temp[i]; + } + } + yylval.sval[newlength] = 0; +} diff --git a/indra/lscript/lscript_compile/indra.y b/indra/lscript/lscript_compile/indra.y new file mode 100755 index 0000000000..a0a034d21c --- /dev/null +++ b/indra/lscript/lscript_compile/indra.y @@ -0,0 +1,1791 @@ +%{ + #include "linden_common.h" + #include "lscript_tree.h" + + int yylex(void); + int yyparse( void ); + int yyerror(const char *fmt, ...); + + #if LL_LINUX + // broken yacc codegen... --ryan. + #define getenv getenv_workaround + #endif + + #ifdef LL_WINDOWS + #pragma warning (disable : 4702) // warning C4702: unreachable code + #pragma warning( disable : 4065 ) // warning: switch statement contains 'default' but no 'case' labels + #endif + +%} + +%union +{ + S32 ival; + F32 fval; + char *sval; + class LLScriptType *type; + class LLScriptConstant *constant; + class LLScriptIdentifier *identifier; + class LLScriptSimpleAssignable *assignable; + class LLScriptGlobalVariable *global; + class LLScriptEvent *event; + class LLScriptEventHandler *handler; + class LLScriptExpression *expression; + class LLScriptStatement *statement; + class LLScriptGlobalFunctions *global_funcs; + class LLScriptFunctionDec *global_decl; + class LLScriptState *state; + class LLScritpGlobalStorage *global_store; + class LLScriptScript *script; +}; + +%token INTEGER +%token FLOAT_TYPE +%token STRING +%token LLKEY +%token VECTOR +%token QUATERNION +%token LIST + +%token STATE +%token EVENT +%token JUMP +%token RETURN + +%token STATE_ENTRY +%token STATE_EXIT +%token TOUCH_START +%token TOUCH +%token TOUCH_END +%token COLLISION_START +%token COLLISION +%token COLLISION_END +%token LAND_COLLISION_START +%token LAND_COLLISION +%token LAND_COLLISION_END +%token TIMER +%token CHAT +%token SENSOR +%token NO_SENSOR +%token CONTROL +%token AT_TARGET +%token NOT_AT_TARGET +%token AT_ROT_TARGET +%token NOT_AT_ROT_TARGET +%token MONEY +%token EMAIL +%token RUN_TIME_PERMISSIONS +%token INVENTORY +%token ATTACH +%token DATASERVER +%token MOVING_START +%token MOVING_END +%token REZ +%token OBJECT_REZ +%token LINK_MESSAGE +%token REMOTE_DATA +%token HTTP_RESPONSE +%token HTTP_REQUEST + +%token IDENTIFIER +%token STATE_DEFAULT + +%token INTEGER_CONSTANT +%token INTEGER_TRUE +%token INTEGER_FALSE + +%token FP_CONSTANT + +%token STRING_CONSTANT + +%token INC_OP +%token DEC_OP +%token ADD_ASSIGN +%token SUB_ASSIGN +%token MUL_ASSIGN +%token DIV_ASSIGN +%token MOD_ASSIGN + +%token EQ +%token NEQ +%token GEQ +%token LEQ + +%token BOOLEAN_AND +%token BOOLEAN_OR + +%token SHIFT_LEFT +%token SHIFT_RIGHT + +%token IF +%token ELSE +%token FOR +%token DO +%token WHILE + +%token PRINT + +%token PERIOD + +%token ZERO_VECTOR +%token ZERO_ROTATION + +%token TOUCH_INVALID_VECTOR +%token TOUCH_INVALID_TEXCOORD + +%nonassoc LOWER_THAN_ELSE +%nonassoc ELSE + + +%type