From f9cd2c9af7fa4dce1edfb32097ed813ff4f38527 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 23 Oct 2016 12:00:25 +0200 Subject: [PATCH] - 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. --- src/scripting/codegeneration/codegen.cpp | 228 +++++++++++++++++++++++ src/scripting/codegeneration/codegen.h | 47 +++++ src/scripting/vm/vmexec.h | 7 + src/scripting/vm/vmops.h | 1 + src/scripting/zscript/zcc_compile.cpp | 21 ++- wadsrc/static/zscript.txt | 2 + 6 files changed, 304 insertions(+), 2 deletions(-) diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index d0713c50b..9568c34a5 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -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(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(content[i]); + if (casestmt->Condition == nullptr) defaultindex = i; + else if (casestmt->CaseValue == static_cast(Condition)->GetValue().GetInt()) caseindex = i; + } + if (content[i]->ExprType == EFX_JumpStatement && static_cast(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(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 BreakAddresses; + + for (auto line : *Content) + { + switch (line->ExprType) + { + case EFX_CaseStatement: + if (static_cast(line)->Condition != nullptr) + { + for (auto &ca : CaseAddresses) + { + if (ca.casevalue == static_cast(line)->CaseValue) + { + build->BackpatchToHere(ca.jumpaddress); + break; + } + } + } + else + { + build->BackpatchToHere(DefaultAddress); + } + break; + + case EFX_JumpStatement: + if (static_cast(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(Condition)->GetValue().GetInt(); + } + return this; +} + //========================================================================== // // FxIfStatement diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 8cce34a15..3ea97ba66 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -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 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 diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index d3c1632b8..2ddc50599 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -372,6 +372,13 @@ begin: pc++; } NEXTOP; + OP(TESTN): + ASSERTD(a); + if (-reg.d[a] != BC) + { + pc++; + } + NEXTOP; OP(JMP): pc += JMPOFS(pc); NEXTOP; diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h index 1b34dcc0f..9ffe2d847 100644 --- a/src/scripting/vm/vmops.h +++ b/src/scripting/vm/vmops.h @@ -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) diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 5d309bedc..ed85ab6b3 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2677,9 +2677,26 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast) // not yet done case AST_SwitchStmt: - case AST_CaseStmt: - break; + { + auto swtch = static_cast(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(swtch->Content); + return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(cmpnd->Content), *ast); + } + } + case AST_CaseStmt: + { + auto cases = static_cast(ast); + return new FxCaseStatement(ConvertNode(cases->Condition), *ast); + } case AST_CompoundStmt: { diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 6de64d07e..3ab33bfd9 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -177,3 +177,5 @@ zscript/chex/chexweapons.txt zscript/chex/chexitems.txt zscript/chex/chexdecorations.txt zscript/chex/chexplayer.txt + +zscript/test2.txt