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