Skip to content

Commit

Permalink
scriptcomp: Optimize constant->variable assignment (#80)
Browse files Browse the repository at this point in the history
The nwscript construct `int n = 3;` gets compiled into the following:
```
    RUNSTACK_ADD, TYPE_INTEGER
    CONSTANT, TYPE_INTEGER, 3
    ASSIGNMENT, TYPE_VOID, -8, 4
    MODIFY_STACK_POINTER, -4
```
This ends up with just the CONSTI 3 on the top of stack, but the
dance is necessary as `n = 3` is also an expression which returns 3.
Then, the last MODSP discards the result of the expression.
Here, we detect the pattern when it is discarded, and replace the
whole set of instructions with just CONSTI 3

Partial fix for #70 

## Testing

Added variables.nss test. Prior to this change, it got compiled into
this mess (without asserts):
```
     0  JSR, 8                                  
     6  RET                                     
     8  RUNSTACK_ADD, TYPE_INTEGER              
    10  CONSTANT, TYPE_INTEGER, 1               
    16  ASSIGNMENT, TYPE_VOID, -8, 4            
    24  MODIFY_STACK_POINTER, -4                
    30  RUNSTACK_ADD, TYPE_INTEGER              
    32  CONSTANT, TYPE_INTEGER, 2               
    38  ASSIGNMENT, TYPE_VOID, -8, 4            
    46  MODIFY_STACK_POINTER, -4                
    52  RUNSTACK_ADD, TYPE_INTEGER              
    54  CONSTANT, TYPE_INTEGER, 3               
    60  ASSIGNMENT, TYPE_VOID, -8, 4            
    68  MODIFY_STACK_POINTER, -4                
    74  RUNSTACK_ADD, TYPE_INTEGER              
    76  CONSTANT, TYPE_INTEGER, 4               
    82  ASSIGNMENT, TYPE_VOID, -8, 4            
    90  MODIFY_STACK_POINTER, -4                
    96  RUNSTACK_ADD, TYPE_STRING               
    98  CONSTANT, TYPE_STRING, "A"              
   103  ASSIGNMENT, TYPE_VOID, -8, 4            
   111  MODIFY_STACK_POINTER, -4                
   117  RUNSTACK_ADD, TYPE_FLOAT                
   119  CONSTANT, TYPE_FLOAT, 1.0               
   125  ASSIGNMENT, TYPE_VOID, -8, 4            
   133  MODIFY_STACK_POINTER, -4                
   139  CONSTANT, TYPE_INTEGER, 1               
   145  JZ, 143                                 
   151  RUNSTACK_ADD, TYPE_INTEGER              
   153  CONSTANT, TYPE_INTEGER, 10              
   159  ASSIGNMENT, TYPE_VOID, -8, 4            
   167  MODIFY_STACK_POINTER, -4                
   173  RUNSTACK_ADD, TYPE_INTEGER              
   175  CONSTANT, TYPE_INTEGER, 20              
   181  ASSIGNMENT, TYPE_VOID, -8, 4            
   189  MODIFY_STACK_POINTER, -4                
   195  RUNSTACK_ADD, TYPE_INTEGER              
   197  CONSTANT, TYPE_INTEGER, 30              
   203  ASSIGNMENT, TYPE_VOID, -8, 4            
   211  MODIFY_STACK_POINTER, -4                
   217  RUNSTACK_ADD, TYPE_INTEGER              
   219  CONSTANT, TYPE_INTEGER, 40              
   225  ASSIGNMENT, TYPE_VOID, -8, 4            
   233  MODIFY_STACK_POINTER, -4                
   239  RUNSTACK_ADD, TYPE_STRING               
   241  CONSTANT, TYPE_STRING, "B"              
   246  ASSIGNMENT, TYPE_VOID, -8, 4            
   254  MODIFY_STACK_POINTER, -4                
   260  RUNSTACK_ADD, TYPE_FLOAT                
   262  CONSTANT, TYPE_FLOAT, 10.0              
   268  ASSIGNMENT, TYPE_VOID, -8, 4            
   276  MODIFY_STACK_POINTER, -28               
   282  JMP, 6                                  
   288  MODIFY_STACK_POINTER, -24               
   294  RET                                     
```
after the change, it becomes:
```
     0  JSR, 8                                  
     6  RET                                     
     8  CONSTANT, TYPE_INTEGER, 1               
    14  CONSTANT, TYPE_INTEGER, 2               
    20  CONSTANT, TYPE_INTEGER, 3               
    26  CONSTANT, TYPE_INTEGER, 4               
    32  CONSTANT, TYPE_STRING, "A"              
    37  CONSTANT, TYPE_FLOAT, 1.0               
    43  CONSTANT, TYPE_INTEGER, 1               
    49  JZ, 53                                  
    55  CONSTANT, TYPE_INTEGER, 10              
    61  CONSTANT, TYPE_INTEGER, 20              
    67  CONSTANT, TYPE_INTEGER, 30              
    73  CONSTANT, TYPE_INTEGER, 40              
    79  CONSTANT, TYPE_STRING, "B"              
    84  CONSTANT, TYPE_FLOAT, 10.0              
    90  MODIFY_STACK_POINTER, -24               
    96  JMP, 6                                  
   102  MODIFY_STACK_POINTER, -24               
   108  RET                            
```

## Changelog

### Performance Improvements
- nwscript compiler will now generate more optimal code for assigning
values to variables

## Licence

- [x] I am licencing my change under the project's MIT licence,
including all changes to GPL-3.0 licenced parts of the codebase.
  • Loading branch information
mtijanic authored Jul 16, 2023
1 parent 85fc548 commit 07a13f1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
2 changes: 2 additions & 0 deletions neverwinter/nwscript/native/scriptcomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@ class CScriptCompiler
int32_t m_nOutputCodeLength;
std::vector<int32_t> m_aOutputCodeInstructionBoundaries;

char *InstructionLookback(uint32_t last=1);

// Resolving code to its proper location ... some buffers!
char *m_pchResolvedOutputBuffer;
int32_t m_nResolvedOutputBufferSize;
Expand Down
51 changes: 47 additions & 4 deletions neverwinter/nwscript/native/scriptcompfinalcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6966,6 +6966,13 @@ int32_t CScriptCompiler::WriteDebuggerOutputToFile(CExoString sFileName)
return 0;
}

