mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-12-11 21:21:45 +00:00
- 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:
parent
6587828e32
commit
f9cd2c9af7
6 changed files with 304 additions and 2 deletions
|
@ -5606,6 +5606,234 @@ bool FxCompoundStatement::CheckLocalVariable(FName name)
|
|||
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
|
||||
|
|
|
@ -261,6 +261,8 @@ enum EFxType
|
|||
EFX_DamageValue,
|
||||
EFX_Nop,
|
||||
EFX_LocalVariableDeclaration,
|
||||
EFX_SwitchStatement,
|
||||
EFX_CaseStatement,
|
||||
EFX_COUNT
|
||||
};
|
||||
|
||||
|
@ -1250,6 +1252,51 @@ public:
|
|||
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
|
||||
|
|
|
@ -372,6 +372,13 @@ begin:
|
|||
pc++;
|
||||
}
|
||||
NEXTOP;
|
||||
OP(TESTN):
|
||||
ASSERTD(a);
|
||||
if (-reg.d[a] != BC)
|
||||
{
|
||||
pc++;
|
||||
}
|
||||
NEXTOP;
|
||||
OP(JMP):
|
||||
pc += JMPOFS(pc);
|
||||
NEXTOP;
|
||||
|
|
|
@ -69,6 +69,7 @@ xx(DYNCAST_K, dyncast,RPRPKP),
|
|||
|
||||
// Control flow.
|
||||
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(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)
|
||||
|
|
|
@ -2677,9 +2677,26 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast)
|
|||
|
||||
// not yet done
|
||||
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:
|
||||
{
|
||||
|
|
|
@ -177,3 +177,5 @@ zscript/chex/chexweapons.txt
|
|||
zscript/chex/chexitems.txt
|
||||
zscript/chex/chexdecorations.txt
|
||||
zscript/chex/chexplayer.txt
|
||||
|
||||
zscript/test2.txt
|
||||
|
|
Loading…
Reference in a new issue