- added switch/case processing. Right now it is just a sequence of test/jmp instructions. It may make sense to add a special opcode that can perform the comparisons natively but that's an option for later.

- added a TESTN instruction. This is like TEST but negates the operand. This was added to avoid flooding the constant table with too many case labels. With TEST and TESTN combined, all numbers between -65535 and 65535 can be kept entirely inside the instruction. Numbers outside this range still use a BEQ instruction.
This commit is contained in:
Christoph Oelckers 2016-10-23 12:00:25 +02:00
parent 6587828e32
commit f9cd2c9af7
6 changed files with 304 additions and 2 deletions

View file

@ -5606,6 +5606,234 @@ bool FxCompoundStatement::CheckLocalVariable(FName name)
return false; return false;
} }
//==========================================================================
//
// FxSwitchStatement
//
//==========================================================================
FxSwitchStatement::FxSwitchStatement(FxExpression *cond, FArgumentList *content, const FScriptPosition &pos)
: FxExpression(EFX_SwitchStatement, pos)
{
Condition = new FxIntCast(cond, false);
Content = content;
}
FxSwitchStatement::~FxSwitchStatement()
{
SAFE_DELETE(Condition);
SAFE_DELETE(Content);
}
FxExpression *FxSwitchStatement::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(Condition, ctx);
if (Content == nullptr || Content->Size() == 0)
{
ScriptPosition.Message(MSG_WARNING, "Empty switch statement");
if (Condition->isConstant())
{
return new FxNop(ScriptPosition);
}
else
{
// The condition may have a side effect so it should be processed (possible to-do: Analyze all nodes in there and delete if not.)
auto x = Condition;
Condition = nullptr;
delete this;
return Condition;
}
}
for (auto &line : *Content)
{
// Do not resolve breaks, they need special treatment inside switch blocks.
if (line->ExprType != EFX_JumpStatement || static_cast<FxJumpStatement *>(line)->Token != TK_Break)
{
SAFE_RESOLVE(line, ctx);
}
}
if (Condition->isConstant())
{
ScriptPosition.Message(MSG_WARNING, "Case expression is constant");
auto &content = *Content;
unsigned defaultindex = -1;
unsigned defaultbreak = -1;
unsigned caseindex = -1;
unsigned casebreak = -1;
// look for a case label with a matching value
for (unsigned i = 0; i < content.Size(); i++)
{
if (content[i] != nullptr)
{
if (content[i]->ExprType == EFX_CaseStatement)
{
auto casestmt = static_cast<FxCaseStatement *>(content[i]);
if (casestmt->Condition == nullptr) defaultindex = i;
else if (casestmt->CaseValue == static_cast<FxConstant *>(Condition)->GetValue().GetInt()) caseindex = i;
}
if (content[i]->ExprType == EFX_JumpStatement && static_cast<FxJumpStatement *>(content[i])->Token == TK_Break)
{
if (defaultindex >= 0 && defaultbreak < 0) defaultbreak = i;
if (caseindex >= 0 && casebreak < 0)
{
casebreak = i;
break; // when we find this we do not need to look any further.
}
}
}
}
if (caseindex < 0)
{
caseindex = defaultindex;
casebreak = defaultbreak;
}
if (caseindex > 0 && casebreak - caseindex > 1)
{
auto seq = new FxSequence(ScriptPosition);
for (unsigned i = caseindex + 1; i < casebreak; i++)
{
if (content[i] != nullptr && content[i]->ExprType != EFX_CaseStatement)
{
seq->Add(content[i]);
content[i] = nullptr;
}
}
delete this;
return seq->Resolve(ctx);
}
delete this;
return new FxNop(ScriptPosition);
}
int mincase = INT_MAX;
int maxcase = INT_MIN;
for (auto line : *Content)
{
if (line->ExprType == EFX_CaseStatement)
{
auto casestmt = static_cast<FxCaseStatement *>(line);
if (casestmt->Condition != nullptr)
{
CaseAddr ca = { casestmt->CaseValue, 0 };
CaseAddresses.Push(ca);
if (ca.casevalue < mincase) mincase = ca.casevalue;
if (ca.casevalue > maxcase) maxcase = ca.casevalue;
}
}
}
return this;
}
ExpEmit FxSwitchStatement::Emit(VMFunctionBuilder *build)
{
assert(Condition != nullptr);
ExpEmit emit = Condition->Emit(build);
assert(emit.RegType == REGT_INT);
// todo:
// - sort jump table by value.
// - optimize the switch dispatcher to run in native code instead of executing each single branch instruction on its own.
// e.g.: build->Emit(OP_SWITCH, emit.RegNum, build->GetConstantInt(CaseAddresses.Size());
for (auto &ca : CaseAddresses)
{
if (ca.casevalue >= 0 && ca.casevalue <= 0xffff)
{
build->Emit(OP_TEST, emit.RegNum, (VM_SHALF)ca.casevalue);
}
else if (ca.casevalue < 0 && ca.casevalue >= -0xffff)
{
build->Emit(OP_TESTN, emit.RegNum, (VM_SHALF)-ca.casevalue);
}
else
{
build->Emit(OP_EQ_K, 1, emit.RegNum, build->GetConstantInt(ca.casevalue));
}
ca.jumpaddress = build->Emit(OP_JMP, 0);
}
size_t DefaultAddress = build->Emit(OP_JMP, 0);
TArray<size_t> BreakAddresses;
for (auto line : *Content)
{
switch (line->ExprType)
{
case EFX_CaseStatement:
if (static_cast<FxCaseStatement *>(line)->Condition != nullptr)
{
for (auto &ca : CaseAddresses)
{
if (ca.casevalue == static_cast<FxCaseStatement *>(line)->CaseValue)
{
build->BackpatchToHere(ca.jumpaddress);
break;
}
}
}
else
{
build->BackpatchToHere(DefaultAddress);
}
break;
case EFX_JumpStatement:
if (static_cast<FxJumpStatement *>(line)->Token == TK_Break)
{
BreakAddresses.Push(build->Emit(OP_JMP, 0));
break;
}
// fall through for continue.
default:
line->Emit(build);
break;
}
}
for (auto addr : BreakAddresses)
{
build->BackpatchToHere(addr);
}
return ExpEmit();
}
//==========================================================================
//
// FxCaseStatement
//
//==========================================================================
FxCaseStatement::FxCaseStatement(FxExpression *cond, const FScriptPosition &pos)
: FxExpression(EFX_CaseStatement, pos)
{
Condition = cond? new FxIntCast(cond, false) : nullptr;
}
FxCaseStatement::~FxCaseStatement()
{
SAFE_DELETE(Condition);
}
FxExpression *FxCaseStatement::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE_OPT(Condition, ctx);
if (Condition != nullptr)
{
if (!Condition->isConstant())
{
ScriptPosition.Message(MSG_ERROR, "Case label must be a constant value");
delete this;
return nullptr;
}
CaseValue = static_cast<FxConstant *>(Condition)->GetValue().GetInt();
}
return this;
}
//========================================================================== //==========================================================================
// //
// FxIfStatement // FxIfStatement