char *CScriptCompiler::InstructionLookback(uint32_t last)
{
if (last == 0 || last > m_aOutputCodeInstructionBoundaries.size())
return NULL;

return &m_pchOutputCode[m_aOutputCodeInstructionBoundaries[m_aOutputCodeInstructionBoundaries.size() - 1 - last]];
}

void CScriptCompiler::WriteByteSwap32(char *buffer, int32_t value)
{
Expand Down Expand Up @@ -6996,14 +7003,50 @@ void CScriptCompiler::EmitModifyStackPointer(int32_t nModifyBy)
{
if (m_bOptimizeBinarySpace) // TODO: Rename or add separate toggle?
{
int32_t last = m_aOutputCodeInstructionBoundaries[m_aOutputCodeInstructionBoundaries.size()-2];
if (m_pchOutputCode[last + CVIRTUALMACHINE_OPCODE_LOCATION] == CVIRTUALMACHINE_OPCODE_MODIFY_STACK_POINTER)
char *last = InstructionLookback(1);
// Multiple MODIFY_STACK_POINTER instructions can always be merged into a single one
if (last[CVIRTUALMACHINE_OPCODE_LOCATION] == CVIRTUALMACHINE_OPCODE_MODIFY_STACK_POINTER)
{
int32_t mod = ReadByteSwap32(&m_pchOutputCode[last + CVIRTUALMACHINE_EXTRA_DATA_LOCATION]);
int32_t mod = ReadByteSwap32(&last[CVIRTUALMACHINE_EXTRA_DATA_LOCATION]);
mod += nModifyBy;
WriteByteSwap32(&m_pchOutputCode[last + CVIRTUALMACHINE_EXTRA_DATA_LOCATION], mod);
WriteByteSwap32(&last[CVIRTUALMACHINE_EXTRA_DATA_LOCATION], mod);
return;
}

// The nwscript construct `int n = 3;` gets compiled into the following:
// RUNSTACK_ADD, TYPE_INTEGER
// CONSTANT, TYPE_INTEGER, 3
// ASSIGNMENT, TYPE_VOID, -8, 4
// MODIFY_STACK_POINTER, -4
// This ends up with just the CONSTI 3 on the top of stack, but the
// dance is necessary as `n = 3` is also an expression which returns 3.
// Then, the last MODSP discards the result of the expression.
// Here, we detect the pattern when it is discarded, and replace the
// whole set of instructions with just CONSTI 3
if (last[CVIRTUALMACHINE_OPCODE_LOCATION] == CVIRTUALMACHINE_OPCODE_ASSIGNMENT)
{
char *instConstant = InstructionLookback(2);
char *instRunstackAdd = InstructionLookback(3);

if (instConstant && instConstant[CVIRTUALMACHINE_OPCODE_LOCATION] == CVIRTUALMACHINE_OPCODE_CONSTANT &&
instRunstackAdd && instRunstackAdd[CVIRTUALMACHINE_OPCODE_LOCATION] == CVIRTUALMACHINE_OPCODE_RUNSTACK_ADD &&
instConstant[CVIRTUALMACHINE_AUXCODE_LOCATION] == instRunstackAdd[CVIRTUALMACHINE_AUXCODE_LOCATION] &&
ReadByteSwap32(&last[CVIRTUALMACHINE_EXTRA_DATA_LOCATION]) == -8
)
{
// Move the CONST instruction up to where runstack add is..
const int32_t instConstantSize = (last - instConstant);
memmove(instRunstackAdd, instConstant, instConstantSize);
// Roll back output code length to where the old instConstant started..
m_nOutputCodeLength = (instRunstackAdd - m_pchOutputCode) + instConstantSize;
// Pop the last two instruction boundaries (CONSTANT, ASSIGNMENT) since those don't exist anymore
m_aOutputCodeInstructionBoundaries.pop_back();
m_aOutputCodeInstructionBoundaries.pop_back();
m_aOutputCodeInstructionBoundaries.pop_back();
m_aOutputCodeInstructionBoundaries.push_back(m_nOutputCodeLength);
return;
}
}
}

char *buf = EmitInstruction(CVIRTUALMACHINE_OPCODE_MODIFY_STACK_POINTER, 0, 4);
Expand Down
33 changes: 33 additions & 0 deletions tests/scriptcomp/corpus/variables.nss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
void main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
string s = "A";
float f = 1.0f;

if (1)
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
string s = "B";
float f = 10.0f;

Assert(a == 10);
Assert(b == 20);
Assert(c == 30);
Assert(d == 40);
Assert(s == "B");
Assert(f == 10.0f);
}

Assert(a == 1);
Assert(b == 2);
Assert(c == 3);
Assert(d == 4);
Assert(s == "A");
Assert(f == 1.0f);
}

0 comments on commit 07a13f1

Please sign in to comment.