From cad4ebc37257872553d41bbd78a966b1c66751a6 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 21 May 2018 23:30:30 -0700 Subject: [PATCH] Introduce control-flow analysis algorithms and data structures. 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. --- include/smx/smx-v1-opcodes.h | 377 ++++++++++++++-------------- third_party/amtl | 2 +- vm/AMBuilder | 2 + vm/bitset.h | 73 ++++++ vm/control-flow.cpp | 119 +++++++++ vm/control-flow.h | 121 +++++++++ vm/graph-builder.cpp | 464 +++++++++++++++++++++++++++++++++++ vm/graph-builder.h | 123 ++++++++++ vm/jit.cpp | 2 +- vm/method-info.cpp | 1 + vm/opcodes.cpp | 57 +++-- vm/opcodes.h | 21 +- 12 files changed, 1149 insertions(+), 213 deletions(-) create mode 100644 vm/bitset.h create mode 100644 vm/control-flow.cpp create mode 100644 vm/control-flow.h create mode 100644 vm/graph-builder.cpp create mode 100644 vm/graph-builder.h diff --git a/include/smx/smx-v1-opcodes.h b/include/smx/smx-v1-opcodes.h index 5f947de53..f46c771d3 100644 --- a/include/smx/smx-v1-opcodes.h +++ b/include/smx/smx-v1-opcodes.h @@ -68,199 +68,200 @@ namespace sp { // sign.pri/alt // // _G - generated, _U - ungenerated -#define OPCODE_LIST(_G, _U) \ - _G(NONE, "none") \ - _G(LOAD_PRI, "load.pri") \ - _G(LOAD_ALT, "load.alt") \ - _G(LOAD_S_PRI, "load.s.pri") \ - _G(LOAD_S_ALT, "load.s.alt") \ - _U(LREF_PRI, "lref.pri") \ - _U(LREF_ALT, "lref.alt") \ - _G(LREF_S_PRI, "lref.s.pri") \ - _G(LREF_S_ALT, "lref.s.alt") \ - _G(LOAD_I, "load.i") \ - _G(LODB_I, "lodb.i") \ - _G(CONST_PRI, "const.pri") \ - _G(CONST_ALT, "const.alt") \ - _G(ADDR_PRI, "addr.pri") \ - _G(ADDR_ALT, "addr.alt") \ - _G(STOR_PRI, "stor.pri") \ - _G(STOR_ALT, "stor.alt") \ - _G(STOR_S_PRI, "stor.s.pri") \ - _G(STOR_S_ALT, "stor.s.alt") \ - _U(SREF_PRI, "sref.pri") \ - _U(SREF_ALT, "sref.alt") \ - _G(SREF_S_PRI, "sref.s.pri") \ - _G(SREF_S_ALT, "sref.s.alt") \ - _G(STOR_I, "stor.i") \ - _G(STRB_I, "strb.i") \ - _G(LIDX, "lidx") \ - _U(LIDX_B, "lidx.b") \ - _G(IDXADDR, "idxaddr") \ - _U(IDXADDR_B, "idxaddr.b") \ - _U(ALIGN_PRI, "align.pri") \ - _U(ALIGN_ALT, "align.alt") \ - _U(LCTRL, "lctrl") \ - _U(SCTRL, "sctrl") \ - _G(MOVE_PRI, "move.pri") \ - _G(MOVE_ALT, "move.alt") \ - _G(XCHG, "xchg") \ - _G(PUSH_PRI, "push.pri") \ - _G(PUSH_ALT, "push.alt") \ - _U(PUSH_R, "push.r") \ - _G(PUSH_C, "push.c") \ - _G(PUSH, "push") \ - _G(PUSH_S, "push.s") \ - _G(POP_PRI, "pop.pri") \ - _G(POP_ALT, "pop.alt") \ - _G(STACK, "stack") \ - _G(HEAP, "heap") \ - _G(PROC, "proc") \ - _U(RET, "ret") \ - _G(RETN, "retn") \ - _G(CALL, "call") \ - _U(CALL_PRI, "call.pri") \ - _G(JUMP, "jump") \ - _U(JREL, "jrel") \ - _G(JZER, "jzer") \ - _G(JNZ, "jnz") \ - _G(JEQ, "jeq") \ - _G(JNEQ, "jneq") \ - _U(JLESS, "jsless") \ - _U(JLEQ, "jleq") \ - _U(JGRTR, "jgrtr") \ - _U(JGEQ, "jgeq") \ - _G(JSLESS, "jsless") \ - _G(JSLEQ, "jsleq") \ - _G(JSGRTR, "jsgrtr") \ - _G(JSGEQ, "jsgeq") \ - _G(SHL, "shl") \ - _G(SHR, "shr") \ - _G(SSHR, "sshr") \ - _G(SHL_C_PRI, "shl.c.pri") \ - _G(SHL_C_ALT, "shl.c.alt") \ - _U(SHR_C_PRI, "shr.c.pri") \ - _U(SHR_C_ALT, "shr.c.alt") \ - _G(SMUL, "smul") \ - _G(SDIV, "sdiv") \ - _G(SDIV_ALT, "sdiv.alt") \ - _U(UMUL, "umul") \ - _U(UDIV, "udiv") \ - _U(UDIV_ALT, "udiv.alt") \ - _G(ADD, "add") \ - _G(SUB, "sub") \ - _G(SUB_ALT, "sub.alt") \ - _G(AND, "and") \ - _G(OR, "or") \ - _G(XOR, "xor") \ - _G(NOT, "not") \ - _G(NEG, "neg") \ - _G(INVERT, "invert") \ - _G(ADD_C, "add.c") \ - _G(SMUL_C, "smul.c") \ - _G(ZERO_PRI, "zero.pri") \ - _G(ZERO_ALT, "zero.alt") \ - _G(ZERO, "zero") \ - _G(ZERO_S, "zero.s") \ - _U(SIGN_PRI, "sign.pri") \ - _U(SIGN_ALT, "sign.alt") \ - _G(EQ, "eq") \ - _G(NEQ, "neq") \ - _U(LESS, "less") \ - _U(LEQ, "leq") \ - _U(GRTR, "grtr") \ - _U(GEQ, "geq") \ - _G(SLESS, "sless") \ - _G(SLEQ, "sleq") \ - _G(SGRTR, "sgrtr") \ - _G(SGEQ, "sgeq") \ - _G(EQ_C_PRI, "eq.c.pri") \ - _G(EQ_C_ALT, "eq.c.alt") \ - _G(INC_PRI, "inc.pri") \ - _G(INC_ALT, "inc.alt") \ - _G(INC, "inc") \ - _G(INC_S, "inc.s") \ - _G(INC_I, "inc.i") \ - _G(DEC_PRI, "dec.pri") \ - _G(DEC_ALT, "dec.alt") \ - _G(DEC, "dec") \ - _G(DEC_S, "dec.s") \ - _G(DEC_I, "dec.i") \ - _G(MOVS, "movs") \ - _U(CMPS, "cmps") \ - _G(FILL, "fill") \ - _G(HALT, "halt") \ - _G(BOUNDS, "bounds") \ - _U(SYSREQ_PRI, "sysreq.pri") \ - _G(SYSREQ_C, "sysreq.c") \ - _U(FILE, "file") \ - _U(LINE, "line") \ - _U(SYMBOL, "symbol") \ - _U(SRANGE, "srange") \ - _U(JUMP_PRI, "jump.pri") \ - _G(SWITCH, "switch") \ - _G(CASETBL, "casetbl") \ - _G(SWAP_PRI, "swap.pri") \ - _G(SWAP_ALT, "swap.alt") \ - _G(PUSH_ADR, "push.adr") \ - _G(NOP, "nop") \ - _G(SYSREQ_N, "sysreq.n") \ - _U(SYMTAG, "symtag") \ - _G(BREAK, "break") \ - _G(PUSH2_C, "push2.c") \ - _G(PUSH2, "push2") \ - _G(PUSH2_S, "push2.s") \ - _G(PUSH2_ADR, "push2.adr") \ - _G(PUSH3_C, "push3.c") \ - _G(PUSH3, "push3") \ - _G(PUSH3_S, "push3.s") \ - _G(PUSH3_ADR, "push3.adr") \ - _G(PUSH4_C, "push4.c") \ - _G(PUSH4, "push4") \ - _G(PUSH4_S, "push4.s") \ - _G(PUSH4_ADR, "push4.adr") \ - _G(PUSH5_C, "push5.c") \ - _G(PUSH5, "push5") \ - _G(PUSH5_S, "push5.s") \ - _G(PUSH5_ADR, "push5.adr") \ - _G(LOAD_BOTH, "load.both") \ - _G(LOAD_S_BOTH, "load.s.both") \ - _G(CONST, "const") \ - _G(CONST_S, "const.s") \ - _U(SYSREQ_D, "sysreq.d") \ +#define OPCODE_LIST(_G, _U) \ + _G(NONE, "none", 1) \ + _G(LOAD_PRI, "load.pri", 2) \ + _G(LOAD_ALT, "load.alt", 2) \ + _G(LOAD_S_PRI, "load.s.pri", 2) \ + _G(LOAD_S_ALT, "load.s.alt", 2) \ + _U(LREF_PRI, "lref.pri") \ + _U(LREF_ALT, "lref.alt") \ + _G(LREF_S_PRI, "lref.s.pri", 2) \ + _G(LREF_S_ALT, "lref.s.alt", 2) \ + _G(LOAD_I, "load.i", 1) \ + _G(LODB_I, "lodb.i", 2) \ + _G(CONST_PRI, "const.pri", 2) \ + _G(CONST_ALT, "const.alt", 2) \ + _G(ADDR_PRI, "addr.pri", 2) \ + _G(ADDR_ALT, "addr.alt", 2) \ + _G(STOR_PRI, "stor.pri", 2) \ + _G(STOR_ALT, "stor.alt", 2) \ + _G(STOR_S_PRI, "stor.s.pri", 2) \ + _G(STOR_S_ALT, "stor.s.alt", 2) \ + _U(SREF_PRI, "sref.pri") \ + _U(SREF_ALT, "sref.alt") \ + _G(SREF_S_PRI, "sref.s.pri", 2) \ + _G(SREF_S_ALT, "sref.s.alt", 2) \ + _G(STOR_I, "stor.i", 1) \ + _G(STRB_I, "strb.i", 2) \ + _G(LIDX, "lidx", 1) \ + _U(LIDX_B, "lidx.b") \ + _G(IDXADDR, "idxaddr", 1) \ + _U(IDXADDR_B, "idxaddr.b") \ + _U(ALIGN_PRI, "align.pri") \ + _U(ALIGN_ALT, "align.alt") \ + _U(LCTRL, "lctrl") \ + _U(SCTRL, "sctrl") \ + _G(MOVE_PRI, "move.pri", 1) \ + _G(MOVE_ALT, "move.alt", 1) \ + _G(XCHG, "xchg", 1) \ + _G(PUSH_PRI, "push.pri", 1) \ + _G(PUSH_ALT, "push.alt", 1) \ + _U(PUSH_R, "push.r") \ + _G(PUSH_C, "push.c", 2) \ + _G(PUSH, "push", 2) \ + _G(PUSH_S, "push.s", 2) \ + _G(POP_PRI, "pop.pri", 1) \ + _G(POP_ALT, "pop.alt", 1) \ + _G(STACK, "stack", 2) \ + _G(HEAP, "heap", 2) \ + _G(PROC, "proc", 1) \ + _U(RET, "ret") \ + _G(RETN, "retn", 1) \ + _G(CALL, "call", 2) \ + _U(CALL_PRI, "call.pri") \ + _G(JUMP, "jump", 2) \ + _U(JREL, "jrel") \ + _G(JZER, "jzer", 2) \ + _G(JNZ, "jnz", 2) \ + _G(JEQ, "jeq", 2) \ + _G(JNEQ, "jneq", 2) \ + _U(JLESS, "jsless") \ + _U(JLEQ, "jleq") \ + _U(JGRTR, "jgrtr") \ + _U(JGEQ, "jgeq") \ + _G(JSLESS, "jsless", 2) \ + _G(JSLEQ, "jsleq", 2) \ + _G(JSGRTR, "jsgrtr", 2) \ + _G(JSGEQ, "jsgeq", 2) \ + _G(SHL, "shl", 1) \ + _G(SHR, "shr", 1) \ + _G(SSHR, "sshr", 1) \ + _G(SHL_C_PRI, "shl.c.pri", 2) \ + _G(SHL_C_ALT, "shl.c.alt", 2) \ + _U(SHR_C_PRI, "shr.c.pri") \ + _U(SHR_C_ALT, "shr.c.alt") \ + _G(SMUL, "smul", 1) \ + _G(SDIV, "sdiv", 1) \ + _G(SDIV_ALT, "sdiv.alt", 1) \ + _U(UMUL, "umul") \ + _U(UDIV, "udiv") \ + _U(UDIV_ALT, "udiv.alt") \ + _G(ADD, "add", 1) \ + _G(SUB, "sub", 1) \ + _G(SUB_ALT, "sub.alt", 1) \ + _G(AND, "and", 1) \ + _G(OR, "or", 1) \ + _G(XOR, "xor", 1) \ + _G(NOT, "not", 1) \ + _G(NEG, "neg", 1) \ + _G(INVERT, "invert", 1) \ + _G(ADD_C, "add.c", 2) \ + _G(SMUL_C, "smul.c", 2) \ + _G(ZERO_PRI, "zero.pri", 1) \ + _G(ZERO_ALT, "zero.alt", 1) \ + _G(ZERO, "zero", 2) \ + _G(ZERO_S, "zero.s", 2) \ + _U(SIGN_PRI, "sign.pri") \ + _U(SIGN_ALT, "sign.alt") \ + _G(EQ, "eq", 1) \ + _G(NEQ, "neq", 1) \ + _U(LESS, "less") \ + _U(LEQ, "leq") \ + _U(GRTR, "grtr") \ + _U(GEQ, "geq") \ + _G(SLESS, "sless", 1) \ + _G(SLEQ, "sleq", 1) \ + _G(SGRTR, "sgrtr", 1) \ + _G(SGEQ, "sgeq", 1) \ + _G(EQ_C_PRI, "eq.c.pri", 2) \ + _G(EQ_C_ALT, "eq.c.alt", 2) \ + _G(INC_PRI, "inc.pri", 1) \ + _G(INC_ALT, "inc.alt", 1) \ + _G(INC, "inc", 2) \ + _G(INC_S, "inc.s", 2) \ + _G(INC_I, "inc.i", 1) \ + _G(DEC_PRI, "dec.pri", 1) \ + _G(DEC_ALT, "dec.alt", 1) \ + _G(DEC, "dec", 2) \ + _G(DEC_S, "dec.s", 2) \ + _G(DEC_I, "dec.i", 1) \ + _G(MOVS, "movs", 2) \ + _U(CMPS, "cmps") \ + _G(FILL, "fill", 2) \ + _G(HALT, "halt", 2) \ + _G(BOUNDS, "bounds", 2) \ + _U(SYSREQ_PRI, "sysreq.pri") \ + _G(SYSREQ_C, "sysreq.c", 2) \ + _U(FILE, "file") \ + _U(LINE, "line") \ + _U(SYMBOL, "symbol") \ + _U(SRANGE, "srange") \ + _U(JUMP_PRI, "jump.pri") \ + _G(SWITCH, "switch", 2) \ + _G(CASETBL, "casetbl", -1) \ + _G(SWAP_PRI, "swap.pri", 1) \ + _G(SWAP_ALT, "swap.alt", 1) \ + _G(PUSH_ADR, "push.adr", 2) \ + _G(NOP, "nop", 1) \ + _G(SYSREQ_N, "sysreq.n", 3) \ + _U(SYMTAG, "symtag") \ + _G(BREAK, "break", 1) \ + _G(PUSH2_C, "push2.c", 3) \ + _G(PUSH2, "push2", 3) \ + _G(PUSH2_S, "push2.s", 3) \ + _G(PUSH2_ADR, "push2.adr", 3) \ + _G(PUSH3_C, "push3.c", 4) \ + _G(PUSH3, "push3", 4) \ + _G(PUSH3_S, "push3.s", 4) \ + _G(PUSH3_ADR, "push3.adr", 4) \ + _G(PUSH4_C, "push4.c", 5) \ + _G(PUSH4, "push4", 5) \ + _G(PUSH4_S, "push4.s", 5) \ + _G(PUSH4_ADR, "push4.adr", 5) \ + _G(PUSH5_C, "push5.c", 6) \ + _G(PUSH5, "push5", 6) \ + _G(PUSH5_S, "push5.s", 6) \ + _G(PUSH5_ADR, "push5.adr", 6) \ + _G(LOAD_BOTH, "load.both", 3) \ + _G(LOAD_S_BOTH, "load.s.both", 3) \ + _G(CONST, "const", 3) \ + _G(CONST_S, "const.s", 3) \ + _U(SYSREQ_D, "sysreq.d") \ _U(SYSREQ_ND, "sysreq.nd") \ - _G(TRACKER_PUSH_C, "trk.push.c") \ - _G(TRACKER_POP_SETHEAP,"trk.pop") \ - _G(GENARRAY, "genarray") \ - _G(GENARRAY_Z, "genarray.z") \ - _G(STRADJUST_PRI, "stradjust.pri") \ - _U(STKADJUST, "stackadjust") \ - _G(ENDPROC, "endproc") \ - _U(LDGFN_PRI, "ldgfn.pri") \ - _G(REBASE, "rebase") \ + _G(TRACKER_PUSH_C, "trk.push.c", 2) \ + _G(TRACKER_POP_SETHEAP,"trk.pop", 1) \ + _G(GENARRAY, "genarray", 2) \ + _G(GENARRAY_Z, "genarray.z", 2) \ + _G(STRADJUST_PRI, "stradjust.pri", 1)\ + _U(STKADJUST, "stackadjust") \ + _G(ENDPROC, "endproc", 1) \ + _U(LDGFN_PRI, "ldgfn.pri") \ + _G(REBASE, "rebase", 4) \ /* Opcodes below this are pseudo-opcodes and are not part of the ABI */ \ - _G(FABS, "fabs") \ - _G(FLOAT, "float") \ - _G(FLOATADD, "float.add") \ - _G(FLOATSUB, "float.sub") \ - _G(FLOATMUL, "float.mul") \ - _G(FLOATDIV, "float.div") \ - _G(RND_TO_NEAREST, "round") \ - _G(RND_TO_FLOOR, "floor") \ - _G(RND_TO_CEIL, "ceil") \ - _G(RND_TO_ZERO, "rndtozero") \ - _G(FLOATCMP, "float.cmp") \ - _G(FLOAT_GT, "float.gt") \ - _G(FLOAT_GE, "float.ge") \ - _G(FLOAT_LT, "float.lt") \ - _G(FLOAT_LE, "float.le") \ - _G(FLOAT_NE, "float.ne") \ - _G(FLOAT_EQ, "float.eq") \ - _G(FLOAT_NOT, "float.not") + _U(FIRST_FAKE, "firstfake") \ + _G(FABS, "fabs", 1) \ + _G(FLOAT, "float", 1) \ + _G(FLOATADD, "float.add", 1) \ + _G(FLOATSUB, "float.sub", 1) \ + _G(FLOATMUL, "float.mul", 1) \ + _G(FLOATDIV, "float.div", 1) \ + _G(RND_TO_NEAREST, "round", 1) \ + _G(RND_TO_FLOOR, "floor", 1) \ + _G(RND_TO_CEIL, "ceil", 1) \ + _G(RND_TO_ZERO, "rndtozero", 1) \ + _G(FLOATCMP, "float.cmp", 1) \ + _G(FLOAT_GT, "float.gt", 1) \ + _G(FLOAT_GE, "float.ge", 1) \ + _G(FLOAT_LT, "float.lt", 1) \ + _G(FLOAT_LE, "float.le", 1) \ + _G(FLOAT_NE, "float.ne", 1) \ + _G(FLOAT_EQ, "float.eq", 1) \ + _G(FLOAT_NOT, "float.not", 1) enum OPCODE { -#define _G(op, text) OP_##op, +#define _G(op, text, cells) OP_##op, #define _U(op, text) OP_UNGEN_##op, OPCODE_LIST(_G, _U) #undef _G diff --git a/third_party/amtl b/third_party/amtl index b35efc84d..6d010aa92 160000 --- a/third_party/amtl +++ b/third_party/amtl @@ -1 +1 @@ -Subproject commit b35efc84d3c2a77609a9f79b4fc136e0fcb3b65b +Subproject commit 6d010aa9275492bc7df18d47338c7b8dbfa2a2fc diff --git a/vm/AMBuilder b/vm/AMBuilder index bd424a6df..05319e5f9 100644 --- a/vm/AMBuilder +++ b/vm/AMBuilder @@ -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', diff --git a/vm/bitset.h b/vm/bitset.h new file mode 100644 index 000000000..bfa9e88cd --- /dev/null +++ b/vm/bitset.h @@ -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 +#include +#include +#include +#include + +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& 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 words_; + ke::Maybe max_bits_; +}; + +} // namespace sp diff --git a/vm/control-flow.cpp b/vm/control-flow.cpp new file mode 100644 index 000000000..8cd41ac81 --- /dev/null +++ b/vm/control-flow.cpp @@ -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 +ControlFlowGraph::newBlock(const uint8_t* start) +{ + ke::RefPtr 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(block->start()), + reinterpret_cast(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 diff --git a/vm/control-flow.h b/vm/control-flow.h new file mode 100644 index 000000000..06e228367 --- /dev/null +++ b/vm/control-flow.h @@ -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 +#include +#include +#include "plugin-runtime.h" + +namespace sp { + +class ControlFlowGraph; + +enum class BlockEnd { + Unknown, + Insn, + Jump +}; + +class Block : public ke::Refcounted +{ + 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>& predecessors() const { + return predecessors_; + } + const ke::Vector>& 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> predecessors_; + ke::Vector> 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 +{ + public: + explicit ControlFlowGraph(PluginRuntime* rt, const uint8_t* start_offset); + ~ControlFlowGraph(); + + ke::RefPtr entry() const { + return entry_; + } + + ke::RefPtr 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 entry_; + ke::Vector> blocks_; + uint32_t epoch_; +}; + +} // namespace sp + +#endif // _include_sourcepawn_vm_control_flow_h_ diff --git a/vm/graph-builder.cpp b/vm/graph-builder.cpp new file mode 100644 index 000000000..f2bd00c1e --- /dev/null +++ b/vm/graph-builder.cpp @@ -0,0 +1,464 @@ +// 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 "graph-builder.h" +#include "plugin-runtime.h" +#include +#include "opcodes.h" + +namespace sp { + +using namespace ke; + +GraphBuilder::GraphBuilder(PluginRuntime* rt, uint32_t start_offset) + : rt_(rt), + start_offset_(start_offset), + error_code_(0) +{ + start_at_ = rt_->code().bytes + start_offset_; + stop_at_ = rt_->code().bytes + rt_->code().length; +} + +RefPtr +GraphBuilder::build() +{ + // Prescan to figure out what offsets are instructions and/or jump targets. + if (!prescan()) + return nullptr; + + if (!scan()) + return nullptr; + + assert(graph_); + assert(!error_code_); + return graph_; +} + +// Scan the instruction stream for control-flow opcodes. For each such opcode, +// we push its target onto a stack so it can be visited again later. +bool +GraphBuilder::scan() +{ + graph_ = new ControlFlowGraph(rt_, start_at_); + current_ = graph_->entry(); + + block_map_.init(16); + + // Set cip, start past the mandatory OP_PROC. + cip_ = start_at_; + assert(peekOp() == OP_PROC); + cip_ += sizeof(cell_t); + + // Begin an epoch to track which blocks have been visited. + graph_->newEpoch(); + + for (;;) { + FlowState state = scanFlow(); + if (state == FlowState::Error) + return false; + + // If there's no more ops to read in the stream, we consider this the same + // as ending control-flow, even though it's illegal. We check for these + // blocks in a separate pass. + if (state == FlowState::Ended || !more()) { + assert(!current_); + + // If there's no more blocks to process, we're done. + if (work_queue_.empty()) + return true; + + current_ = work_queue_.popCopy(); + assert(!current_->ended()); + + // Set the cip_ accordingly and proceed. + cip_ = current_->start(); + continue; + } + + assert(state == FlowState::Continue); + assert(current_); + + // Skip past the opcode. + cip_ = NextInstruction(cip_); + } +} + +static inline bool +IsControlOpcode(OPCODE op) +{ + switch (op) { + case OP_RETN: + case OP_JUMP: + case OP_JZER: + case OP_JNZ: + case OP_JEQ: + case OP_JNEQ: + case OP_JSLESS: + case OP_JSLEQ: + case OP_JSGRTR: + case OP_JSGEQ: + case OP_SWITCH: + return true; + default: + return false; + } +} + +auto +GraphBuilder::scanFlow() -> FlowState +{ + uint32_t cell_number = getCellNumber(cip_); + assert(insn_bitmap_.test(cell_number)); + + // Does this opcode mark the start of a new block? Note that we skip this if + // the current block starts at this cip. This could happen, for example, with + // a JUMP opcode that loops back to itself. While this is totally pointless, + // it is not illegal. + if (jump_targets_.test(cell_number) && current_->start() != cip_) { + RefPtr block = getOrAddBlock(cip_); + current_->endWithJump(cip_, block); + current_ = nullptr; + return FlowState::Ended; + } + + OPCODE op = peekOp(); + if (!IsControlOpcode(op)) { + // This opcode does not affect control flow, so return. + return FlowState::Continue; + } + + // Save a pointer to the start of the instruction. + const uint8_t* insn = cip_; + cip_ += sizeof(cell_t); + + switch (op) { + case OP_RETN: + current_->end(insn, BlockEnd::Insn); + current_ = nullptr; + return FlowState::Ended; + + case OP_JUMP: + case OP_JZER: + case OP_JNZ: + case OP_JEQ: + case OP_JNEQ: + case OP_JSLESS: + case OP_JSLEQ: + case OP_JSGRTR: + case OP_JSGEQ: + case OP_SWITCH: + { + cell_t target_pos = read(); + const uint8_t* target = rt_->code().bytes + target_pos; + uint32_t target_cell_number = getCellNumber(target); + + // This will check that (a) we target a valid instruction, and (b) that + // the instruction is within method bounds. + if (!insn_bitmap_.test(target_cell_number)) { + error(SP_ERROR_INSTRUCTION_PARAM); + return FlowState::Error; + } + assert(op == OP_SWITCH || jump_targets_.test(target_cell_number)); + + // If this is a switch, we need specialized logic. + if (op == OP_SWITCH) { + // Re-position cip_ to be the casetable location. + cip_ = target; + return scanSwitchFlow(insn); + } + + RefPtr target_block = getOrAddBlock(target); + enqueueBlock(target_block); + + // If this is an unconditional jump, there is only one target, so end. + if (op == OP_JUMP) { + current_->addTarget(target_block); + current_->end(insn, BlockEnd::Insn); + current_ = nullptr; + return FlowState::Ended; + } + + // If we've reached the end of the stream, the instruction is not valid, + // because the default case proceeds to the next instruction. + if (!more()) { + error(SP_ERROR_INVALID_INSTRUCTION); + return FlowState::Error; + } + + // Acquire a block for the next cip, and implicitly link it to this + // block. The first successor is if the jump is not taken; the second + // successor is if the jump is taken. + RefPtr next_block = getOrAddBlock(cip_); + current_->addTarget(next_block); + current_->addTarget(target_block); + current_->end(insn, BlockEnd::Insn); + current_ = nullptr; + + enqueueBlock(next_block); + return FlowState::Ended; + } + + default: + assert(false); + } + return FlowState::Ended; +} + +auto +GraphBuilder::scanSwitchFlow(const uint8_t* insn) -> FlowState +{ + if (!insn_bitmap_.test(getCellNumber(cip_))) { + error(SP_ERROR_INSTRUCTION_PARAM); + return FlowState::Error; + } + if (readOp() != OP_CASETBL) { + error(SP_ERROR_INSTRUCTION_PARAM); + return FlowState::Error; + } + + cell_t ncases = read(); + + // Add the default case. + Vector cases; + cases.append(read()); + + // Add all cases. + for (cell_t i = 0; i < ncases; i++) { + read(); + cases.append(read()); + } + + // Process each case. + for (cell_t target_pos : cases) { + const uint8_t* target = rt_->code().bytes + target_pos; + if (!insn_bitmap_.test(getCellNumber(target))) { + error(SP_ERROR_INSTRUCTION_PARAM); + return FlowState::Error; + } + assert(jump_targets_.test(getCellNumber(target))); + + RefPtr target_block = getOrAddBlock(target); + enqueueBlock(target_block); + + current_->addTarget(target_block); + } + + current_->end(insn, BlockEnd::Insn); + current_ = nullptr; + return FlowState::Ended; +} + +ke::RefPtr +GraphBuilder::getOrAddBlock(const uint8_t* cip) +{ + // We use a quick existence test before diving into the hash table. + uint32_t cell_number = getCellNumber(cip); + if (!block_bitmap_.test(cell_number)) { + RefPtr block = graph_->newBlock(cip); + + BlockMap::Insert p = block_map_.findForAdd(cip); + assert(!p.found()); + + block_map_.add(p, cip, block); + block_bitmap_.set(cell_number); + return block; + } + + BlockMap::Result r = block_map_.find(cip); + assert(r.found()); + return r->value; +} + +void +GraphBuilder::enqueueBlock(Block* block) +{ + if (block->visited()) + return; + + work_queue_.append(block); + block->setVisited(); +} + +// This pass generates the following information: +// (1) The code stream bounds, by updating stop_at_. +// (2) A bitmap of which cells are valid instructions (insn_bitmap_). +// (3) A bitmap of which cells are the target of jumps (jump_targets_). +// +// It also verifies that jump targets occur within a reasonable boudnary and +// are cell-aligned. We will harden that verification in the full scan phase. +bool +GraphBuilder::prescan() +{ + if (!IsAligned(start_offset_, sizeof(cell_t))) + return error(SP_ERROR_INVALID_ADDRESS); + + cip_ = start_at_; + if (!more() || read() != OP_PROC) + return error(SP_ERROR_INVALID_INSTRUCTION); + + // Allocate the jump bitmap. + size_t max_cells = (stop_at_ - start_at_) / sizeof(cell_t); + insn_bitmap_ = BitSet(max_cells); + jump_targets_ = BitSet(max_cells); + + while (more()) { + OPCODE op = peekOp(); + if (op == OP_PROC || op == OP_ENDPROC) + break; + + if (op <= 0 || op >= OP_UNGEN_FIRST_FAKE) + return error(SP_ERROR_INVALID_INSTRUCTION); + + // Mark the bitmap. + insn_bitmap_.set(getCellNumber(cip_)); + + // Skip past the opcode. + cip_ += sizeof(cell_t); + + // Deduce parameter count. + int opcode_params; + if (op == OP_CASETBL) { + if (!more()) + return error(SP_ERROR_INVALID_INSTRUCTION); + cell_t ncases = read(); + if (ncases > (INT_MAX - 1) / 2) + return error(SP_ERROR_INVALID_INSTRUCTION); + opcode_params = (ncases * 2) + 1; + } else { + opcode_params = kOpcodeSizes[op] - 1; + } + assert(opcode_params >= 0); + + // Make sure the opcode can be read. + if (cip_ + (opcode_params * sizeof(cell_t)) > stop_at_) + return error(SP_ERROR_INVALID_INSTRUCTION); + + // If this is a control opcode, we need to markup any jump targets. + if (IsControlOpcode(op) && op != OP_RETN) { + // All jump instructions, and SWITCH, have the target as an immediate + // value. + if (!prescan_jump_target(op, peek())) + return false; + } else if (op == OP_CASETBL) { + if (!prescan_casetable(cip_, opcode_params)) + return false; + } + + // Advance to the next instruction. + cip_ += opcode_params * sizeof(cell_t); + } + + // Update the stop-at point. + stop_at_ = cip_; + return true; +} + +bool +GraphBuilder::prescan_jump_target(OPCODE op, cell_t target) +{ + if (target < 0) + return error(SP_ERROR_INSTRUCTION_PARAM); + + if (!ke::IsAligned(size_t(target), sizeof(cell_t))) + return error(SP_ERROR_INSTRUCTION_PARAM); + + // Note that stop_at_ is still the end of the code section, so this is a + // valid check. If we ever pre-fill stop_at_ with the correct value, this + // will still be valid, just redundant. + if (size_t(target) >= size_t(stop_at_ - rt_->code().bytes)) + return error(SP_ERROR_INSTRUCTION_PARAM); + + // Note that the target must not be equal to start_at_, since jumping to + // the OP_PROC is illegal (this would push infinite stack frames or + // something). + const uint8_t* cip = rt_->code().bytes + target; + if (cip <= start_at_) + return error(SP_ERROR_INSTRUCTION_PARAM); + + // Since OP_SWITCH points to a CASETBL, not an actual jump target, ignore it + // lest we create a pointless block. + if (op != OP_SWITCH) + jump_targets_.set(getCellNumber(cip)); + return true; +} + +bool +GraphBuilder::prescan_casetable(const uint8_t* pos, cell_t size) +{ + const uint8_t* end = pos + (size * sizeof(cell_t)); + // OP_CASETBL: + // ncases + // default_offset + // [value, offset] + // + // |pos| is aligned to |default_offset|. + cell_t default_offset = *reinterpret_cast(pos); + if (!prescan_jump_target(OP_JUMP, default_offset)) + return false; + pos += sizeof(cell_t); + + while (pos < end) { + // Skip the value. + pos += sizeof(cell_t); + cell_t target = *reinterpret_cast(pos); + if (!prescan_jump_target(OP_JUMP, target)) + return false; + pos += sizeof(cell_t); + } + return true; +} + +bool +GraphBuilder::cleanup() +{ + assert(work_queue_.empty()); + + graph_->newEpoch(); + + // Find all reachable blocks, from the entrypoint. + graph_->entry()->setVisited(); + work_queue_.append(graph_->entry()); + while (!work_queue_.empty()) { + ke::RefPtr block = work_queue_.popCopy(); + assert(block->visited()); + + if (!block->ended()) { + // This is a serious condition since the function can proceed into + // uninitialized memory. + return error(SP_ERROR_INVALID_INSTRUCTION); + } + + for (const auto& successor : block->successors()) { + if (successor->visited()) + continue; + work_queue_.append(successor); + successor->setVisited(); + } + } + +#if !defined(NDEBUG) + // It should not be possible to have dangling blocks. + for (BlockMap::iterator iter = block_map_.iter(); !iter.empty(); iter.next()) + assert(iter->value->visited()); +#endif + return true; +} + +bool +GraphBuilder::error(int code) +{ + error_code_ = code; + return false; +} + +} // namespace sp diff --git a/vm/graph-builder.h b/vm/graph-builder.h new file mode 100644 index 000000000..a368c1bb3 --- /dev/null +++ b/vm/graph-builder.h @@ -0,0 +1,123 @@ +// 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_graph_builder_h_ +#define _include_sourcepawn_vm_graph_builder_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "control-flow.h" +#include "bitset.h" + +namespace sp { + +class PluginRuntime; + +class GraphBuilder +{ + public: + GraphBuilder(PluginRuntime* rt, uint32_t start_offset); + + ke::RefPtr build(); + + int error_code() const { + return error_code_; + } + + private: + bool prescan(); + bool prescan_jump_target(OPCODE op, cell_t target); + bool prescan_casetable(const uint8_t* pos, cell_t size); + bool error(int code); + + enum class FlowState { + // Continue processing opcodes in the current block. + Continue, + // Do not process any more opcodes in the current block. + Ended, + // An error occurred, abort. + Error + }; + + bool scan(); + FlowState scanFlow(); + FlowState scanSwitchFlow(const uint8_t* insn); + ke::RefPtr getOrAddBlock(const uint8_t* cip); + void enqueueBlock(Block* block); + + bool cleanup(); + + bool more() { + return cip_ + sizeof(cell_t) <= stop_at_; + } + cell_t peek() { + assert(more()); + return *reinterpret_cast(cip_); + } + cell_t read() { + cell_t value = peek(); + cip_ += sizeof(cell_t); + return value; + } + OPCODE peekOp() { + return (OPCODE)peek(); + } + OPCODE readOp() { + return (OPCODE)read(); + } + + // We use bitmaps to efficiently to track true/false information about + // addresses. To do this, we convert each instruction addresses into + // a cell # from the start of the function. This effectively compresses + // each address by 4 * 32, so a 256KB method has 65,536 instructions, which + // needs only 8,192 bytes to encode a bitmap. + uint32_t getCellNumber(const uint8_t* cip) { + assert(cip >= start_at_); + return static_cast((cip - start_at_) / sizeof(cell_t)); + } + + private: + PluginRuntime* rt_; + uint32_t start_offset_; + ke::RefPtr graph_; + int error_code_; + + // Reader state. + const uint8_t* start_at_; + const uint8_t* stop_at_; + const uint8_t* cip_; + + // Computed by prescan(). + BitSet insn_bitmap_; + BitSet jump_targets_; + + // Block building. + ke::RefPtr current_; + ke::Vector> work_queue_; + + typedef ke::HashMap, + ke::PointerPolicy> BlockMap; + BlockMap block_map_; + BitSet block_bitmap_; +}; + +} // namespace sp + +#endif // _include_sourcepawn_vm_graph_builder_h_ diff --git a/vm/jit.cpp b/vm/jit.cpp index c1b7fc142..8b4a4f35f 100644 --- a/vm/jit.cpp +++ b/vm/jit.cpp @@ -82,7 +82,7 @@ CompilerBase::emit() rt_->Name(), rt_->image()->LookupFunction(pcode_start_)); - SpewOpcode(rt_, code_start_, reader.cip()); + SpewOpcode(stdout, rt_, code_start_, reader.cip()); #endif const cell_t* codeseg = reinterpret_cast(rt_->code().bytes); diff --git a/vm/method-info.cpp b/vm/method-info.cpp index 62dfb8754..ba8869a43 100644 --- a/vm/method-info.cpp +++ b/vm/method-info.cpp @@ -14,6 +14,7 @@ #include "compiled-function.h" #include "method-info.h" #include "method-verifier.h" +#include "graph-builder.h" namespace sp { diff --git a/vm/opcodes.cpp b/vm/opcodes.cpp index 6654bbbb4..7ef928b02 100644 --- a/vm/opcodes.cpp +++ b/vm/opcodes.cpp @@ -34,25 +34,44 @@ using namespace sp; using namespace SourcePawn; const char* OpcodeNames[] = { -#define _(op, text) text, - OPCODE_LIST(_, _) -#undef _ +#define G(op, text, cells) text, +#define U(op, text) text, + OPCODE_LIST(G, U) +#undef U +#undef G NULL }; -#ifdef JIT_SPEW +namespace sp { + +const int kOpcodeSizes[] = { +#define G(op, text, cells) cells, +#define U(op, text) 0, + OPCODE_LIST(G, U) +#undef U +#undef G +}; + +int +GetCaseTableSize(const uint8_t* cip) +{ + assert((OPCODE)*reinterpret_cast(cip) == OP_CASETBL); + cip += sizeof(cell_t); + return (*reinterpret_cast(cip) * 2) + 1; +} + void -SourcePawn::SpewOpcode(PluginRuntime* runtime, const cell_t* start, const cell_t* cip) +SpewOpcode(FILE* fp, PluginRuntime* runtime, const cell_t* start, const cell_t* cip) { - fprintf(stdout, " [%05d:%04d]", cip - (cell_t*)runtime->code().bytes, cip - start); + fprintf(fp, " [%05d:%04d]", int(cip - (cell_t*)runtime->code().bytes), int(cip - start)); if (*cip >= OPCODES_LAST) { - fprintf(stdout, " unknown-opcode\n"); + fprintf(fp, " unknown-opcode\n"); return; } OPCODE op = (OPCODE)*cip; - fprintf(stdout, " %s ", OpcodeNames[op]); + fprintf(fp, " %s ", OpcodeNames[op]); switch (op) { case OP_PUSH_C: @@ -71,7 +90,7 @@ SourcePawn::SpewOpcode(PluginRuntime* runtime, const cell_t* start, const cell_t case OP_GENARRAY_Z: case OP_CONST_PRI: case OP_CONST_ALT: - fprintf(stdout, "%d", cip[1]); + fprintf(fp, "%d", cip[1]); break; case OP_JUMP: @@ -83,7 +102,7 @@ SourcePawn::SpewOpcode(PluginRuntime* runtime, const cell_t* start, const cell_t case OP_JSGRTR: case OP_JSGEQ: case OP_JSLEQ: - fprintf(stdout, "%05d:%04d", + fprintf(fp, "%05d:%04d", cip[1] / 4, ((cell_t*)runtime->code().bytes + cip[1] / 4) - start); break; @@ -93,11 +112,11 @@ SourcePawn::SpewOpcode(PluginRuntime* runtime, const cell_t* start, const cell_t { uint32_t index = cip[1]; if (index < runtime->image()->NumNatives()) - fprintf(stdout, "%s", runtime->GetNative(index)->name); + fprintf(fp, "%s", runtime->GetNative(index)->name); if (op == OP_SYSREQ_N) - fprintf(stdout, " ; (%d args, index %d)", cip[2], index); + fprintf(fp, " ; (%d args, index %d)", cip[2], index); else - fprintf(stdout, " ; (index %d)", index); + fprintf(fp, " ; (index %d)", index); break; } @@ -105,35 +124,35 @@ SourcePawn::SpewOpcode(PluginRuntime* runtime, const cell_t* start, const cell_t case OP_PUSH2: case OP_PUSH2_S: case OP_PUSH2_ADR: - fprintf(stdout, "%d, %d", cip[1], cip[2]); + fprintf(fp, "%d, %d", cip[1], cip[2]); break; case OP_PUSH3_C: case OP_PUSH3: case OP_PUSH3_S: case OP_PUSH3_ADR: - fprintf(stdout, "%d, %d, %d", cip[1], cip[2], cip[3]); + fprintf(fp, "%d, %d, %d", cip[1], cip[2], cip[3]); break; case OP_PUSH4_C: case OP_PUSH4: case OP_PUSH4_S: case OP_PUSH4_ADR: - fprintf(stdout, "%d, %d, %d, %d", cip[1], cip[2], cip[3], cip[4]); + fprintf(fp, "%d, %d, %d, %d", cip[1], cip[2], cip[3], cip[4]); break; case OP_PUSH5_C: case OP_PUSH5: case OP_PUSH5_S: case OP_PUSH5_ADR: - fprintf(stdout, "%d, %d, %d, %d, %d", cip[1], cip[2], cip[3], cip[4], cip[5]); + fprintf(fp, "%d, %d, %d, %d, %d", cip[1], cip[2], cip[3], cip[4], cip[5]); break; default: break; } - fprintf(stdout, "\n"); + fprintf(fp, "\n"); } -#endif +} // namespace sp diff --git a/vm/opcodes.h b/vm/opcodes.h index 7997ab589..d55894174 100644 --- a/vm/opcodes.h +++ b/vm/opcodes.h @@ -36,10 +36,23 @@ #include #include "plugin-runtime.h" -namespace SourcePawn { -#ifdef JIT_SPEW - void SpewOpcode(sp::PluginRuntime* runtime, const cell_t* start, const cell_t* cip); -#endif +namespace sp { + +void SpewOpcode(FILE* fp, sp::PluginRuntime* runtime, const cell_t* start, const cell_t* cip); + +// These count opcodes in # of cells, not bytes. +int GetCaseTableSize(const uint8_t* cip); +extern const int kOpcodeSizes[]; + +static inline const uint8_t* +NextInstruction(const uint8_t* cip) +{ + OPCODE op = (OPCODE)*reinterpret_cast(cip); + if (op == OP_CASETBL) + return cip + GetCaseTableSize(cip) * sizeof(cell_t); + return cip + kOpcodeSizes[op] * sizeof(cell_t); } +} // namespace sp + #endif //_INCLUDE_SOURCEPAWN_JIT_X86_OPCODES_H_