View file

@ -261,6 +261,8 @@ enum EFxType
EFX_DamageValue, EFX_DamageValue,
EFX_Nop, EFX_Nop,
EFX_LocalVariableDeclaration, EFX_LocalVariableDeclaration,
EFX_SwitchStatement,
EFX_CaseStatement,
EFX_COUNT EFX_COUNT
}; };
@ -1250,6 +1252,51 @@ public:
bool CheckLocalVariable(FName name); bool CheckLocalVariable(FName name);
}; };
//==========================================================================
//
// FxSwitchStatement
//
//==========================================================================
class FxSwitchStatement : public FxExpression
{
FxExpression *Condition;
FArgumentList *Content;
struct CaseAddr
{
int casevalue;
size_t jumpaddress;
};
TArray<CaseAddr> CaseAddresses;
public:
FxSwitchStatement(FxExpression *cond, FArgumentList *content, const FScriptPosition &pos);
~FxSwitchStatement();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxSwitchStatement
//
//==========================================================================
class FxCaseStatement : public FxExpression
{
FxExpression *Condition;
int CaseValue; // copy the value to here for easier access.
friend class FxSwitchStatement;
public:
FxCaseStatement(FxExpression *cond, const FScriptPosition &pos);
~FxCaseStatement();
FxExpression *Resolve(FCompileContext&);
};
//========================================================================== //==========================================================================
// //
// FxIfStatement // FxIfStatement

View file

@ -372,6 +372,13 @@ begin:
pc++; pc++;
} }
NEXTOP; NEXTOP;
OP(TESTN):
ASSERTD(a);
if (-reg.d[a] != BC)
{
pc++;
}
NEXTOP;
OP(JMP): OP(JMP):
pc += JMPOFS(pc); pc += JMPOFS(pc);
NEXTOP; NEXTOP;

View file

@ -69,6 +69,7 @@ xx(DYNCAST_K, dyncast,RPRPKP),
// Control flow. // Control flow.
xx(TEST, test, RII16), // if (dA != BC) then pc++ xx(TEST, test, RII16), // if (dA != BC) then pc++
xx(TESTN, testn, RII16), // if (dA != -BC) then pc++
xx(JMP, jmp, I24), // pc += ABC -- The ABC fields contain a signed 24-bit offset. xx(JMP, jmp, I24), // pc += ABC -- The ABC fields contain a signed 24-bit offset.
xx(IJMP, ijmp, RII16), // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP. xx(IJMP, ijmp, RII16), // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP.
xx(PARAM, param, __BCP), // push parameter encoded in BC for function call (B=regtype, C=regnum) xx(PARAM, param, __BCP), // push parameter encoded in BC for function call (B=regtype, C=regnum)

View file

@ -2677,9 +2677,26 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast)
// not yet done // not yet done
case AST_SwitchStmt: case AST_SwitchStmt:
case AST_CaseStmt: {
break; auto swtch = static_cast<ZCC_SwitchStmt *>(ast);
if (swtch->Content->NodeType != AST_CompoundStmt)
{
Error(ast, "Expecting { after 'switch'");
return new FxNop(*ast); // allow compiler to continue looking for errors.
}
else
{
// The switch content is wrapped into a compound statement which needs to be unraveled here.
auto cmpnd = static_cast<ZCC_CompoundStmt *>(swtch->Content);
return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(cmpnd->Content), *ast);
}
}
case AST_CaseStmt:
{
auto cases = static_cast<ZCC_CaseStmt *>(ast);
return new FxCaseStatement(ConvertNode(cases->Condition), *ast);
}
case AST_CompoundStmt: case AST_CompoundStmt:
{ {

View file

@ -177,3 +177,5 @@ zscript/chex/chexweapons.txt
zscript/chex/chexitems.txt zscript/chex/chexitems.txt
zscript/chex/chexdecorations.txt zscript/chex/chexdecorations.txt
zscript/chex/chexplayer.txt zscript/chex/chexplayer.txt
zscript/test2.txt