2016-04-29 09:30:10 +00:00
|
|
|
/*
|
|
|
|
* OpenClonk, http://www.openclonk.org
|
|
|
|
*
|
|
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
|
|
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
|
|
|
|
*
|
|
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
|
|
* "COPYING" for details.
|
|
|
|
*
|
|
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
|
|
* See accompanying file "TRADEMARK" for details.
|
|
|
|
*
|
|
|
|
* To redistribute this file separately, substitute the full license texts
|
|
|
|
* for the above references.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "C4Include.h"
|
|
|
|
#include "script/C4AulCompiler.h"
|
|
|
|
|
|
|
|
#include "script/C4Aul.h"
|
|
|
|
#include "script/C4AulScriptFunc.h"
|
|
|
|
|
2016-04-29 10:02:37 +00:00
|
|
|
static int GetStackValue(C4AulBCCType eType, intptr_t X)
|
2016-04-29 09:30:10 +00:00
|
|
|
{
|
|
|
|
switch (eType)
|
|
|
|
{
|
|
|
|
case AB_INT:
|
|
|
|
case AB_BOOL:
|
|
|
|
case AB_STRING:
|
|
|
|
case AB_CPROPLIST:
|
|
|
|
case AB_CARRAY:
|
|
|
|
case AB_CFUNCTION:
|
|
|
|
case AB_NIL:
|
|
|
|
case AB_LOCALN:
|
|
|
|
case AB_GLOBALN:
|
|
|
|
case AB_DUP:
|
|
|
|
case AB_DUP_CONTEXT:
|
|
|
|
case AB_THIS:
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
case AB_Pow:
|
|
|
|
case AB_Div:
|
|
|
|
case AB_Mul:
|
|
|
|
case AB_Mod:
|
|
|
|
case AB_Sub:
|
|
|
|
case AB_Sum:
|
|
|
|
case AB_LeftShift:
|
|
|
|
case AB_RightShift:
|
|
|
|
case AB_LessThan:
|
|
|
|
case AB_LessThanEqual:
|
|
|
|
case AB_GreaterThan:
|
|
|
|
case AB_GreaterThanEqual:
|
|
|
|
case AB_Equal:
|
|
|
|
case AB_NotEqual:
|
|
|
|
case AB_BitAnd:
|
|
|
|
case AB_BitXOr:
|
|
|
|
case AB_BitOr:
|
|
|
|
case AB_PROP_SET:
|
|
|
|
case AB_ARRAYA:
|
|
|
|
case AB_CONDN:
|
|
|
|
case AB_COND:
|
|
|
|
case AB_POP_TO:
|
|
|
|
case AB_RETURN:
|
|
|
|
// JUMPAND/JUMPOR/JUMPNNIL are special: They either jump over instructions adding one to the stack
|
|
|
|
// or decrement the stack. Thus, for stack counting purposes, they decrement.
|
|
|
|
case AB_JUMPAND:
|
|
|
|
case AB_JUMPOR:
|
|
|
|
case AB_JUMPNNIL:
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case AB_FUNC:
|
|
|
|
return -reinterpret_cast<C4AulFunc *>(X)->GetParCount() + 1;
|
|
|
|
|
|
|
|
case AB_CALL:
|
|
|
|
case AB_CALLFS:
|
|
|
|
return -C4AUL_MAX_Par;
|
|
|
|
|
|
|
|
case AB_STACK_SET:
|
|
|
|
case AB_LOCALN_SET:
|
|
|
|
case AB_PROP:
|
|
|
|
case AB_GLOBALN_SET:
|
|
|
|
case AB_Inc:
|
|
|
|
case AB_Dec:
|
|
|
|
case AB_BitNot:
|
|
|
|
case AB_Not:
|
|
|
|
case AB_Neg:
|
|
|
|
case AB_PAR:
|
|
|
|
case AB_FOREACH_NEXT:
|
|
|
|
case AB_ERR:
|
|
|
|
case AB_EOFN:
|
|
|
|
case AB_JUMP:
|
|
|
|
case AB_DEBUG:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case AB_STACK:
|
|
|
|
return X;
|
|
|
|
|
|
|
|
case AB_NEW_ARRAY:
|
|
|
|
return -X + 1;
|
|
|
|
|
|
|
|
case AB_NEW_PROPLIST:
|
|
|
|
return -X * 2 + 1;
|
|
|
|
|
|
|
|
case AB_ARRAYA_SET:
|
|
|
|
case AB_ARRAY_SLICE:
|
|
|
|
return -2;
|
|
|
|
|
|
|
|
case AB_ARRAY_SLICE_SET:
|
|
|
|
return -3;
|
|
|
|
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int C4AulCompiler::AddBCC(const char * TokenSPos, C4AulBCCType eType, intptr_t X)
|
|
|
|
{
|
|
|
|
// Track stack size
|
|
|
|
iStack += GetStackValue(eType, X);
|
|
|
|
|
|
|
|
// Use stack operation instead of 0-Any (enable optimization)
|
|
|
|
if (eType == AB_NIL)
|
|
|
|
{
|
|
|
|
eType = AB_STACK;
|
|
|
|
X = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join checks only if it's not a jump target
|
|
|
|
if (!fJump && Fn->GetLastCode())
|
|
|
|
{
|
|
|
|
C4AulBCC *pCPos1 = Fn->GetLastCode();
|
|
|
|
|
|
|
|
// Skip noop stack operation
|
|
|
|
if (eType == AB_STACK && X == 0)
|
|
|
|
{
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join together stack operations
|
|
|
|
if (eType == AB_STACK && pCPos1->bccType == AB_STACK &&
|
|
|
|
(X <= 0 || pCPos1->Par.i >= 0))
|
|
|
|
{
|
|
|
|
pCPos1->Par.i += X;
|
|
|
|
// Empty? Remove it. This relies on the parser not issuing
|
|
|
|
// multiple negative stack operations consecutively, as
|
|
|
|
// that could result in removing a jump target bytecode.
|
|
|
|
if (!pCPos1->Par.i)
|
|
|
|
Fn->RemoveLastBCC();
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prune unneeded Incs / Decs
|
|
|
|
if (eType == AB_STACK && X < 0 && (pCPos1->bccType == AB_Inc || pCPos1->bccType == AB_Dec))
|
|
|
|
{
|
|
|
|
if (!pCPos1->Par.X)
|
|
|
|
{
|
|
|
|
pCPos1->bccType = eType;
|
|
|
|
pCPos1->Par.i = X;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If it was a result modifier, we can safely remove it knowing that it was neither
|
|
|
|
// the first chunk nor a jump target. We can therefore apply additional optimizations.
|
|
|
|
Fn->RemoveLastBCC();
|
|
|
|
pCPos1--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join STACK_SET + STACK -1 to POP_TO (equivalent)
|
|
|
|
if (eType == AB_STACK && X == -1 && pCPos1->bccType == AB_STACK_SET)
|
|
|
|
{
|
|
|
|
pCPos1->bccType = AB_POP_TO;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join POP_TO + DUP to AB_STACK_SET if both target the same slot
|
|
|
|
if (eType == AB_DUP && pCPos1->bccType == AB_POP_TO && X == pCPos1->Par.i + 1)
|
|
|
|
{
|
|
|
|
pCPos1->bccType = AB_STACK_SET;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reduce some constructs like SUM + INT 1 to INC or DEC
|
|
|
|
if ((eType == AB_Sum || eType == AB_Sub) &&
|
|
|
|
pCPos1->bccType == AB_INT &&
|
|
|
|
(pCPos1->Par.i == 1 || pCPos1->Par.i == -1))
|
|
|
|
{
|
|
|
|
if ((pCPos1->Par.i > 0) == (eType == AB_Sum))
|
|
|
|
pCPos1->bccType = AB_Inc;
|
|
|
|
else
|
|
|
|
pCPos1->bccType = AB_Dec;
|
|
|
|
pCPos1->Par.i = X;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reduce Not + CONDN to COND, Not + COND to CONDN
|
|
|
|
if ((eType == AB_CONDN || eType == AB_COND) && pCPos1->bccType == AB_Not)
|
|
|
|
{
|
|
|
|
pCPos1->bccType = eType == AB_CONDN ? AB_COND : AB_CONDN;
|
|
|
|
pCPos1->Par.i = X + 1;
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add
|
|
|
|
Fn->AddBCC(eType, X, TokenSPos);
|
|
|
|
|
|
|
|
// Reset jump flag
|
|
|
|
fJump = false;
|
|
|
|
|
|
|
|
return Fn->GetCodePos() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::RemoveLastBCC()
|
|
|
|
{
|
|
|
|
// Security: This is unsafe on anything that might get optimized away
|
|
|
|
C4AulBCC *pBCC = Fn->GetLastCode();
|
|
|
|
assert(pBCC->bccType != AB_STACK && pBCC->bccType != AB_STACK_SET && pBCC->bccType != AB_POP_TO);
|
|
|
|
// Correct stack
|
|
|
|
iStack -= GetStackValue(pBCC->bccType, pBCC->Par.X);
|
|
|
|
// Remove
|
|
|
|
Fn->RemoveLastBCC();
|
|
|
|
}
|
|
|
|
|
|
|
|
C4V_Type C4AulCompiler::GetLastRetType(C4AulScriptEngine * Engine, C4V_Type to)
|
|
|
|
{
|
|
|
|
C4V_Type from;
|
|
|
|
switch (Fn->GetLastCode()->bccType)
|
|
|
|
{
|
|
|
|
case AB_INT: from = Config.Developer.ExtraWarnings || Fn->GetLastCode()->Par.i ? C4V_Int : C4V_Any; break;
|
|
|
|
case AB_STRING: from = C4V_String; break;
|
|
|
|
case AB_NEW_ARRAY: case AB_CARRAY: case AB_ARRAY_SLICE: from = C4V_Array; break;
|
|
|
|
case AB_CFUNCTION: from = C4V_Function; break;
|
|
|
|
case AB_NEW_PROPLIST: case AB_CPROPLIST: from = C4V_PropList; break;
|
|
|
|
case AB_BOOL: from = C4V_Bool; break;
|
|
|
|
case AB_FUNC:
|
|
|
|
from = Fn->GetLastCode()->Par.f->GetRetType(); break;
|
|
|
|
case AB_CALL: case AB_CALLFS:
|
|
|
|
{
|
|
|
|
C4String * pName = Fn->GetLastCode()->Par.s;
|
|
|
|
C4AulFunc * pFunc2 = Engine->GetFirstFunc(pName->GetCStr());
|
|
|
|
bool allwarn = true;
|
|
|
|
from = C4V_Any;
|
|
|
|
while (pFunc2 && allwarn)
|
|
|
|
{
|
|
|
|
from = pFunc2->GetRetType();
|
|
|
|
if (!C4Value::WarnAboutConversion(from, to))
|
|
|
|
{
|
|
|
|
allwarn = false;
|
|
|
|
from = C4V_Any;
|
|
|
|
}
|
|
|
|
pFunc2 = Engine->GetNextSNFunc(pFunc2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AB_Inc: case AB_Dec: case AB_BitNot: case AB_Neg:
|
|
|
|
case AB_Pow: case AB_Div: case AB_Mul: case AB_Mod: case AB_Sub: case AB_Sum:
|
|
|
|
case AB_LeftShift: case AB_RightShift: case AB_BitAnd: case AB_BitXOr: case AB_BitOr:
|
|
|
|
from = C4V_Int; break;
|
|
|
|
case AB_Not: case AB_LessThan: case AB_LessThanEqual: case AB_GreaterThan: case AB_GreaterThanEqual:
|
|
|
|
case AB_Equal: case AB_NotEqual:
|
|
|
|
from = C4V_Bool; break;
|
|
|
|
case AB_DUP:
|
|
|
|
{
|
|
|
|
int pos = Fn->GetLastCode()->Par.i + iStack - 2 + Fn->VarNamed.iSize + Fn->GetParCount();
|
|
|
|
if (pos < Fn->GetParCount())
|
|
|
|
from = Fn->GetParType()[pos];
|
|
|
|
else
|
|
|
|
from = C4V_Any;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
from = C4V_Any; break;
|
|
|
|
}
|
|
|
|
return from;
|
|
|
|
}
|
|
|
|
|
|
|
|
C4AulBCC C4AulCompiler::MakeSetter(const char * SPos, bool fLeaveValue)
|
|
|
|
{
|
|
|
|
C4AulBCC Value = *(Fn->GetLastCode()), Setter = Value;
|
|
|
|
// Check type
|
|
|
|
switch (Value.bccType)
|
|
|
|
{
|
|
|
|
case AB_ARRAYA: Setter.bccType = AB_ARRAYA_SET; break;
|
|
|
|
case AB_ARRAY_SLICE: Setter.bccType = AB_ARRAY_SLICE_SET; break;
|
|
|
|
case AB_DUP:
|
|
|
|
Setter.bccType = AB_STACK_SET;
|
|
|
|
// the setter additionally has the new value on the stack
|
|
|
|
--Setter.Par.i;
|
|
|
|
break;
|
|
|
|
case AB_STACK_SET: Setter.bccType = AB_STACK_SET; break;
|
|
|
|
case AB_LOCALN:
|
|
|
|
Setter.bccType = AB_LOCALN_SET;
|
|
|
|
break;
|
|
|
|
case AB_PROP:
|
|
|
|
Setter.bccType = AB_PROP_SET;
|
|
|
|
break;
|
|
|
|
case AB_GLOBALN: Setter.bccType = AB_GLOBALN_SET; break;
|
|
|
|
default:
|
|
|
|
throw C4AulParseError(Fn, SPos, "assignment to a constant");
|
|
|
|
}
|
|
|
|
// If the new value is produced using the old one, the parameters to get the old one need to be duplicated.
|
|
|
|
// Otherwise, the setter can just use the parameters originally meant for the getter.
|
|
|
|
// All getters push one value, so the parameter count is one more than the values they pop from the stack.
|
|
|
|
int iParCount = 1 - GetStackValue(Value.bccType, Value.Par.X);
|
|
|
|
if (Value.bccType == AB_STACK_SET)
|
|
|
|
{
|
|
|
|
// STACK_SET has a side effect, so it can't be simply removed.
|
|
|
|
// Discard the unused value the usual way instead.
|
|
|
|
if (!fLeaveValue)
|
|
|
|
AddBCC(SPos, AB_STACK, -1);
|
|
|
|
// The original parameter isn't needed anymore, since in contrast to the other getters
|
|
|
|
// it does not indicate a position.
|
|
|
|
iParCount = 0;
|
|
|
|
}
|
|
|
|
else if (!fLeaveValue || iParCount)
|
|
|
|
{
|
|
|
|
RemoveLastBCC();
|
|
|
|
fJump = true; // In case the original BCC was a jump target
|
|
|
|
}
|
|
|
|
if (fLeaveValue && iParCount)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < iParCount; i++)
|
|
|
|
AddBCC(SPos, AB_DUP, 1 - iParCount);
|
|
|
|
// Finally re-add original BCC
|
|
|
|
AddBCC(SPos, Value.bccType, Value.Par.X);
|
|
|
|
}
|
|
|
|
// Done. The returned BCC should be added later once the value to be set was pushed on top.
|
|
|
|
assert(iParCount == -GetStackValue(Setter.bccType, Setter.Par.X));
|
|
|
|
return Setter;
|
|
|
|
}
|
|
|
|
|
|
|
|
int C4AulCompiler::JumpHere()
|
|
|
|
{
|
|
|
|
// Set flag so the next generated code chunk won't get joined
|
|
|
|
fJump = true;
|
|
|
|
return Fn->GetCodePos();
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool IsJump(C4AulBCCType t)
|
|
|
|
{
|
|
|
|
return t == AB_JUMP || t == AB_JUMPAND || t == AB_JUMPOR || t == AB_JUMPNNIL || t == AB_CONDN || t == AB_COND;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::SetJumpHere(int iJumpOp)
|
|
|
|
{
|
|
|
|
// Set target
|
|
|
|
C4AulBCC *pBCC = Fn->GetCodeByPos(iJumpOp);
|
|
|
|
assert(IsJump(pBCC->bccType));
|
|
|
|
pBCC->Par.i = Fn->GetCodePos() - iJumpOp;
|
|
|
|
// Set flag so the next generated code chunk won't get joined
|
|
|
|
fJump = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::SetJump(int iJumpOp, int iWhere)
|
|
|
|
{
|
|
|
|
// Set target
|
|
|
|
C4AulBCC *pBCC = Fn->GetCodeByPos(iJumpOp);
|
|
|
|
assert(IsJump(pBCC->bccType));
|
|
|
|
pBCC->Par.i = iWhere - iJumpOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::AddJump(const char * SPos, C4AulBCCType eType, int iWhere)
|
|
|
|
{
|
|
|
|
AddBCC(SPos, eType, iWhere - Fn->GetCodePos());
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::PushLoop()
|
|
|
|
{
|
|
|
|
Loop *pNew = new Loop();
|
|
|
|
pNew->StackSize = iStack;
|
|
|
|
pNew->Controls = NULL;
|
|
|
|
pNew->Next = pLoopStack;
|
|
|
|
pLoopStack = pNew;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::PopLoop(int ContinueJump)
|
|
|
|
{
|
|
|
|
// Set targets for break/continue
|
|
|
|
for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next)
|
|
|
|
if (pCtrl->Break)
|
|
|
|
SetJumpHere(pCtrl->Pos);
|
|
|
|
else
|
|
|
|
SetJump(pCtrl->Pos, ContinueJump);
|
|
|
|
// Delete loop controls
|
|
|
|
Loop *pLoop = pLoopStack;
|
|
|
|
while (pLoop->Controls)
|
|
|
|
{
|
|
|
|
// Unlink
|
|
|
|
Loop::Control *pCtrl = pLoop->Controls;
|
|
|
|
pLoop->Controls = pCtrl->Next;
|
|
|
|
// Delete
|
|
|
|
delete pCtrl;
|
|
|
|
}
|
|
|
|
// Unlink & delete
|
|
|
|
pLoopStack = pLoop->Next;
|
|
|
|
delete pLoop;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::AddLoopControl(const char * SPos, bool fBreak)
|
|
|
|
{
|
|
|
|
// Insert code
|
|
|
|
if (pLoopStack->StackSize != iStack)
|
|
|
|
AddBCC(SPos, AB_STACK, pLoopStack->StackSize - iStack);
|
|
|
|
Loop::Control *pNew = new Loop::Control();
|
|
|
|
pNew->Break = fBreak;
|
|
|
|
pNew->Pos = Fn->GetCodePos();
|
|
|
|
pNew->Next = pLoopStack->Controls;
|
|
|
|
pLoopStack->Controls = pNew;
|
|
|
|
AddBCC(SPos, AB_JUMP);
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4AulCompiler::ErrorOut(const char * SPos, C4AulError & e)
|
|
|
|
{
|
|
|
|
// make all jumps that don't have their destination yet jump here
|
|
|
|
for (unsigned int i = 0; i < Fn->Code.size(); i++)
|
|
|
|
{
|
|
|
|
C4AulBCC *pBCC = &Fn->Code[i];
|
|
|
|
if (IsJump(pBCC->bccType))
|
|
|
|
if (!pBCC->Par.i)
|
|
|
|
pBCC->Par.i = Fn->Code.size() - i;
|
|
|
|
}
|
|
|
|
// add an error chunk
|
|
|
|
const char * msg = e.what();
|
|
|
|
if (SEqual2(msg, "ERROR: ")) msg += 7;
|
|
|
|
AddBCC(SPos, AB_ERR, reinterpret_cast<intptr_t>(::Strings.RegString(msg)));
|
|
|
|
}
|