Files
2022-06-26 19:14:49 +02:00

615 lines
20 KiB
C++

/* ScummVM Tools
*
* ScummVM Tools 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 "control_flow.h"
#include "stack.h"
#include <algorithm>
#include <iostream>
#include <set>
#include <boost/format.hpp>
#define PUT(vertex, group) boost::put(boost::vertex_name, _g, vertex, group);
#define PUT_EDGE(edge, isJump) boost::put(boost::edge_attribute, _g, edge, isJump);
#define PUT_ID(vertex, id) boost::put(boost::vertex_index, _g, vertex, id);
#define GET(vertex) (boost::get(boost::vertex_name, _g, vertex))
#define GET_EDGE(edge) (boost::get(boost::edge_attribute, _g, edge))
ControlFlow::ControlFlow(const InstVec &insts, Engine *engine) : _insts(insts) {
_engine = engine;
// Automatically add a function if we're not supposed to look for more functions and no functions are defined
// This avoids a special case for when no real functions exist in the script
if (engine->_functions.empty() && !_engine->detectMoreFuncs())
engine->_functions[(*insts.begin())->_address] = Function(insts.begin(), insts.end());
GroupPtr prev = NULL;
int id = 0;
// Create vertices
for (ConstInstIterator it = insts.begin(); it != insts.end(); ++it) {
GraphVertex cur = boost::add_vertex(_g);
_addrMap[(*it)->_address] = cur;
PUT(cur, new Group(cur, it, it, prev));
PUT_ID(cur, id);
id++;
// Add reference to vertex if function starts here
if (_engine->_functions.find((*it)->_address) != _engine->_functions.end())
_engine->_functions[(*it)->_address]._v = cur;
prev = GET(cur);
}
// Add regular edges
FuncMap::iterator fn;
GraphVertex last;
bool addEdge = false;
prev = NULL;
for (ConstInstIterator it = insts.begin(); it != insts.end(); ++it) {
if (_engine->_functions.find((*it)->_address) != _engine->_functions.end()) {
addEdge = false;
}
GraphVertex cur = find(it);
if (addEdge) {
GraphEdge e = boost::add_edge(last, cur, _g).first;
PUT_EDGE(e, false);
}
last = cur;
addEdge = !((*it)->isUncondJump() || (*it)->isReturn());
prev = GET(cur);
}
// Add jump edges
for (ConstInstIterator it = insts.begin(); it != insts.end(); ++it) {
if ((*it)->isJump()) {
GraphEdge e = boost::add_edge(find(it), find((*it)->getDestAddress()), _g).first;
PUT_EDGE(e, true);
}
}
}
GraphVertex ControlFlow::find(const InstPtr inst) {
return _addrMap[inst->_address];
}
GraphVertex ControlFlow::find(ConstInstIterator it) {
return _addrMap[(*it)->_address];
}
GraphVertex ControlFlow::find(uint32 address) {
std::map<uint32, GraphVertex>::iterator it = _addrMap.find(address);
if (it == _addrMap.end())
std::cerr << "Request for instruction at unknown address " << boost::format("0x%08x") % address << std::endl;
return it->second;
}
void ControlFlow::merge(GraphVertex g1, GraphVertex g2) {
// Update property
GroupPtr gr1 = GET(g1);
GroupPtr gr2 = GET(g2);
gr1->_end = gr2->_end;
PUT(g1, gr1);
// Update address map
ConstInstIterator it = gr2->_start;
do {
_addrMap[(*it)->_address] = g1;
++it;
} while (gr2->_start != gr2->_end && it != gr2->_end);
// Add outgoing edges from g2
OutEdgeRange r = boost::out_edges(g2, _g);
for (OutEdgeIterator e = r.first; e != r.second; ++e) {
GraphEdge newE = boost::add_edge(g1, boost::target(*e, _g), _g).first;
PUT_EDGE(newE, GET_EDGE(*e));
}
// Update _next pointer
gr1->_next = gr2->_next;
if (gr2->_next != NULL)
gr2->_next->_prev = gr2->_prev;
// Remove edges to/from g2
boost::clear_vertex(g2, _g);
// Remove vertex
boost::remove_vertex(g2, _g);
}
typedef std::pair<GraphVertex, int> LevelEntry;
void ControlFlow::setStackLevel(GraphVertex g, int level) {
Stack<LevelEntry> levelStack;
std::set<GraphVertex> seen;
levelStack.push(LevelEntry(g, level));
seen.insert(g);
while (!levelStack.empty()) {
LevelEntry e = levelStack.pop();
GroupPtr gr = GET(e.first);
if (gr->_stackLevel != -1) {
if (gr->_stackLevel != e.second)
std::cerr << boost::format("WARNING: Inconsistency in expected stack level for instruction at address 0x%08x (current: %d, requested: %d)\n") % (*gr->_start)->_address % gr->_stackLevel % e.second;
continue;
}
gr->_stackLevel = e.second;
OutEdgeRange r = boost::out_edges(e.first, _g);
for (OutEdgeIterator oe = r.first; oe != r.second; ++oe) {
GraphVertex target = boost::target(*oe, _g);
if (seen.find(target) == seen.end()) {
levelStack.push(LevelEntry(target, e.second + (*gr->_start)->_stackChange));
seen.insert(target);
}
}
}
}
void ControlFlow::detectFunctions() {
uint32 nextFunc = 0;
for (ConstInstIterator it = _insts.begin(); it != _insts.end(); ++it) {
GraphVertex v = find(it);
GroupPtr gr = GET(v);
if ((*it)->_address < nextFunc)
continue;
bool functionExists = false;
bool detectEndPoint = false;
for (FuncMap::iterator fn = _engine->_functions.begin(); fn != _engine->_functions.end(); ++fn) {
if (fn->first == (*it)->_address) {
if (fn->second._endIt == _insts.end()) {
return;
}
if (fn->second._startIt == fn->second._endIt) {
// We already know this is an entry point, we only need to detect the end point
detectEndPoint = true;
break;
}
nextFunc = (*fn->second._endIt)->_address;
functionExists = true;
}
}
if (functionExists)
continue;
bool isEntryPoint = true;
if (!detectEndPoint) {
InEdgeRange ier = boost::in_edges(v, _g);
for (InEdgeIterator e = ier.first; e != ier.second; ++e) {
// If an ingoing edge exists from earlier in the code, this is not a function entry point
if ((*GET(boost::source(*e, _g))->_start)->_address < (*gr->_start)->_address)
isEntryPoint = false;
}
}
if (isEntryPoint) {
// Detect end point
Stack<GraphVertex> stack;
std::set<GraphVertex> seen;
stack.push(v);
GroupPtr endPoint = gr;
while (!stack.empty()) {
v = stack.pop();
GroupPtr tmp = GET(v);
if ((*tmp->_start)->_address > (*endPoint->_start)->_address)
endPoint = tmp;
OutEdgeRange r = boost::out_edges(v, _g);
for (OutEdgeIterator i = r.first; i != r.second; ++i) {
GraphVertex target = boost::target(*i, _g);
if (seen.find(target) == seen.end()) {
stack.push(target);
seen.insert(target);
}
}
}
ConstInstIterator endInst;
if (endPoint->_next) {
endInst = endPoint->_next->_start;
nextFunc = (*endInst)->_address;
} else {
endInst = _insts.end();
}
Function f;
if (detectEndPoint) {
f = _engine->_functions[(*gr->_start)->_address];
f._endIt = endInst;
} else {
f = Function(gr->_start, endInst);
f._name = "auto_";
}
f._v = find(it);
_engine->_functions[(*gr->_start)->_address] = f;
if (!endPoint->_next)
return;
}
}
}
void ControlFlow::createGroups() {
if (!_engine->_functions.empty() && GET(_engine->_functions.begin()->second._v)->_stackLevel != -1)
return;
// Detect more functions
if (_engine->detectMoreFuncs())
detectFunctions();
for (FuncMap::iterator fn = _engine->_functions.begin(); fn != _engine->_functions.end(); ++fn)
setStackLevel(fn->second._v, 0);
ConstInstIterator curInst, nextInst;
nextInst = _insts.begin();
++nextInst;
int stackLevel = 0;
int expectedStackLevel = 0;
for (curInst = _insts.begin(); nextInst != _insts.end(); ++curInst, ++nextInst) {
GraphVertex cur = find(curInst);
GraphVertex next = find(nextInst);
GroupPtr grCur = GET(cur);
GroupPtr grNext = GET(next);
// Don't process unreachable code
if (grCur->_stackLevel < 0) {
stackLevel = grNext->_stackLevel;
continue;
}
expectedStackLevel = grCur->_stackLevel;
// If expected stack level decreases in next vertex, then use next vertex level as expected level
if (expectedStackLevel > grNext->_stackLevel && grNext->_stackLevel >= 0) {
expectedStackLevel = grNext->_stackLevel;
// Also set the stack level of the current group to remember that we expect it to be lower
grCur->_stackLevel = expectedStackLevel;
}
stackLevel += (*curInst)->_stackChange;
// For stack operations, the new stack level becomes the expected stack level starting from the next group
if ((*curInst)->isStackOp()) {
expectedStackLevel = stackLevel;
grNext->_stackLevel = stackLevel;
}
// Group ends after a jump
if ((*curInst)->isJump()) {
stackLevel = grNext->_stackLevel;
continue;
}
// Group ends with a return
if ((*curInst)->isReturn()) {
stackLevel = grNext->_stackLevel;
continue;
}
// Group ends before target of a jump
if (in_degree(next, _g) != 1) {
stackLevel = grNext->_stackLevel;
continue;
}
// This part is only relevant if we use the stack level.
if (!_engine->usePureGrouping()) {
// If group has no instructions with stack effect >= 0, don't merge on balanced stack
bool forceMerge = true;
ConstInstIterator it = grCur->_start;
do {
if ((*it)->_stackChange >= 0)
forceMerge = false;
++it;
} while (grCur->_start != grCur->_end && it != grCur->_end);
// Group ends when stack is balanced, unless just before conditional jump
if (stackLevel == expectedStackLevel && !forceMerge && !(*nextInst)->isCondJump()) {
continue;
}
}
// All checks passed, merge groups
merge(cur, next);
}
// FIXME: The short-circuit detection is disabled because short-circuited groups require some special handling
// in the code generation. It's not entirely clear how to handle it properly, though: you need to deduce which
// effect is created by the conditional jumps in the middle of a block, which seems to get fairly complex when
// you have multiple groups that are merged by the short-circuit detection.
//detectShortCircuit();
}
void ControlFlow::detectShortCircuit() {
ConstInstIterator lastInst = _insts.end();
--lastInst;
GraphVertex cur = find(lastInst);
GroupPtr gr = GET(cur);
while (gr->_prev != NULL) {
bool doMerge = false;
cur = find(gr->_start);
GraphVertex prev = find(gr->_prev->_start);
// Block is candidate for short-circuit merging if it and the preceding block both end with conditional jumps
if (out_degree(cur, _g) == 2 && out_degree(prev, _g) == 2) {
doMerge = true;
OutEdgeRange rCur = boost::out_edges(cur, _g);
std::vector<GraphVertex> succs;
// Find possible target vertices
for (OutEdgeIterator it = rCur.first; it != rCur.second; ++it) {
succs.push_back(boost::target(*it, _g));
}
// Check if vertex would add new targets - if yes, don't merge
OutEdgeRange rPrev = boost::out_edges(prev, _g);
for (OutEdgeIterator it = rPrev.first; it != rPrev.second; ++it) {
GraphVertex target = boost::target(*it, _g);
doMerge &= (std::find(succs.begin(), succs.end(), target) != succs.end() || target == cur);
}
if (doMerge) {
gr = gr->_prev;
merge(prev, cur);
continue;
}
}
gr = gr->_prev;
}
}
const Graph &ControlFlow::analyze() {
detectDoWhile();
detectWhile();
detectBreak();
detectContinue();
detectIf();
detectElse();
return _g;
}
void ControlFlow::detectWhile() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
// Undetermined block that ends with conditional jump
if (out_degree(*v, _g) == 2 && gr->_type == kNormalGroupType) {
InEdgeRange ier = boost::in_edges(*v, _g);
bool isWhile = false;
for (InEdgeIterator e = ier.first; e != ier.second; ++e) {
GroupPtr sourceGr = GET(boost::source(*e, _g));
// Block has ingoing edge from block later in the code that isn't a do-while condition
if ((*sourceGr->_start)->_address > (*gr->_start)->_address && sourceGr->_type != kDoWhileCondGroupType)
isWhile = true;
}
if (isWhile)
gr->_type = kWhileCondGroupType;
}
}
}
void ControlFlow::detectDoWhile() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
// Undetermined block that ends with conditional jump...
if (out_degree(*v, _g) == 2 && gr->_type == kNormalGroupType) {
OutEdgeRange oer = boost::out_edges(*v, _g);
for (OutEdgeIterator e = oer.first; e != oer.second; ++e) {
GroupPtr targetGr = GET(boost::target(*e, _g));
// ...to earlier in code
if ((*targetGr->_start)->_address < (*gr->_start)->_address)
gr->_type = kDoWhileCondGroupType;
}
}
}
}
void ControlFlow::detectBreak() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
// Undetermined block with unconditional jump...
if (gr->_type == kNormalGroupType && ((*gr->_end)->isUncondJump()) && out_degree(*v, _g) == 1) {
OutEdgeIterator oe = boost::out_edges(*v, _g).first;
GraphVertex target = boost::target(*oe, _g);
GroupPtr targetGr = GET(target);
// ...to somewhere later in the code...
if ((*gr->_start)->_address >= (*targetGr->_start)->_address)
continue;
InEdgeRange ier = boost::in_edges(target, _g);
for (InEdgeIterator ie = ier.first; ie != ier.second; ++ie) {
GroupPtr sourceGr = GET(boost::source(*ie, _g));
// ...to block immediately after a do-while condition, or to jump target of a while condition
if ((targetGr->_prev == sourceGr && sourceGr->_type == kDoWhileCondGroupType) || sourceGr->_type == kWhileCondGroupType) {
if (validateBreakOrContinue(gr, sourceGr))
gr->_type = kBreakGroupType;
}
}
}
}
}
void ControlFlow::detectContinue() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
// Undetermined block with unconditional jump...
if (gr->_type == kNormalGroupType && ((*gr->_end)->isUncondJump()) && out_degree(*v, _g) == 1) {
OutEdgeIterator oe = boost::out_edges(*v, _g).first;
GraphVertex target = boost::target(*oe, _g);
GroupPtr targetGr = GET(target);
// ...to a while or do-while condition...
if (targetGr->_type == kWhileCondGroupType || targetGr->_type == kDoWhileCondGroupType) {
bool isContinue = true;
// ...unless...
OutEdgeRange toer = boost::out_edges(target, _g);
bool afterJumpTargets = true;
for (OutEdgeIterator toe = toer.first; toe != toer.second; ++toe) {
// ...it is targeting a while condition which jumps to the next sequential group
if (targetGr->_type == kWhileCondGroupType && GET(boost::target(*toe, _g)) == gr->_next)
isContinue = false;
// ...or the instruction is placed after all jump targets from condition
if ((*GET(boost::target(*toe, _g))->_start)->_address > (*gr->_start)->_address)
afterJumpTargets = false;
}
if (afterJumpTargets)
isContinue = false;
if (isContinue && validateBreakOrContinue(gr, targetGr))
gr->_type = kContinueGroupType;
}
}
}
}
bool ControlFlow::validateBreakOrContinue(GroupPtr gr, GroupPtr condGr) {
GroupPtr from, to, cursor;
if (condGr->_type == kDoWhileCondGroupType) {
to = condGr;
from = gr;
} else {
to = gr;
from = condGr->_next;
}
GroupType ogt = (condGr->_type == kDoWhileCondGroupType ? kWhileCondGroupType : kDoWhileCondGroupType);
// Verify that destination deals with innermost while/do-while
for (cursor = from; cursor->_next != NULL && cursor != to; cursor = cursor->_next) {
if (cursor->_type == condGr->_type) {
OutEdgeRange oerValidate = boost::out_edges(find(cursor->_start), _g);
for (OutEdgeIterator oeValidate = oerValidate.first; oeValidate != oerValidate.second; ++oeValidate) {
GraphVertex vValidate = boost::target(*oeValidate, _g);
GroupPtr gValidate = GET(vValidate);
// For all other loops of same type found in range, all targets must fall within that range
if ((*gValidate->_start)->_address < (*from->_start)->_address || (*gValidate->_start)->_address > (*to->_start)->_address )
return false;
InEdgeRange ierValidate = boost::in_edges(vValidate, _g);
for (InEdgeIterator ieValidate = ierValidate.first; ieValidate != ierValidate.second; ++ieValidate) {
GroupPtr igValidate = GET(boost::source(*ieValidate, _g));
// All loops of other type going into range must be placed within range
if (igValidate->_type == ogt && ((*igValidate->_start)->_address < (*from->_start)->_address || (*igValidate->_start)->_address > (*to->_start)->_address ))
return false;
}
}
}
}
return true;
}
void ControlFlow::detectIf() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
// if: Undetermined block with conditional jump
if (gr->_type == kNormalGroupType && ((*gr->_end)->isCondJump())) {
gr->_type = kIfCondGroupType;
}
}
}
void ControlFlow::detectElse() {
VertexRange vr = boost::vertices(_g);
for (VertexIterator v = vr.first; v != vr.second; ++v) {
GroupPtr gr = GET(*v);
if (gr->_type == kIfCondGroupType) {
OutEdgeRange oer = boost::out_edges(*v, _g);
GraphVertex target;
uint32 maxAddress = 0;
GroupPtr targetGr;
// Find jump target
for (OutEdgeIterator oe = oer.first; oe != oer.second; ++oe) {
targetGr = GET(boost::target(*oe, _g));
if ((*targetGr->_start)->_address > maxAddress) {
target = boost::target(*oe, _g);
maxAddress = (*targetGr->_start)->_address;
}
}
targetGr = GET(target);
// else: Jump target of if immediately preceded by an unconditional jump...
if (!(*targetGr->_prev->_end)->isUncondJump())
continue;
// ...which is not a break or a continue...
if (targetGr->_prev->_type == kContinueGroupType || targetGr->_prev->_type == kBreakGroupType)
continue;
// ...to later in the code
OutEdgeIterator toe = boost::out_edges(find((*targetGr->_prev->_start)->_address), _g).first;
GroupPtr targetTargetGr = GET(boost::target(*toe, _g));
if ((*targetTargetGr->_start)->_address > (*targetGr->_end)->_address) {
if (validateElseBlock(gr, targetGr, targetTargetGr)) {
targetGr->_startElse = true;
targetTargetGr->_prev->_endElse.push_back(targetGr.get());
}
}
}
}
}
bool ControlFlow::validateElseBlock(GroupPtr ifGroup, GroupPtr start, GroupPtr end) {
for (GroupPtr cursor = start; cursor != end; cursor = cursor->_next) {
if (cursor->_type == kIfCondGroupType || cursor->_type == kWhileCondGroupType || cursor->_type == kDoWhileCondGroupType) {
// Validate outgoing edges of conditions
OutEdgeRange oer = boost::out_edges(find(cursor->_start), _g);
for (OutEdgeIterator oe = oer.first; oe != oer.second; ++oe) {
GraphVertex target = boost::target(*oe, _g);
GroupPtr targetGr = GET(target);
// Each edge from condition must not leave the range [start, end]
if ((*start->_start)->_address > (*targetGr->_start)->_address || (*targetGr->_start)->_address > (*end->_start)->_address)
return false;
}
}
// If previous group ends an else, that else must start inside the range
for (ElseEndIterator it = cursor->_prev->_endElse.begin(); it != cursor->_prev->_endElse.end(); ++it)
{
if ((*(*it)->_start)->_address < (*start->_start)->_address)
return false;
}
// Unless group is a simple unconditional jump...
if ((*cursor->_start)->isUncondJump())
continue;
// ...validate ingoing edges
InEdgeRange ier = boost::in_edges(find(cursor->_start), _g);
for (InEdgeIterator ie = ier.first; ie != ier.second; ++ie) {
GraphVertex source = boost::source(*ie, _g);
GroupPtr sourceGr = GET(source);
// Edges going to conditions...
if (sourceGr->_type == kIfCondGroupType || sourceGr->_type == kWhileCondGroupType || sourceGr->_type == kDoWhileCondGroupType) {
// ...must not come from outside the range [start, end]...
if ((*start->_start)->_address > (*sourceGr->_start)->_address || (*sourceGr->_start)->_address > (*end->_start)->_address) {
// ...unless source is simple unconditional jump...
if ((*sourceGr->_start)->isUncondJump())
continue;
// ...or the edge is from the if condition associated with this else
if (ifGroup == sourceGr)
continue;
return false;
}
}
}
}
return true;
}