Skip to content

Commit

Permalink
Introduce control-flow analysis algorithms and data structures.
Browse files Browse the repository at this point in the history
This introduces three new data structures. The first is ControlFlowGraph,
which represents a collection of basic blocks for a method. A basic block is
a unit of code that terminates in a control flow instruction, and for which
the only entry point is the start of the block. Basic blocks are represented by
the new Block class.

Finally, there is a new GraphBuilder class which can extract a ControlFlowGraph
for a method. It also performs basic verification on control-flow, namely:
 - Jump targets must be a valid decoded instruction in the method.
 - The method's local control flow does not escape the method bounds.
  • Loading branch information
dvander committed May 24, 2018
1 parent d79ac5a commit cad4ebc
Show file tree
Hide file tree
Showing 12 changed files with 1,149 additions and 213 deletions.
377 changes: 189 additions & 188 deletions include/smx/smx-v1-opcodes.h

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion third_party/amtl
2 changes: 2 additions & 0 deletions vm/AMBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ library.sources += [
'builtins.cpp',
'code-allocator.cpp',
'code-stubs.cpp',
'control-flow.cpp',
'compiled-function.cpp',
'environment.cpp',
'file-utils.cpp',
'graph-builder.cpp',
'interpreter.cpp',
'md5/md5.cpp',
'method-info.cpp',
Expand Down
73 changes: 73 additions & 0 deletions vm/bitset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// vim: set sts=2 ts=8 sw=2 tw=99 et:
//
// Copyright (C) 2006-2015 AlliedModders LLC
//
// This file is part of SourcePawn. SourcePawn 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.
//
// You should have received a copy of the GNU General Public License along with
// SourcePawn. If not, see http://www.gnu.org/licenses/.
//

#include <amtl/am-bits.h>
#include <amtl/am-maybe.h>
#include <amtl/am-function.h>
#include <amtl/am-vector.h>
#include <stddef.h>

namespace sp {

class BitSet
{
public:
BitSet()
{}
explicit BitSet(size_t max_bits)
: max_bits_(ke::Some(max_bits))
{}

bool test(uintptr_t bit) {
size_t word = word_for_bit(bit);
if (word >= words_.length())
return false;
return !!(words_[word] & (uintptr_t(1) << pos_in_word(bit)));
}

void set(uintptr_t bit) {
assert(!max_bits_ || bit <= *max_bits_);
size_t word = word_for_bit(bit);
if (word >= words_.length())
words_.resize(word + 1);
words_[word] |= (uintptr_t(1) << pos_in_word(bit));
}

void for_each(const ke::Function<void(uintptr_t)>& callback) {
for (size_t i = 0; i < words_.length(); i++) {
uintptr_t word = words_[i];

while (word) {
size_t bit = ke::FindRightmostBit(word);
word &= ~(uintptr_t(1) << bit);
callback(i * kBitsPerWord + bit);
}
}
}

private:
static const size_t kBitsPerWord = sizeof(uintptr_t) * 8;

static inline size_t word_for_bit(uintptr_t bit) {
return bit / kBitsPerWord;
}
static inline size_t pos_in_word(uintptr_t bit) {
return bit % kBitsPerWord;
}

private:
ke::Vector<uintptr_t> words_;
ke::Maybe<size_t> max_bits_;
};

} // namespace sp
119 changes: 119 additions & 0 deletions vm/control-flow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// vim: set sts=2 ts=8 sw=2 tw=99 et:
//
// Copyright (C) 2006-2015 AlliedModders LLC
//
// This file is part of SourcePawn. SourcePawn 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.
//
// You should have received a copy of the GNU General Public License along with
// SourcePawn. If not, see http://www.gnu.org/licenses/.
//

#include "control-flow.h"
#include "opcodes.h"

namespace sp {

using namespace ke;

ControlFlowGraph::ControlFlowGraph(PluginRuntime* rt, const uint8_t* start_offset)
: rt_(rt),
epoch_(1)
{
entry_ = newBlock(start_offset);
}

ControlFlowGraph::~ControlFlowGraph()
{
// This is necessary because blocks contain cycles between each other.
for (const auto& block : blocks_)
block->unlink();
}

ke::RefPtr<Block>
ControlFlowGraph::newBlock(const uint8_t* start)
{
ke::RefPtr<Block> block = new Block(*this, start);
blocks_.append(block);
return block;
}

void
ControlFlowGraph::dump(FILE* fp)
{
for (const auto& block : blocks_) {
fprintf(fp, "Block %p:\n", block.get());
for (const auto& pred : block->predecessors())
fprintf(fp, " predecessor: %p\n", pred.get());
for (const auto& child : block->successors())
fprintf(fp, " successor: %p\n", child.get());
fprintf(fp, " ---\n");
const uint8_t* cip = block->start();
while (true) {
if (block->endType() == BlockEnd::Jump && cip >= block->end())
break;
SpewOpcode(fp,
rt_,
reinterpret_cast<const cell_t*>(block->start()),
reinterpret_cast<const cell_t*>(cip));
cip = NextInstruction(cip);
if (block->endType() == BlockEnd::Insn && cip > block->end())
break;
}
fprintf(fp, "\n");
}
}

Block::Block(ControlFlowGraph& graph, const uint8_t* start)
: graph_(graph),
start_(start),
end_(nullptr),
end_type_(BlockEnd::Unknown),
epoch_(0)
{
}

void
Block::addTarget(Block* target)
{
target->predecessors_.append(this);
successors_.append(target);
}

void
Block::endWithJump(const uint8_t* end_at, Block* target)
{
end(end_at, BlockEnd::Jump);
addTarget(target);
}

void
Block::end(const uint8_t* end_at, BlockEnd end_type)
{
assert(!end_);
end_ = end_at;
end_type_ = end_type;
}

bool
Block::visited() const
{
return epoch_ == graph_.epoch();
}

void
Block::setVisited()
{
epoch_ = graph_.epoch();
}

void
Block::unlink()
{
predecessors_.clear();
successors_.clear();
}

} // namespace sp
121 changes: 121 additions & 0 deletions vm/control-flow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// vim: set sts=2 ts=8 sw=2 tw=99 et:
//
// Copyright (C) 2006-2015 AlliedModders LLC
//
// This file is part of SourcePawn. SourcePawn 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.
//
// You should have received a copy of the GNU General Public License along with
// SourcePawn. If not, see http://www.gnu.org/licenses/.
//
#ifndef _include_sourcepawn_vm_control_flow_h_
#define _include_sourcepawn_vm_control_flow_h_

#include <amtl/am-refcounting.h>
#include <amtl/am-vector.h>
#include <stdio.h>
#include "plugin-runtime.h"

namespace sp {

class ControlFlowGraph;

enum class BlockEnd {
Unknown,
Insn,
Jump
};

class Block : public ke::Refcounted<Block>
{
friend class ControlFlowGraph;

private:
Block(ControlFlowGraph& graph, const uint8_t* start);

public:
const uint8_t* start() const {
return start_;
}
const uint8_t* end() const {
return end_;
}
bool ended() const {
return !!end_;
}
BlockEnd endType() const {
return end_type_;
}
const ke::Vector<ke::RefPtr<Block>>& predecessors() const {
return predecessors_;
}
const ke::Vector<ke::RefPtr<Block>>& successors() const {
return successors_;
}

~Block() {
assert(!predecessors_.length());
assert(!successors_.length());
}

void addTarget(Block* target);
void endWithJump(const uint8_t* cip, Block* target);
void end(const uint8_t* end_at, BlockEnd end_type);

bool visited() const;
void setVisited();

// Zap all references so the block has no cycles.
void unlink();

private:
ControlFlowGraph& graph_;
ke::Vector<ke::RefPtr<Block>> predecessors_;
ke::Vector<ke::RefPtr<Block>> successors_;

// Note that |end| is dependent on end_type. If it's Insn, then end_ should
// be the |start| of the last instruction, since that is the terminating
// instruction.
const uint8_t* start_;
const uint8_t* end_;
BlockEnd end_type_;

// Counter for fast already-visited testing.
uint32_t epoch_;
};

class ControlFlowGraph : public ke::Refcounted<ControlFlowGraph>
{
public:
explicit ControlFlowGraph(PluginRuntime* rt, const uint8_t* start_offset);
~ControlFlowGraph();

ke::RefPtr<Block> entry() const {
return entry_;
}

ke::RefPtr<Block> newBlock(const uint8_t* start);

// Increase the epoch number, effectively resetting which blcoks have been
// visited.
void newEpoch() {
epoch_++;
}
uint32_t epoch() const {
return epoch_;
}

void dump(FILE* fp);

private:
PluginRuntime* rt_;
ke::RefPtr<Block> entry_;
ke::Vector<ke::RefPtr<Block>> blocks_;
uint32_t epoch_;
};

} // namespace sp

#endif // _include_sourcepawn_vm_control_flow_h_
Loading

0 comments on commit cad4ebc

Please sign in to comment.