From f40104f42cf2419c25cc0e0e5289278e7697061b Mon Sep 17 00:00:00 2001 From: mtijanic Date: Tue, 5 Aug 2025 09:14:06 +0200 Subject: [PATCH 1/3] scriptcomp: constant folding is a core language feature, no longer controllable by opt flags --- neverwinter/nwscript/compiler.nim | 5 ++--- neverwinter/nwscript/native/scriptcomp.h | 9 +++------ neverwinter/nwscript/native/scriptcompcore.cpp | 9 +++------ neverwinter/nwscript/native/scriptcompfinalcode.cpp | 6 +++--- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/neverwinter/nwscript/compiler.nim b/neverwinter/nwscript/compiler.nim index af0c607..f540560 100644 --- a/neverwinter/nwscript/compiler.nim +++ b/neverwinter/nwscript/compiler.nim @@ -40,9 +40,8 @@ type # Needs to match scriptcomp.h OptimizationFlag* {.pure.} = enum RemoveDeadCode = 0x1 - FoldConstants = 0x2 - MeldInstructions = 0x4 - RemoveDeadBranches = 0x8 + MeldInstructions = 0x2 + RemoveDeadBranches = 0x4 const OptimizationFlagsO0* = {} diff --git a/neverwinter/nwscript/native/scriptcomp.h b/neverwinter/nwscript/native/scriptcomp.h index d959e5f..a87153a 100644 --- a/neverwinter/nwscript/native/scriptcomp.h +++ b/neverwinter/nwscript/native/scriptcomp.h @@ -51,13 +51,10 @@ class CScriptCompilerIdentifierHashTableEntry; // Removes any functions that cannot possibly be called by any codepath #define CSCRIPTCOMPILER_OPTIMIZE_DEAD_FUNCTIONS 0x00000001 -// Merges constant expressions into a single constant where possible. -// Note: Only affects runtime expressions, assignments to const variables are always folded. -#define CSCRIPTCOMPILER_OPTIMIZE_FOLD_CONSTANTS 0x00000002 // Post processes generated instructions to merge sequences into shorter equivalents -#define CSCRIPTCOMPILER_OPTIMIZE_MELD_INSTRUCTIONS 0x00000004 +#define CSCRIPTCOMPILER_OPTIMIZE_MELD_INSTRUCTIONS 0x00000002 // Removes the jump and the dead branches in if (CONST) constructs -#define CSCRIPTCOMPILER_OPTIMIZE_DEAD_BRANCHES 0x00000008 +#define CSCRIPTCOMPILER_OPTIMIZE_DEAD_BRANCHES 0x00000004 #define CSCRIPTCOMPILER_OPTIMIZE_NOTHING 0x00000000 #define CSCRIPTCOMPILER_OPTIMIZE_EVERYTHING 0xFFFFFFFF @@ -558,7 +555,7 @@ class CScriptCompiler CScriptParseTreeNode *m_pGlobalVariableParseTree; int32_t AddToGlobalVariableList(CScriptParseTreeNode *pGlobalVariableNode); - BOOL ConstantFoldNode(CScriptParseTreeNode *pNode, BOOL bForce=FALSE); + BOOL ConstantFoldNode(CScriptParseTreeNode *pNode); CScriptParseTreeNode *TrimParseTree(CScriptParseTreeNode *pNode); diff --git a/neverwinter/nwscript/native/scriptcompcore.cpp b/neverwinter/nwscript/native/scriptcompcore.cpp index 82462d4..b88f1bc 100644 --- a/neverwinter/nwscript/native/scriptcompcore.cpp +++ b/neverwinter/nwscript/native/scriptcompcore.cpp @@ -1544,11 +1544,8 @@ int32_t CScriptCompiler::OutputError(int32_t nError, CExoString *psFileName, int // Destructively modify a node and all its children to decay it into a single // CONSTANT operation, if possible. // This function is safe to call multiple times on the same node. -BOOL CScriptCompiler::ConstantFoldNode(CScriptParseTreeNode *pNode, BOOL bForce) +BOOL CScriptCompiler::ConstantFoldNode(CScriptParseTreeNode *pNode) { - if (!bForce && !(m_nOptimizationFlags & CSCRIPTCOMPILER_OPTIMIZE_FOLD_CONSTANTS)) - return FALSE; - if (!pNode) return FALSE; @@ -1596,9 +1593,9 @@ BOOL CScriptCompiler::ConstantFoldNode(CScriptParseTreeNode *pNode, BOOL bForce) // In case of complex expression, start folding at the leaf nodes // e.g.: C = 3 + 2*4 - First fold 2*4 into 8, then 3+8 into 11 - ConstantFoldNode(pNode->pLeft, bForce); + ConstantFoldNode(pNode->pLeft); if (pNode->pRight) - ConstantFoldNode(pNode->pRight, bForce); + ConstantFoldNode(pNode->pRight); // Can only fold if the operands are constants. if (pNode->pLeft->nOperation != CSCRIPTCOMPILER_OPERATION_CONSTANT_INTEGER && diff --git a/neverwinter/nwscript/native/scriptcompfinalcode.cpp b/neverwinter/nwscript/native/scriptcompfinalcode.cpp index 156e253..43bd10f 100644 --- a/neverwinter/nwscript/native/scriptcompfinalcode.cpp +++ b/neverwinter/nwscript/native/scriptcompfinalcode.cpp @@ -1003,7 +1003,7 @@ int32_t CScriptCompiler::TraverseTreeForSwitchLabels(CScriptParseTreeNode *pNode { int32_t nCaseValue; - ConstantFoldNode(pNode->pLeft, TRUE); + ConstantFoldNode(pNode->pLeft); // Evaluate the constant value that is contained. if (pNode->pLeft != NULL && pNode->pLeft->nOperation == CSCRIPTCOMPILER_OPERATION_NEGATION && @@ -1250,7 +1250,7 @@ int32_t CScriptCompiler::GenerateIdentifiersFromConstantVariables(CScriptParseTr pNode->pRight->pLeft->pLeft->pLeft != NULL) { CScriptParseTreeNode *pNodeConstant = pNode->pRight->pLeft->pLeft->pLeft; - ConstantFoldNode(pNodeConstant, TRUE); + ConstantFoldNode(pNodeConstant); int32_t nConstantOperation = pNodeConstant->nOperation; int32_t nSign = 1; @@ -1561,7 +1561,7 @@ int32_t CScriptCompiler::PreVisitGenerateCode(CScriptParseTreeNode *pNode) { int32_t nCaseValue; - ConstantFoldNode(pNode->pLeft, TRUE); + ConstantFoldNode(pNode->pLeft); // Evaluate the constant value that is contained. if (pNode->pLeft != NULL && pNode->pLeft->nOperation == CSCRIPTCOMPILER_OPERATION_NEGATION && From e9eeb6afdfae89fa3cbac6fcc6d12621519c4796 Mon Sep 17 00:00:00 2001 From: mtijanic Date: Tue, 5 Aug 2025 09:21:59 +0200 Subject: [PATCH 2/3] scriptcomp: optimization flags rework. Now default to _SAFE, which is just dead code elimination. This is what the game/toolset will use. --- neverwinter/nwscript/compilerapi.cpp | 2 +- neverwinter/nwscript/native/scriptcomp.h | 8 +++++++- neverwinter/nwscript/native/scriptcompcore.cpp | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/neverwinter/nwscript/compilerapi.cpp b/neverwinter/nwscript/compilerapi.cpp index 41eac1e..9351d0f 100644 --- a/neverwinter/nwscript/compilerapi.cpp +++ b/neverwinter/nwscript/compilerapi.cpp @@ -25,7 +25,7 @@ extern "C" void scriptCompApiInitCompiler(CScriptCompiler* instance, const char* { instance->SetGenerateDebuggerOutput(writeDebug); instance->SetOptimizationFlags( - writeDebug ? CSCRIPTCOMPILER_OPTIMIZE_NOTHING : CSCRIPTCOMPILER_OPTIMIZE_EVERYTHING); + writeDebug ? CSCRIPTCOMPILER_OPTIMIZE_NOTHING : CSCRIPTCOMPILER_OPTIMIZE_AGGRESSIVE); instance->SetCompileConditionalOrMain(1); instance->SetIdentifierSpecification(lang); instance->SetOutputAlias(outputAlias); diff --git a/neverwinter/nwscript/native/scriptcomp.h b/neverwinter/nwscript/native/scriptcomp.h index a87153a..c04e3cb 100644 --- a/neverwinter/nwscript/native/scriptcomp.h +++ b/neverwinter/nwscript/native/scriptcomp.h @@ -56,8 +56,14 @@ class CScriptCompilerIdentifierHashTableEntry; // Removes the jump and the dead branches in if (CONST) constructs #define CSCRIPTCOMPILER_OPTIMIZE_DEAD_BRANCHES 0x00000004 +// Optimization groups - roughly corresponding to -O0, -O1, -O2, -O3. Each group includes the previous one. +// Safe - Known good, used by the game and toolset +// Aggressive - Probably good, used by the external compiler +// Experimental - Untested or known to break something #define CSCRIPTCOMPILER_OPTIMIZE_NOTHING 0x00000000 -#define CSCRIPTCOMPILER_OPTIMIZE_EVERYTHING 0xFFFFFFFF +#define CSCRIPTCOMPILER_OPTIMIZE_SAFE (CSCRIPTCOMPILER_OPTIMIZE_DEAD_FUNCTIONS) +#define CSCRIPTCOMPILER_OPTIMIZE_AGGRESSIVE (CSCRIPTCOMPILER_OPTIMIZE_SAFE | CSCRIPTCOMPILER_OPTIMIZE_DEAD_BRANCHES) +#define CSCRIPTCOMPILER_OPTIMIZE_EXPERIMENTAL (CSCRIPTCOMPILER_OPTIMIZE_AGGRESSIVE | CSCRIPTCOMPILER_OPTIMIZE_MELD_INSTRUCTIONS) class CScriptCompilerIncludeFileStackEntry diff --git a/neverwinter/nwscript/native/scriptcompcore.cpp b/neverwinter/nwscript/native/scriptcompcore.cpp index b88f1bc..2594747 100644 --- a/neverwinter/nwscript/native/scriptcompcore.cpp +++ b/neverwinter/nwscript/native/scriptcompcore.cpp @@ -307,7 +307,7 @@ CScriptCompiler::CScriptCompiler(RESTYPE nSource, RESTYPE nCompiled, RESTYPE nDe m_sLanguageSource = ""; m_sOutputAlias = "OVERRIDE"; - m_nOptimizationFlags = CSCRIPTCOMPILER_OPTIMIZE_EVERYTHING; + m_nOptimizationFlags = CSCRIPTCOMPILER_OPTIMIZE_SAFE; m_nIdentifierListState = 0; m_pSRStack = NULL; From 831726c400e9d20f7323bff75f6d680b8b544e81 Mon Sep 17 00:00:00 2001 From: mtijanic Date: Tue, 5 Aug 2025 09:35:27 +0200 Subject: [PATCH 3/3] nwn_script_comp: Make the CLI optimization flags reflect the backend changes. Now defaults to -O1. --- neverwinter/nwscript/compiler.nim | 4 +++- nwn_script_comp.nim | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/neverwinter/nwscript/compiler.nim b/neverwinter/nwscript/compiler.nim index f540560..9afbc4a 100644 --- a/neverwinter/nwscript/compiler.nim +++ b/neverwinter/nwscript/compiler.nim @@ -45,7 +45,9 @@ type const OptimizationFlagsO0* = {} - OptimizationFlagsO2* = fullSet(OptimizationFlag) + OptimizationFlagsO1* = {RemoveDeadCode} + OptimizationFlagsO2* = {RemoveDeadCode, RemoveDeadBranches} + OptimizationFlagsO3* = {RemoveDeadCode, RemoveDeadBranches, MeldInstructions} proc scriptCompApiNewCompiler( src, bin, dbt: cint, diff --git a/nwn_script_comp.nim b/nwn_script_comp.nim index 72860b6..6246b3c 100644 --- a/nwn_script_comp.nim +++ b/nwn_script_comp.nim @@ -4,6 +4,9 @@ import shared import neverwinter/nwscript/compiler import neverwinter/resman +proc fmtOptFlags(flags: set[OptimizationFlag], indentSize: int = 36): string = + indent(join(toSeq(flags).mapIt("+" & $it), "\n"), indentSize) + const ArgsHelp = """ Compile one or more scripts using the official compiler library. @@ -31,10 +34,14 @@ Usage: -y Continue processing input files even on error. -j N Parallel execution (default: all CPUs). - -O N Optimisation levels [default: 2] + -O N Optimisation levels [default: 1] 0: Optimise nothing - 2: Aggressive optimisations; fastest and smallest code size: -""" & indent(join(toSeq(OptimizationFlagsO2).mapIt("+" & $it), "\n"), 36) & """ + 1: Safe optimisations only, as used by the game and toolset +""" & fmtOptFlags(OptimizationFlagsO1, 36) & "\n" & """ + 2: Aggressive optimisations; faster and smaller code size +""" & fmtOptFlags(OptimizationFlagsO2, 36) & "\n" & """ + 3: Experimental optimisations; untested or known to break something +""" & fmtOptFlags(OptimizationFlagsO3, 36) & "\n" & """ --max-include-depth=N Maximum include depth [default: 16] @@ -98,7 +105,9 @@ globalState.params = Params( debugSymbols: globalState.args["-g"].to_bool, optFlags: (case parseInt($globalState.args["-O"]) of 0: OptimizationFlagsO0 + of 1: OptimizationFlagsO1 of 2: OptimizationFlagsO2 + of 3: OptimizationFlagsO3 else: raise newException(ValueError, "No such optimisation flag: " & $globalState.args["-O"]) ), continueOnError: globalState.args["-y"].to_bool,