Initial commit
This commit is contained in:
767
engines/mtropolis/coroutine_manager.cpp
Normal file
767
engines/mtropolis/coroutine_manager.cpp
Normal file
@@ -0,0 +1,767 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mtropolis/coroutine_manager.h"
|
||||
#include "mtropolis/coroutines.h"
|
||||
#include "mtropolis/coroutine_exec.h"
|
||||
|
||||
namespace MTropolis {
|
||||
|
||||
class CoroutineManager : public ICoroutineManager {
|
||||
public:
|
||||
CoroutineManager();
|
||||
~CoroutineManager();
|
||||
|
||||
private:
|
||||
void registerCoroutine(CompiledCoroutine **compiledCoroPtr) override;
|
||||
void compileCoroutine(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) override;
|
||||
|
||||
Common::Array<CompiledCoroutine **> _compiledCoroutineRefs;
|
||||
};
|
||||
|
||||
class CoroutineCompiler : public ICoroutineCompiler {
|
||||
public:
|
||||
explicit CoroutineCompiler(ICoroutineManager *coroManager);
|
||||
|
||||
void addFunctionToCompile(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn);
|
||||
|
||||
void compileAll();
|
||||
|
||||
void defineFunction(CoroutineFrameConstructor_t frameConstructor, CoroutineGetFrameParametersFunction_t frameGetParams) override;
|
||||
void addOp(CoroOps op, CoroutineFragmentFunction_t fragmentFunc) override;
|
||||
|
||||
private:
|
||||
struct PendingCompile {
|
||||
CompiledCoroutine *_compiledCoro;
|
||||
CoroutineCompileFunction_t _compileFunction;
|
||||
};
|
||||
|
||||
enum class ControlFlowType {
|
||||
Invalid = 0,
|
||||
|
||||
Function,
|
||||
If, // Label 1 = Else label, Label 2 = End label
|
||||
While, // Label 1 = Loop label, Label 2 = End label
|
||||
DoWhile, // Label 1 = Loop label, Label 2 = End label
|
||||
For, // Label 1 = Loop label, Label 2 = End label, Label 3 = Iterate label
|
||||
};
|
||||
|
||||
enum class ControlFlowState {
|
||||
Default = 0,
|
||||
|
||||
NoBody,
|
||||
NoElse,
|
||||
HasElse,
|
||||
};
|
||||
|
||||
struct ControlFlowStack {
|
||||
ControlFlowStack()
|
||||
: _type(ControlFlowType::Invalid), _state(ControlFlowState::Default), _endLabel(0), _loopOrElseLabel(0), _iterateLabel(0) {
|
||||
}
|
||||
|
||||
ControlFlowType _type;
|
||||
ControlFlowState _state;
|
||||
uint _endLabel;
|
||||
uint _loopOrElseLabel;
|
||||
uint _iterateLabel;
|
||||
};
|
||||
|
||||
enum class ProtoOp {
|
||||
Invalid = 0,
|
||||
|
||||
Code,
|
||||
|
||||
NoOp,
|
||||
|
||||
Jump,
|
||||
JumpIfFalse,
|
||||
Label,
|
||||
|
||||
YieldToFunction,
|
||||
CheckMiniscript,
|
||||
|
||||
Return,
|
||||
Error,
|
||||
|
||||
InfiniteLoop,
|
||||
};
|
||||
|
||||
struct ProtoInstr {
|
||||
ProtoOp _op;
|
||||
uint _value;
|
||||
CoroutineFragmentFunction_t _func;
|
||||
};
|
||||
|
||||
void compileOne(CompiledCoroutine *compiledCoro, CoroutineCompileFunction_t compileFunction);
|
||||
void reportError(const char *str);
|
||||
|
||||
void addProtoInstr(ProtoOp op, CoroutineFragmentFunction_t func);
|
||||
void addProtoInstr(ProtoOp op, uint value, CoroutineFragmentFunction_t func);
|
||||
void addProtoInstr(ProtoOp op, uint value);
|
||||
void addProtoInstr(ProtoOp op);
|
||||
|
||||
static bool isSimpleTerminalOp(ProtoOp op);
|
||||
|
||||
uint allocLabel();
|
||||
|
||||
ICoroutineManager *_coroManager;
|
||||
Common::Array<PendingCompile> _pendingCompiles;
|
||||
|
||||
Common::Array<ControlFlowStack> _funcControlFlowStack;
|
||||
Common::Array<ProtoInstr> _funcProtoInstrs;
|
||||
uint _funcNumLabels;
|
||||
bool _funcIsVoidReturn;
|
||||
|
||||
CoroutineFrameConstructor_t _funcFrameCtor;
|
||||
CoroutineGetFrameParametersFunction_t _funcFrameGetParams;
|
||||
};
|
||||
|
||||
CoroutineManager::CoroutineManager() {
|
||||
}
|
||||
|
||||
CoroutineManager::~CoroutineManager() {
|
||||
for (CompiledCoroutine **compiledCoroRef : _compiledCoroutineRefs) {
|
||||
delete (*compiledCoroRef);
|
||||
*compiledCoroRef = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CoroutineManager::registerCoroutine(CompiledCoroutine **compiledCoroPtr) {
|
||||
_compiledCoroutineRefs.push_back(compiledCoroPtr);
|
||||
}
|
||||
|
||||
void CoroutineManager::compileCoroutine(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) {
|
||||
CoroutineCompiler coroCompiler(this);
|
||||
|
||||
coroCompiler.addFunctionToCompile(compiledCoroPtr, compileFunction, isVoidReturn);
|
||||
coroCompiler.compileAll();
|
||||
}
|
||||
|
||||
CoroutineCompiler::CoroutineCompiler(ICoroutineManager *coroManager)
|
||||
: _coroManager(coroManager), _funcFrameCtor(nullptr), _funcFrameGetParams(nullptr), _funcNumLabels(0), _funcIsVoidReturn(false) {
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addFunctionToCompile(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) {
|
||||
if (*compiledCoroPtr)
|
||||
return;
|
||||
|
||||
CompiledCoroutine *compiledCoro = new CompiledCoroutine();
|
||||
|
||||
_coroManager->registerCoroutine(compiledCoroPtr);
|
||||
|
||||
compiledCoro->_isVoidReturn = isVoidReturn;
|
||||
|
||||
PendingCompile pendingCompile;
|
||||
pendingCompile._compiledCoro = compiledCoro;
|
||||
pendingCompile._compileFunction = compileFunction;
|
||||
|
||||
*compiledCoroPtr = pendingCompile._compiledCoro;
|
||||
|
||||
_pendingCompiles.push_back(pendingCompile);
|
||||
}
|
||||
|
||||
void CoroutineCompiler::compileAll() {
|
||||
// pendingCompiles may grow during this
|
||||
for (uint i = 0; i < _pendingCompiles.size(); i++) {
|
||||
const PendingCompile &pendingCompile = _pendingCompiles[i];
|
||||
compileOne(pendingCompile._compiledCoro, pendingCompile._compileFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void CoroutineCompiler::defineFunction(CoroutineFrameConstructor_t frameConstructor, CoroutineGetFrameParametersFunction_t frameGetParams) {
|
||||
_funcFrameCtor = frameConstructor;
|
||||
_funcFrameGetParams = frameGetParams;
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addOp(CoroOps op, CoroutineFragmentFunction_t fragmentFunc) {
|
||||
if (op == CoroOps::BeginFunction && _funcProtoInstrs.size() != 0)
|
||||
reportError("Begin function came after the start of the function");
|
||||
|
||||
if (op != CoroOps::BeginFunction && _funcProtoInstrs.size() == 0)
|
||||
reportError("First op wasn't begin function");
|
||||
|
||||
if (op != CoroOps::BeginFunction && _funcControlFlowStack.size() == 0)
|
||||
reportError("Op after end of function");
|
||||
|
||||
switch (op) {
|
||||
case CoroOps::BeginFunction: {
|
||||
ControlFlowStack cf;
|
||||
cf._type = ControlFlowType::Function;
|
||||
|
||||
_funcControlFlowStack.push_back(cf);
|
||||
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::EndFunction: {
|
||||
if (_funcControlFlowStack.size() != 1)
|
||||
reportError("End function doesn't close function scope");
|
||||
|
||||
_funcControlFlowStack.pop_back();
|
||||
|
||||
if (_funcIsVoidReturn) {
|
||||
addProtoInstr(ProtoOp::Return);
|
||||
} else {
|
||||
if (_funcProtoInstrs.back()._op != ProtoOp::Return)
|
||||
reportError("Value-returning function didn't return a value");
|
||||
}
|
||||
} break;
|
||||
|
||||
case CoroOps::IfCond: {
|
||||
ControlFlowStack cf;
|
||||
cf._type = ControlFlowType::If;
|
||||
cf._state = ControlFlowState::NoBody;
|
||||
cf._endLabel = allocLabel();
|
||||
cf._loopOrElseLabel = allocLabel();
|
||||
|
||||
_funcControlFlowStack.push_back(cf);
|
||||
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::IfBody: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::If || cf._state != ControlFlowState::NoBody)
|
||||
reportError("If body in wrong location");
|
||||
|
||||
cf._state = ControlFlowState::NoElse;
|
||||
|
||||
addProtoInstr(ProtoOp::JumpIfFalse, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
case CoroOps::Else: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::If)
|
||||
reportError("Unexpected 'else'");
|
||||
|
||||
if (cf._state != ControlFlowState::NoElse)
|
||||
reportError("If block has an 'else' already");
|
||||
|
||||
cf._state = ControlFlowState::HasElse;
|
||||
|
||||
addProtoInstr(ProtoOp::Jump, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
case CoroOps::ElseIfCond: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::If)
|
||||
reportError("Unexpected 'else if'");
|
||||
|
||||
if (cf._state != ControlFlowState::NoElse)
|
||||
reportError("If block has an 'else' already");
|
||||
|
||||
addProtoInstr(ProtoOp::Jump, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
|
||||
cf._loopOrElseLabel = allocLabel();
|
||||
} break;
|
||||
case CoroOps::ElseIfBody: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::If)
|
||||
reportError("Else if body in the wrong place");
|
||||
|
||||
addProtoInstr(ProtoOp::JumpIfFalse, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
case CoroOps::EndIf: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::If)
|
||||
reportError("Else if body in the wrong place");
|
||||
|
||||
if (cf._state != ControlFlowState::HasElse)
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
|
||||
addProtoInstr(ProtoOp::Label, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
|
||||
_funcControlFlowStack.pop_back();
|
||||
} break;
|
||||
|
||||
case CoroOps::WhileCond: {
|
||||
ControlFlowStack cf;
|
||||
cf._type = ControlFlowType::While;
|
||||
cf._loopOrElseLabel = allocLabel();
|
||||
cf._endLabel = allocLabel();
|
||||
|
||||
_funcControlFlowStack.push_back(cf);
|
||||
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
case CoroOps::WhileBody: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::While)
|
||||
reportError("While body in the wrong place");
|
||||
|
||||
addProtoInstr(ProtoOp::JumpIfFalse, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
case CoroOps::EndWhile: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::While)
|
||||
reportError("'end while' didn't close while block");
|
||||
|
||||
addProtoInstr(ProtoOp::Jump, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Label, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
|
||||
_funcControlFlowStack.pop_back();
|
||||
} break;
|
||||
|
||||
// Order of for loops is Next->Cond->Body
|
||||
case CoroOps::ForNext: {
|
||||
ControlFlowStack cf;
|
||||
cf._type = ControlFlowType::For;
|
||||
cf._iterateLabel = allocLabel();
|
||||
cf._loopOrElseLabel = allocLabel();
|
||||
cf._endLabel = allocLabel();
|
||||
|
||||
_funcControlFlowStack.push_back(cf);
|
||||
|
||||
addProtoInstr(ProtoOp::Jump, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Label, cf._iterateLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::ForCond: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::For)
|
||||
reportError("'for' condition in the wrong place");
|
||||
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::ForBody: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::For)
|
||||
reportError("'for' body in the wrong place");
|
||||
|
||||
addProtoInstr(ProtoOp::JumpIfFalse, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::EndFor: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::For)
|
||||
reportError("'end for' didn't close a for loop");
|
||||
|
||||
addProtoInstr(ProtoOp::Jump, cf._iterateLabel);
|
||||
addProtoInstr(ProtoOp::Label, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
|
||||
_funcControlFlowStack.pop_back();
|
||||
} break;
|
||||
|
||||
case CoroOps::Do: {
|
||||
ControlFlowStack cf;
|
||||
cf._type = ControlFlowType::DoWhile;
|
||||
cf._loopOrElseLabel = allocLabel();
|
||||
cf._endLabel = allocLabel();
|
||||
|
||||
_funcControlFlowStack.push_back(cf);
|
||||
|
||||
addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::DoWhileCond: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::DoWhile)
|
||||
reportError("'do/while' condition didn't close a 'do' block");
|
||||
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
} break;
|
||||
|
||||
case CoroOps::DoWhile: {
|
||||
ControlFlowStack &cf = _funcControlFlowStack.back();
|
||||
if (cf._type != ControlFlowType::DoWhile)
|
||||
reportError("'do while' in the wrong place");
|
||||
|
||||
addProtoInstr(ProtoOp::JumpIfFalse, fragmentFunc);
|
||||
addProtoInstr(ProtoOp::Label, cf._endLabel);
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
|
||||
_funcControlFlowStack.pop_back();
|
||||
} break;
|
||||
|
||||
case CoroOps::Return:
|
||||
addProtoInstr(ProtoOp::Return);
|
||||
break;
|
||||
|
||||
case CoroOps::Error:
|
||||
addProtoInstr(ProtoOp::Error);
|
||||
break;
|
||||
|
||||
case CoroOps::Code:
|
||||
addProtoInstr(ProtoOp::Code, fragmentFunc);
|
||||
break;
|
||||
|
||||
case CoroOps::YieldToFunction:
|
||||
addProtoInstr(ProtoOp::YieldToFunction);
|
||||
break;
|
||||
|
||||
case CoroOps::CheckMiniscript:
|
||||
addProtoInstr(ProtoOp::CheckMiniscript);
|
||||
break;
|
||||
|
||||
default:
|
||||
reportError("Unimplemented coro opcode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CoroutineCompiler::compileOne(CompiledCoroutine *compiledCoro, CoroutineCompileFunction_t compileFunction) {
|
||||
_funcNumLabels = 0;
|
||||
_funcProtoInstrs.clear();
|
||||
|
||||
_funcIsVoidReturn = compiledCoro->_isVoidReturn;
|
||||
|
||||
compileFunction(this);
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
if (instr._op == ProtoOp::Code) {
|
||||
// Empty cdecl function:
|
||||
// 33 c0 xor eax,eax
|
||||
// c3 ret
|
||||
const byte emptyFunctionSignature[] = {0x33u, 0xc0u, 0xc3u};
|
||||
|
||||
if (!memcmp(reinterpret_cast<const void *>(instr._func), emptyFunctionSignature, sizeof(emptyFunctionSignature)))
|
||||
instr._op = ProtoOp::NoOp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Renumber label to instructions
|
||||
{
|
||||
Common::Array<uint> labelToInstr;
|
||||
labelToInstr.resize(_funcNumLabels, (uint)-1);
|
||||
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
|
||||
if (instr._op == ProtoOp::Label) {
|
||||
labelToInstr[instr._value] = i;
|
||||
instr._op = ProtoOp::NoOp;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
|
||||
if (instr._op == ProtoOp::JumpIfFalse || instr._op == ProtoOp::Jump) {
|
||||
assert(labelToInstr[instr._value] != (uint)-1);
|
||||
instr._value = labelToInstr[instr._value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool haveWork = true;
|
||||
while (haveWork) {
|
||||
haveWork = false;
|
||||
|
||||
// Locate infinite loops and thread jumps
|
||||
{
|
||||
Common::Array<uint> chainEndInstr;
|
||||
Common::Array<uint> instrJumpRoot;
|
||||
Common::Array<uint> rootToChain;
|
||||
|
||||
uint initialNumInstrs = _funcProtoInstrs.size();
|
||||
|
||||
chainEndInstr.push_back(0);
|
||||
rootToChain.push_back(0);
|
||||
|
||||
instrJumpRoot.resize(initialNumInstrs, 0);
|
||||
|
||||
for (uint i = 0; i < initialNumInstrs; i++) {
|
||||
const ProtoInstr &baseInstr = _funcProtoInstrs[i];
|
||||
|
||||
if (baseInstr._op == ProtoOp::Jump && instrJumpRoot[i] == 0) {
|
||||
uint jumpRootID = rootToChain.size();
|
||||
uint chainID = chainEndInstr.size();
|
||||
|
||||
chainEndInstr.push_back(0);
|
||||
rootToChain.push_back(chainID);
|
||||
|
||||
uint traceInstr = i;
|
||||
for (;;) {
|
||||
const ProtoInstr &instr = _funcProtoInstrs[traceInstr];
|
||||
|
||||
// Ended as a new chain
|
||||
if (instr._op != ProtoOp::Jump) {
|
||||
chainEndInstr[chainID] = traceInstr;
|
||||
break;
|
||||
}
|
||||
|
||||
if (instr._op == ProtoOp::InfiniteLoop || instrJumpRoot[traceInstr] == jumpRootID) {
|
||||
// Ended in an infinite loop.
|
||||
chainEndInstr[chainID] = (uint)-1;
|
||||
break;
|
||||
} else if (instrJumpRoot[traceInstr] == 0) {
|
||||
// Propagate jump chain
|
||||
instrJumpRoot[traceInstr] = jumpRootID;
|
||||
traceInstr = instr._value;
|
||||
} else {
|
||||
// Converge into existing chain
|
||||
rootToChain[jumpRootID] = rootToChain[instrJumpRoot[traceInstr]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < initialNumInstrs; i++) {
|
||||
ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
|
||||
if (instr._op == ProtoOp::Jump) {
|
||||
uint endInstr = chainEndInstr[rootToChain[instrJumpRoot[i]]];
|
||||
if (endInstr == (uint)-1)
|
||||
instr._op = ProtoOp::InfiniteLoop;
|
||||
else
|
||||
instr._value = endInstr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
|
||||
if (instr._op == ProtoOp::JumpIfFalse || instr._op == ProtoOp::Jump) {
|
||||
// Remove jumps that jump to the next instruction
|
||||
if (instr._value == i + 1) {
|
||||
instr._op = ProtoOp::NoOp;
|
||||
continue;
|
||||
}
|
||||
|
||||
const ProtoInstr &targetInstr = _funcProtoInstrs[instr._value];
|
||||
|
||||
if (instr._op == ProtoOp::JumpIfFalse) {
|
||||
// Thread conditional jumps to jumps
|
||||
if (targetInstr._op == ProtoOp::Jump) {
|
||||
instr._value = targetInstr._value;
|
||||
haveWork = true;
|
||||
}
|
||||
|
||||
// Remove conditional jumps that jump to the same target as the next instruction
|
||||
const ProtoInstr &nextInstr = _funcProtoInstrs[i + 1];
|
||||
if (nextInstr._op == ProtoOp::Jump && instr._value == nextInstr._value) {
|
||||
instr._op = ProtoOp::NoOp;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove conditional jumps to simple terminal ops with the terminal op
|
||||
if (isSimpleTerminalOp(nextInstr._op) && targetInstr._op == nextInstr._op) {
|
||||
instr._op = ProtoOp::NoOp;
|
||||
continue;
|
||||
}
|
||||
} else if (instr._op == ProtoOp::Jump) {
|
||||
// Replace jumps to simple terminal ops
|
||||
if (isSimpleTerminalOp(targetInstr._op)) {
|
||||
instr._op = targetInstr._op;
|
||||
haveWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dead instructions
|
||||
{
|
||||
Common::Array<bool> instrIsAlive;
|
||||
Common::Array<uint> pendingExecRoots;
|
||||
pendingExecRoots.push_back(0);
|
||||
|
||||
instrIsAlive.resize(_funcProtoInstrs.size(), false);
|
||||
instrIsAlive[0] = true;
|
||||
|
||||
while (pendingExecRoots.size() > 0) {
|
||||
uint fillLocation = pendingExecRoots.back();
|
||||
pendingExecRoots.pop_back();
|
||||
|
||||
for (;;) {
|
||||
const ProtoInstr &instr = _funcProtoInstrs[fillLocation];
|
||||
|
||||
if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::JumpIfFalse) {
|
||||
if (!instrIsAlive[instr._value]) {
|
||||
pendingExecRoots.push_back(instr._value);
|
||||
instrIsAlive[instr._value] = true;
|
||||
}
|
||||
}
|
||||
|
||||
instrIsAlive[fillLocation] = true;
|
||||
|
||||
if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::Return || instr._op == ProtoOp::InfiniteLoop)
|
||||
break;
|
||||
|
||||
fillLocation++;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
if (_funcProtoInstrs[i]._op == ProtoOp::NoOp)
|
||||
instrIsAlive[i] = false;
|
||||
}
|
||||
|
||||
uint numDeadInstructions = 0;
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
if (!instrIsAlive[i])
|
||||
numDeadInstructions++;
|
||||
}
|
||||
|
||||
if (numDeadInstructions > 0) {
|
||||
haveWork = true;
|
||||
|
||||
Common::Array<ProtoInstr> newInstrs;
|
||||
|
||||
newInstrs.resize(_funcProtoInstrs.size() - numDeadInstructions);
|
||||
uint newInstrWritePos = 0;
|
||||
|
||||
for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
|
||||
if (instrIsAlive[i])
|
||||
newInstrs[newInstrWritePos++] = _funcProtoInstrs[i];
|
||||
}
|
||||
|
||||
assert(newInstrWritePos == newInstrs.size());
|
||||
|
||||
Common::Array<uint> oldInstrToNewInstr;
|
||||
oldInstrToNewInstr.resize(_funcProtoInstrs.size());
|
||||
|
||||
uint newInstrIndex = newInstrs.size();
|
||||
uint oldInstrIndex = _funcProtoInstrs.size();
|
||||
|
||||
for (;;) {
|
||||
oldInstrIndex--;
|
||||
|
||||
if (instrIsAlive[oldInstrIndex]) {
|
||||
assert(newInstrIndex > 0);
|
||||
newInstrIndex--;
|
||||
}
|
||||
|
||||
oldInstrToNewInstr[oldInstrIndex] = newInstrIndex;
|
||||
|
||||
if (oldInstrIndex == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
for (ProtoInstr &instr : newInstrs) {
|
||||
if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::JumpIfFalse)
|
||||
instr._value = oldInstrToNewInstr[instr._value];
|
||||
}
|
||||
|
||||
_funcProtoInstrs = Common::move(newInstrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compiledCoro->_frameConstructor = _funcFrameCtor;
|
||||
compiledCoro->_getFrameParameters = _funcFrameGetParams;
|
||||
compiledCoro->_numInstructions = _funcProtoInstrs.size();
|
||||
compiledCoro->_instructions = new CoroExecInstr[compiledCoro->_numInstructions];
|
||||
|
||||
CoroExecInstr *outInstrs = compiledCoro->_instructions;
|
||||
|
||||
for (uint i = 0; i < compiledCoro->_numInstructions; i++) {
|
||||
const ProtoInstr &instr = _funcProtoInstrs[i];
|
||||
CoroExecInstr *outInstr = outInstrs + i;
|
||||
|
||||
switch (instr._op) {
|
||||
case ProtoOp::Code:
|
||||
outInstr->_opcode = CoroExecOp::Code;
|
||||
outInstr->_func = instr._func;
|
||||
break;
|
||||
case ProtoOp::Jump:
|
||||
outInstr->_opcode = CoroExecOp::Jump;
|
||||
outInstr->_arg = instr._value;
|
||||
break;
|
||||
case ProtoOp::JumpIfFalse:
|
||||
outInstr->_opcode = CoroExecOp::JumpIfFalse;
|
||||
outInstr->_arg = instr._value;
|
||||
break;
|
||||
case ProtoOp::Return:
|
||||
outInstr->_opcode = CoroExecOp::ExitFunction;
|
||||
outInstr->_arg = 0;
|
||||
break;
|
||||
case ProtoOp::YieldToFunction:
|
||||
outInstr->_opcode = CoroExecOp::EnterFunction;
|
||||
outInstr->_arg = 0;
|
||||
break;
|
||||
case ProtoOp::Error:
|
||||
outInstr->_opcode = CoroExecOp::Error;
|
||||
outInstr->_arg = 0;
|
||||
break;
|
||||
case ProtoOp::CheckMiniscript:
|
||||
outInstr->_opcode = CoroExecOp::CheckMiniscript;
|
||||
outInstr->_arg = 0;
|
||||
break;
|
||||
default:
|
||||
error("Internal error: Unhandled coro op");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoroutineCompiler::reportError(const char *str) {
|
||||
error("%s", str);
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addProtoInstr(ProtoOp op, CoroutineFragmentFunction_t func) {
|
||||
addProtoInstr(op, 0, func);
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addProtoInstr(ProtoOp op, uint value) {
|
||||
addProtoInstr(op, value, nullptr);
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addProtoInstr(ProtoOp op) {
|
||||
addProtoInstr(op, 0, nullptr);
|
||||
}
|
||||
|
||||
void CoroutineCompiler::addProtoInstr(ProtoOp op, uint value, CoroutineFragmentFunction_t func) {
|
||||
ProtoInstr instr;
|
||||
instr._func = func;
|
||||
instr._op = op;
|
||||
instr._value = value;
|
||||
|
||||
_funcProtoInstrs.push_back(instr);
|
||||
}
|
||||
|
||||
bool CoroutineCompiler::isSimpleTerminalOp(ProtoOp op) {
|
||||
return op == ProtoOp::InfiniteLoop || op == ProtoOp::Error || op == ProtoOp::Return;
|
||||
}
|
||||
|
||||
uint CoroutineCompiler::allocLabel() {
|
||||
return _funcNumLabels++;
|
||||
}
|
||||
|
||||
ICoroutineManager::~ICoroutineManager() {
|
||||
}
|
||||
|
||||
ICoroutineManager *ICoroutineManager::create() {
|
||||
return new CoroutineManager();
|
||||
}
|
||||
|
||||
ICoroutineCompiler::~ICoroutineCompiler() {
|
||||
}
|
||||
|
||||
} // End of namespace MTropolis
|
||||
Reference in New Issue
Block a user