Genoil
10 years ago
194 changed files with 9381 additions and 2704 deletions
@ -0,0 +1,16 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ..) |
|||
include_directories(${LEVELDB_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE abi) |
|||
|
|||
add_executable(${EXECUTABLE} ${SRC_LIST}) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ethereum) |
|||
|
|||
install( TARGETS ${EXECUTABLE} DESTINATION bin) |
|||
|
@ -0,0 +1,769 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file main.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
* RLP tool. |
|||
*/ |
|||
#include <fstream> |
|||
#include <iostream> |
|||
#include <boost/regex.hpp> |
|||
#include <boost/algorithm/string.hpp> |
|||
#include "../test/JsonSpiritHeaders.h" |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethereum/Client.h> |
|||
using namespace std; |
|||
using namespace dev; |
|||
namespace js = json_spirit; |
|||
|
|||
void help() |
|||
{ |
|||
cout |
|||
<< "Usage abi enc <method_name> (<arg1>, (<arg2>, ... ))" << endl |
|||
<< " abi enc -a <abi.json> <method_name> (<arg1>, (<arg2>, ... ))" << endl |
|||
<< " abi dec -a <abi.json> [ <signature> | <unique_method_name> ]" << endl |
|||
<< "Options:" << endl |
|||
<< " -a,--abi-file <filename> Specify the JSON ABI file." << endl |
|||
<< " -h,--help Print this help message and exit." << endl |
|||
<< " -V,--version Show the version and exit." << endl |
|||
<< "Input options:" << endl |
|||
<< " -f,--format-prefix Require all input formats to be prefixed e.g. 0x for hex, . for decimal, @ for binary." << endl |
|||
<< " -F,--no-format-prefix Require no input format to be prefixed." << endl |
|||
<< " -t,--typing Require all arguments to be typed e.g. b32: (bytes32), u64: (uint64), b[]: (byte[]), i: (int256)." << endl |
|||
<< " -T,--no-typing Require no arguments to be typed." << endl |
|||
<< "Output options:" << endl |
|||
<< " -i,--index <n> Output only the nth (counting from 0) return value." << endl |
|||
<< " -d,--decimal All data should be displayed as decimal." << endl |
|||
<< " -x,--hex Display all data as hex." << endl |
|||
<< " -b,--binary Display all data as binary." << endl |
|||
<< " -p,--prefix Prefix by a base identifier." << endl |
|||
; |
|||
exit(0); |
|||
} |
|||
|
|||
void version() |
|||
{ |
|||
cout << "abi version " << dev::Version << endl; |
|||
exit(0); |
|||
} |
|||
|
|||
enum class Mode |
|||
{ |
|||
Encode, |
|||
Decode |
|||
}; |
|||
|
|||
enum class Encoding |
|||
{ |
|||
Auto, |
|||
Decimal, |
|||
Hex, |
|||
Binary, |
|||
}; |
|||
|
|||
enum class Tristate |
|||
{ |
|||
False = false, |
|||
True = true, |
|||
Mu |
|||
}; |
|||
|
|||
enum class Format |
|||
{ |
|||
Binary, |
|||
Hex, |
|||
Decimal, |
|||
Open, |
|||
Close |
|||
}; |
|||
|
|||
struct InvalidUserString: public Exception {}; |
|||
struct InvalidFormat: public Exception {}; |
|||
|
|||
enum class Base |
|||
{ |
|||
Unknown, |
|||
Bytes, |
|||
Address, |
|||
Int, |
|||
Uint, |
|||
Fixed |
|||
}; |
|||
|
|||
static const map<Base, string> s_bases = |
|||
{ |
|||
{ Base::Bytes, "bytes" }, |
|||
{ Base::Address, "address" }, |
|||
{ Base::Int, "int" }, |
|||
{ Base::Uint, "uint" }, |
|||
{ Base::Fixed, "fixed" } |
|||
}; |
|||
|
|||
struct EncodingPrefs |
|||
{ |
|||
Encoding e = Encoding::Auto; |
|||
bool prefix = true; |
|||
}; |
|||
|
|||
struct ABIType |
|||
{ |
|||
Base base = Base::Unknown; |
|||
unsigned size = 32; |
|||
unsigned ssize = 0; |
|||
vector<int> dims; |
|||
string name; |
|||
ABIType() = default; |
|||
ABIType(std::string const& _type, std::string const& _name): |
|||
name(_name) |
|||
{ |
|||
string rest; |
|||
for (auto const& i: s_bases) |
|||
if (boost::algorithm::starts_with(_type, i.second)) |
|||
{ |
|||
base = i.first; |
|||
rest = _type.substr(i.second.size()); |
|||
} |
|||
if (base == Base::Unknown) |
|||
throw InvalidFormat(); |
|||
boost::regex r("(\\d*)(x(\\d+))?((\\[\\d*\\])*)"); |
|||
boost::smatch res; |
|||
boost::regex_match(rest, res, r); |
|||
size = res[1].length() > 0 ? stoi(res[1]) : 0; |
|||
ssize = res[3].length() > 0 ? stoi(res[3]) : 0; |
|||
boost::regex r2("\\[(\\d*)\\](.*)"); |
|||
for (rest = res[4]; boost::regex_match(rest, res, r2); rest = res[2]) |
|||
dims.push_back(!res[1].length() ? -1 : stoi(res[1])); |
|||
} |
|||
|
|||
ABIType(std::string const& _s) |
|||
{ |
|||
if (_s.size() < 1) |
|||
return; |
|||
switch (_s[0]) |
|||
{ |
|||
case 'b': base = Base::Bytes; break; |
|||
case 'a': base = Base::Address; break; |
|||
case 'i': base = Base::Int; break; |
|||
case 'u': base = Base::Uint; break; |
|||
case 'f': base = Base::Fixed; break; |
|||
default: throw InvalidFormat(); |
|||
} |
|||
if (_s.size() < 2) |
|||
{ |
|||
if (base == Base::Fixed) |
|||
size = ssize = 16; |
|||
else if (base == Base::Address || base == Base::Bytes) |
|||
size = 0; |
|||
else |
|||
size = 32; |
|||
return; |
|||
} |
|||
strings d; |
|||
boost::algorithm::split(d, _s, boost::is_any_of("*")); |
|||
string s = d[0]; |
|||
if (s.find_first_of('x') == string::npos) |
|||
size = stoi(s.substr(1)); |
|||
else |
|||
{ |
|||
size = stoi(s.substr(1, s.find_first_of('x') - 1)); |
|||
ssize = stoi(s.substr(s.find_first_of('x') + 1)); |
|||
} |
|||
for (unsigned i = 1; i < d.size(); ++i) |
|||
if (d[i].empty()) |
|||
dims.push_back(-1); |
|||
else |
|||
dims.push_back(stoi(d[i])); |
|||
} |
|||
|
|||
string canon() const |
|||
{ |
|||
string ret; |
|||
switch (base) |
|||
{ |
|||
case Base::Bytes: ret = "bytes" + (size > 0 ? toString(size) : ""); break; |
|||
case Base::Address: ret = "address"; break; |
|||
case Base::Int: ret = "int" + toString(size); break; |
|||
case Base::Uint: ret = "uint" + toString(size); break; |
|||
case Base::Fixed: ret = "fixed" + toString(size) + "x" + toString(ssize); break; |
|||
default: throw InvalidFormat(); |
|||
} |
|||
for (int i: dims) |
|||
ret += "[" + ((i > -1) ? toString(i) : "") + "]"; |
|||
return ret; |
|||
} |
|||
|
|||
bool isBytes() const { return base == Base::Bytes && !size; } |
|||
|
|||
string render(bytes const& _data, EncodingPrefs _e) const |
|||
{ |
|||
if (base == Base::Uint || base == Base::Int) |
|||
{ |
|||
if (_e.e == Encoding::Hex) |
|||
return (_e.prefix ? "0x" : "") + toHex(toCompactBigEndian(fromBigEndian<bigint>(bytesConstRef(&_data).cropped(32 - size / 8)))); |
|||
else |
|||
{ |
|||
bigint i = fromBigEndian<bigint>(bytesConstRef(&_data).cropped(32 - size / 8)); |
|||
if (base == Base::Int && i > (bigint(1) << (size - 1))) |
|||
i -= (bigint(1) << size); |
|||
return toString(i); |
|||
} |
|||
} |
|||
else if (base == Base::Address) |
|||
{ |
|||
Address a = Address(h256(_data), Address::AlignRight); |
|||
return _e.e == Encoding::Binary ? asString(a.asBytes()) : ((_e.prefix ? "0x" : "") + toString(a)); |
|||
} |
|||
else if (isBytes()) |
|||
{ |
|||
return _e.e == Encoding::Binary ? asString(_data) : ((_e.prefix ? "0x" : "") + toHex(_data)); |
|||
} |
|||
else if (base == Base::Bytes) |
|||
{ |
|||
bytesConstRef b(&_data); |
|||
b = b.cropped(0, size); |
|||
return _e.e == Encoding::Binary ? asString(b) : ((_e.prefix ? "0x" : "") + toHex(b)); |
|||
} |
|||
else |
|||
throw InvalidFormat(); |
|||
} |
|||
|
|||
bytes unrender(bytes const& _data, Format _f) const |
|||
{ |
|||
if (isBytes()) |
|||
{ |
|||
auto ret = _data; |
|||
while (ret.size() % 32 != 0) |
|||
ret.push_back(0); |
|||
return ret; |
|||
} |
|||
else |
|||
return aligned(_data, _f, 32); |
|||
} |
|||
|
|||
void noteHexInput(unsigned _nibbles) { if (base == Base::Unknown) { if (_nibbles == 40) base = Base::Address; else { base = Base::Bytes; size = _nibbles / 2; } } } |
|||
void noteBinaryInput() { if (base == Base::Unknown) { base = Base::Bytes; size = 32; } } |
|||
void noteDecimalInput() { if (base == Base::Unknown) { base = Base::Uint; size = 32; } } |
|||
|
|||
bytes aligned(bytes const& _b, Format _f, unsigned _length) const |
|||
{ |
|||
bytes ret = _b; |
|||
while (ret.size() < _length) |
|||
if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) |
|||
ret.push_back(0); |
|||
else |
|||
ret.insert(ret.begin(), 0); |
|||
while (ret.size() > _length) |
|||
if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) |
|||
ret.pop_back(); |
|||
else |
|||
ret.erase(ret.begin()); |
|||
return ret; |
|||
} |
|||
}; |
|||
|
|||
tuple<bytes, ABIType, Format> fromUser(std::string const& _arg, Tristate _prefix, Tristate _typing) |
|||
{ |
|||
ABIType type; |
|||
string val; |
|||
if (_typing == Tristate::True || (_typing == Tristate::Mu && _arg.find(':') != string::npos)) |
|||
{ |
|||
if (_arg.find(':') == string::npos) |
|||
throw InvalidUserString(); |
|||
type = ABIType(_arg.substr(0, _arg.find(':'))); |
|||
val = _arg.substr(_arg.find(':') + 1); |
|||
} |
|||
else |
|||
val = _arg; |
|||
|
|||
if (_prefix != Tristate::False) |
|||
{ |
|||
if (val.substr(0, 2) == "0x") |
|||
{ |
|||
type.noteHexInput(val.size() - 2); |
|||
return make_tuple(fromHex(val), type, Format::Hex); |
|||
} |
|||
if (val.substr(0, 1) == "+") |
|||
{ |
|||
type.noteDecimalInput(); |
|||
return make_tuple(toCompactBigEndian(bigint(val.substr(1))), type, Format::Decimal); |
|||
} |
|||
if (val.substr(0, 1) == "'") |
|||
{ |
|||
type.noteBinaryInput(); |
|||
return make_tuple(asBytes(val.substr(1)), type, Format::Binary); |
|||
} |
|||
if (val == "[") |
|||
return make_tuple(bytes(), type, Format::Open); |
|||
if (val == "]") |
|||
return make_tuple(bytes(), type, Format::Close); |
|||
} |
|||
if (_prefix != Tristate::True) |
|||
{ |
|||
if (val.find_first_not_of("0123456789") == string::npos) |
|||
{ |
|||
type.noteDecimalInput(); |
|||
return make_tuple(toCompactBigEndian(bigint(val)), type, Format::Decimal); |
|||
} |
|||
if (val.find_first_not_of("0123456789abcdefABCDEF") == string::npos) |
|||
{ |
|||
type.noteHexInput(val.size()); |
|||
return make_tuple(fromHex(val), type, Format::Hex); |
|||
} |
|||
if (val == "[") |
|||
return make_tuple(bytes(), type, Format::Open); |
|||
if (val == "]") |
|||
return make_tuple(bytes(), type, Format::Close); |
|||
type.noteBinaryInput(); |
|||
return make_tuple(asBytes(val), type, Format::Binary); |
|||
} |
|||
throw InvalidUserString(); |
|||
} |
|||
|
|||
struct ExpectedAdditionalParameter: public Exception {}; |
|||
struct ExpectedOpen: public Exception {}; |
|||
struct ExpectedClose: public Exception {}; |
|||
|
|||
struct ABIMethod |
|||
{ |
|||
string name; |
|||
vector<ABIType> ins; |
|||
vector<ABIType> outs; |
|||
bool isConstant = false; |
|||
|
|||
// isolation *IS* documentation.
|
|||
|
|||
ABIMethod() = default; |
|||
|
|||
ABIMethod(js::mObject _o) |
|||
{ |
|||
name = _o["name"].get_str(); |
|||
isConstant = _o["constant"].get_bool(); |
|||
if (_o.count("inputs")) |
|||
for (auto const& i: _o["inputs"].get_array()) |
|||
{ |
|||
js::mObject a = i.get_obj(); |
|||
ins.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); |
|||
} |
|||
if (_o.count("outputs")) |
|||
for (auto const& i: _o["outputs"].get_array()) |
|||
{ |
|||
js::mObject a = i.get_obj(); |
|||
outs.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); |
|||
} |
|||
} |
|||
|
|||
ABIMethod(string const& _name, vector<ABIType> const& _args) |
|||
{ |
|||
name = _name; |
|||
ins = _args; |
|||
} |
|||
|
|||
string sig() const |
|||
{ |
|||
string methodArgs; |
|||
for (auto const& arg: ins) |
|||
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); |
|||
return name + "(" + methodArgs + ")"; |
|||
} |
|||
FixedHash<4> id() const { return FixedHash<4>(sha3(sig())); } |
|||
|
|||
std::string solidityDeclaration() const |
|||
{ |
|||
ostringstream ss; |
|||
ss << "function " << name << "("; |
|||
int f = 0; |
|||
for (ABIType const& i: ins) |
|||
ss << (f++ ? ", " : "") << i.canon() << " " << i.name; |
|||
ss << ") "; |
|||
if (isConstant) |
|||
ss << "constant "; |
|||
if (!outs.empty()) |
|||
{ |
|||
ss << "returns("; |
|||
f = 0; |
|||
for (ABIType const& i: outs) |
|||
ss << (f++ ? ", " : "") << i.canon() << " " << i.name; |
|||
ss << ")"; |
|||
} |
|||
return ss.str(); |
|||
} |
|||
|
|||
bytes encode(vector<pair<bytes, Format>> const& _params) const |
|||
{ |
|||
bytes ret = name.empty() ? bytes() : id().asBytes(); |
|||
bytes suffix; |
|||
|
|||
// int int[] int
|
|||
// example: 42 [ 1 2 3 ] 69
|
|||
// int[2][][3]
|
|||
// example: [ [ [ 1 2 3 ] [ 4 5 6 ] ] [ ] ]
|
|||
|
|||
unsigned pi = 0; |
|||
|
|||
for (ABIType const& a: ins) |
|||
{ |
|||
if (pi >= _params.size()) |
|||
throw ExpectedAdditionalParameter(); |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
ret += h256(u256(_params[pi].first.size())).asBytes(); |
|||
suffix += a.unrender(_params[pi].first, _params[pi].second); |
|||
pi++; |
|||
if (pi >= _params.size()) |
|||
throw ExpectedAdditionalParameter(); |
|||
}; |
|||
function<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
if (_params[pi].second != Format::Open) |
|||
throw ExpectedOpen(); |
|||
++pi; |
|||
int l = a.dims[addr.size()]; |
|||
if (l == -1) |
|||
{ |
|||
// read ahead in params and discover the arity.
|
|||
unsigned depth = 0; |
|||
l = 0; |
|||
for (unsigned pi2 = pi; depth || _params[pi2].second != Format::Close;) |
|||
{ |
|||
if (_params[pi2].second == Format::Open) |
|||
++depth; |
|||
if (_params[pi2].second == Format::Close) |
|||
--depth; |
|||
if (!depth) |
|||
++l; |
|||
if (++pi2 == _params.size()) |
|||
throw ExpectedClose(); |
|||
} |
|||
ret += h256(u256(l)).asBytes(); |
|||
} |
|||
q *= l; |
|||
for (addr.push_back(0); addr.back() < l; ++addr.back()) |
|||
putDim(addr, q); |
|||
if (_params[pi].second != Format::Close) |
|||
throw ExpectedClose(); |
|||
++pi; |
|||
} |
|||
}; |
|||
putDim(vector<int>(), 1); |
|||
} |
|||
return ret + suffix; |
|||
} |
|||
string decode(bytes const& _data, int _index, EncodingPrefs _ep) |
|||
{ |
|||
stringstream out; |
|||
unsigned di = 0; |
|||
vector<unsigned> catDims; |
|||
for (ABIType const& a: outs) |
|||
{ |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
{ |
|||
catDims.push_back(fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32))); |
|||
di += 32; |
|||
} |
|||
}; |
|||
function<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
int l = a.dims[addr.size()]; |
|||
if (l == -1) |
|||
{ |
|||
l = fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32)); |
|||
catDims.push_back(l); |
|||
di += 32; |
|||
} |
|||
q *= l; |
|||
for (addr.push_back(0); addr.back() < l; ++addr.back()) |
|||
putDim(addr, q); |
|||
} |
|||
}; |
|||
putDim(vector<int>(), 1); |
|||
} |
|||
unsigned d = 0; |
|||
for (ABIType const& a: outs) |
|||
{ |
|||
if (_index == -1 && out.tellp() > 0) |
|||
out << ", "; |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
{ |
|||
out << a.render(bytesConstRef(&_data).cropped(di, catDims[d]).toBytes(), _ep); |
|||
di += ((catDims[d] + 31) / 32) * 32; |
|||
d++; |
|||
} |
|||
else |
|||
{ |
|||
out << a.render(bytesConstRef(&_data).cropped(di, 32).toBytes(), _ep); |
|||
di += 32; |
|||
} |
|||
}; |
|||
function<void(vector<int>)> putDim = [&](vector<int> addr) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
out << "["; |
|||
addr.push_back(0); |
|||
int l = a.dims[addr.size() - 1]; |
|||
if (l == -1) |
|||
l = catDims[d++]; |
|||
for (addr.back() = 0; addr.back() < l; ++addr.back()) |
|||
{ |
|||
if (addr.back()) |
|||
out << ", "; |
|||
putDim(addr); |
|||
} |
|||
out << "]"; |
|||
} |
|||
}; |
|||
putDim(vector<int>()); |
|||
} |
|||
return out.str(); |
|||
} |
|||
}; |
|||
|
|||
string canonSig(string const& _name, vector<ABIType> const& _args) |
|||
{ |
|||
try { |
|||
string methodArgs; |
|||
for (auto const& arg: _args) |
|||
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); |
|||
return _name + "(" + methodArgs + ")"; |
|||
} |
|||
catch (...) { |
|||
return string(); |
|||
} |
|||
} |
|||
|
|||
struct UnknownMethod: public Exception {}; |
|||
struct OverloadedMethod: public Exception {}; |
|||
|
|||
class ABI |
|||
{ |
|||
public: |
|||
ABI() = default; |
|||
ABI(std::string const& _json) |
|||
{ |
|||
js::mValue v; |
|||
js::read_string(_json, v); |
|||
for (auto const& i: v.get_array()) |
|||
{ |
|||
js::mObject o = i.get_obj(); |
|||
if (o["type"].get_str() != "function") |
|||
continue; |
|||
ABIMethod m(o); |
|||
m_methods[m.id()] = m; |
|||
} |
|||
} |
|||
|
|||
ABIMethod method(string _nameOrSig, vector<ABIType> const& _args) const |
|||
{ |
|||
auto id = FixedHash<4>(sha3(_nameOrSig)); |
|||
if (!m_methods.count(id)) |
|||
id = FixedHash<4>(sha3(canonSig(_nameOrSig, _args))); |
|||
if (!m_methods.count(id)) |
|||
for (auto const& m: m_methods) |
|||
if (m.second.name == _nameOrSig) |
|||
{ |
|||
if (m_methods.count(id)) |
|||
throw OverloadedMethod(); |
|||
id = m.first; |
|||
} |
|||
if (m_methods.count(id)) |
|||
return m_methods.at(id); |
|||
throw UnknownMethod(); |
|||
} |
|||
|
|||
friend ostream& operator<<(ostream& _out, ABI const& _abi); |
|||
|
|||
private: |
|||
map<FixedHash<4>, ABIMethod> m_methods; |
|||
}; |
|||
|
|||
ostream& operator<<(ostream& _out, ABI const& _abi) |
|||
{ |
|||
_out << "contract {" << endl; |
|||
for (auto const& i: _abi.m_methods) |
|||
_out << " " << i.second.solidityDeclaration() << "; // " << i.first.abridged() << endl; |
|||
_out << "}" << endl; |
|||
return _out; |
|||
} |
|||
|
|||
void userOutput(ostream& _out, bytes const& _data, Encoding _e) |
|||
{ |
|||
switch (_e) |
|||
{ |
|||
case Encoding::Binary: |
|||
_out.write((char const*)_data.data(), _data.size()); |
|||
break; |
|||
default: |
|||
_out << toHex(_data) << endl; |
|||
} |
|||
} |
|||
|
|||
template <unsigned n, class T> vector<typename std::remove_reference<decltype(get<n>(T()))>::type> retrieve(vector<T> const& _t) |
|||
{ |
|||
vector<typename std::remove_reference<decltype(get<n>(T()))>::type> ret; |
|||
for (T const& i: _t) |
|||
ret.push_back(get<n>(i)); |
|||
return ret; |
|||
} |
|||
|
|||
int main(int argc, char** argv) |
|||
{ |
|||
Encoding encoding = Encoding::Auto; |
|||
Mode mode = Mode::Encode; |
|||
string abiFile; |
|||
string method; |
|||
Tristate formatPrefix = Tristate::Mu; |
|||
Tristate typePrefix = Tristate::Mu; |
|||
EncodingPrefs prefs; |
|||
bool verbose = false; |
|||
int outputIndex = -1; |
|||
vector<pair<bytes, Format>> params; |
|||
vector<ABIType> args; |
|||
string incoming; |
|||
|
|||
for (int i = 1; i < argc; ++i) |
|||
{ |
|||
string arg = argv[i]; |
|||
if (arg == "-h" || arg == "--help") |
|||
help(); |
|||
else if (arg == "enc" && i == 1) |
|||
mode = Mode::Encode; |
|||
else if (arg == "dec" && i == 1) |
|||
mode = Mode::Decode; |
|||
else if ((arg == "-a" || arg == "--abi") && argc > i) |
|||
abiFile = argv[++i]; |
|||
else if ((arg == "-i" || arg == "--index") && argc > i) |
|||
outputIndex = atoi(argv[++i]); |
|||
else if (arg == "-p" || arg == "--prefix") |
|||
prefs.prefix = true; |
|||
else if (arg == "-f" || arg == "--format-prefix") |
|||
formatPrefix = Tristate::True; |
|||
else if (arg == "-F" || arg == "--no-format-prefix") |
|||
formatPrefix = Tristate::False; |
|||
else if (arg == "-t" || arg == "--typing") |
|||
typePrefix = Tristate::True; |
|||
else if (arg == "-T" || arg == "--no-typing") |
|||
typePrefix = Tristate::False; |
|||
else if (arg == "-x" || arg == "--hex") |
|||
prefs.e = Encoding::Hex; |
|||
else if (arg == "-d" || arg == "--decimal" || arg == "--dec") |
|||
prefs.e = Encoding::Decimal; |
|||
else if (arg == "-b" || arg == "--binary" || arg == "--bin") |
|||
prefs.e = Encoding::Binary; |
|||
else if (arg == "-v" || arg == "--verbose") |
|||
verbose = true; |
|||
else if (arg == "-V" || arg == "--version") |
|||
version(); |
|||
else if (method.empty()) |
|||
method = arg; |
|||
else if (mode == Mode::Encode) |
|||
{ |
|||
auto u = fromUser(arg, formatPrefix, typePrefix); |
|||
args.push_back(get<1>(u)); |
|||
params.push_back(make_pair(get<0>(u), get<2>(u))); |
|||
} |
|||
else if (mode == Mode::Decode) |
|||
incoming += arg; |
|||
} |
|||
|
|||
string abiData; |
|||
if (!abiFile.empty()) |
|||
abiData = contentsString(abiFile); |
|||
|
|||
if (mode == Mode::Encode) |
|||
{ |
|||
ABIMethod m; |
|||
if (abiData.empty()) |
|||
m = ABIMethod(method, args); |
|||
else |
|||
{ |
|||
ABI abi(abiData); |
|||
if (verbose) |
|||
cerr << "ABI:" << endl << abi; |
|||
try { |
|||
m = abi.method(method, args); |
|||
} |
|||
catch (...) |
|||
{ |
|||
cerr << "Unknown method in ABI." << endl; |
|||
exit(-1); |
|||
} |
|||
} |
|||
try { |
|||
userOutput(cout, m.encode(params), encoding); |
|||
} |
|||
catch (ExpectedAdditionalParameter const&) |
|||
{ |
|||
cerr << "Expected additional parameter in input." << endl; |
|||
exit(-1); |
|||
} |
|||
catch (ExpectedOpen const&) |
|||
{ |
|||
cerr << "Expected open-bracket '[' in input." << endl; |
|||
exit(-1); |
|||
} |
|||
catch (ExpectedClose const&) |
|||
{ |
|||
cerr << "Expected close-bracket ']' in input." << endl; |
|||
exit(-1); |
|||
} |
|||
} |
|||
else if (mode == Mode::Decode) |
|||
{ |
|||
if (abiData.empty()) |
|||
{ |
|||
cerr << "Please specify an ABI file." << endl; |
|||
exit(-1); |
|||
} |
|||
else |
|||
{ |
|||
ABI abi(abiData); |
|||
ABIMethod m; |
|||
if (verbose) |
|||
cerr << "ABI:" << endl << abi; |
|||
try { |
|||
m = abi.method(method, args); |
|||
} |
|||
catch(...) |
|||
{ |
|||
cerr << "Unknown method in ABI." << endl; |
|||
exit(-1); |
|||
} |
|||
string encoded; |
|||
if (incoming == "--" || incoming.empty()) |
|||
for (int i = cin.get(); i != -1; i = cin.get()) |
|||
encoded.push_back((char)i); |
|||
else |
|||
{ |
|||
encoded = contentsString(incoming); |
|||
} |
|||
cout << m.decode(fromHex(boost::trim_copy(encoded)), outputIndex, prefs) << endl; |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,63 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Connect.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "Connect.h" |
|||
|
|||
#include <libp2p/Host.h> |
|||
#include "ui_Connect.h" |
|||
|
|||
Connect::Connect(QWidget *parent) : |
|||
QDialog(parent), |
|||
ui(new Ui::Connect) |
|||
{ |
|||
ui->setupUi(this); |
|||
} |
|||
|
|||
Connect::~Connect() |
|||
{ |
|||
delete ui; |
|||
} |
|||
|
|||
void Connect::setEnvironment(QStringList const& _nodes) |
|||
{ |
|||
ui->host->addItems(_nodes); |
|||
} |
|||
|
|||
void Connect::reset() |
|||
{ |
|||
ui->nodeId->clear(); |
|||
ui->required->setChecked(false); |
|||
} |
|||
|
|||
QString Connect::host() |
|||
{ |
|||
return ui->host->currentText(); |
|||
} |
|||
|
|||
QString Connect::nodeId() |
|||
{ |
|||
return ui->nodeId->text(); |
|||
} |
|||
|
|||
bool Connect::required() |
|||
{ |
|||
return ui->required->isChecked(); |
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Connect.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QDialog> |
|||
#include <QList> |
|||
|
|||
namespace Ui { class Connect; } |
|||
namespace dev { namespace p2p { class Host; } } |
|||
|
|||
class Connect : public QDialog |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit Connect(QWidget* _parent = 0); |
|||
~Connect(); |
|||
|
|||
/// Populate host chooser with default host entries.
|
|||
void setEnvironment(QStringList const& _nodes); |
|||
|
|||
/// Clear dialogue inputs.
|
|||
void reset(); |
|||
|
|||
/// @returns the host string, as chosen or entered by the user. Assumed to be "hostOrIP:port" (:port is optional).
|
|||
QString host(); |
|||
|
|||
/// @returns the identity of the node, as entered by the user. Assumed to be a 64-character hex string.
|
|||
QString nodeId(); |
|||
|
|||
/// @returns true if Required is checked by the user, indicating that the host is a required Peer.
|
|||
bool required(); |
|||
|
|||
private: |
|||
Ui::Connect* ui; |
|||
}; |
@ -0,0 +1,123 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<ui version="4.0"> |
|||
<class>Connect</class> |
|||
<widget class="QDialog" name="Connect"> |
|||
<property name="windowModality"> |
|||
<enum>Qt::WindowModal</enum> |
|||
</property> |
|||
<property name="geometry"> |
|||
<rect> |
|||
<x>0</x> |
|||
<y>0</y> |
|||
<width>343</width> |
|||
<height>178</height> |
|||
</rect> |
|||
</property> |
|||
<property name="sizePolicy"> |
|||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> |
|||
<horstretch>0</horstretch> |
|||
<verstretch>0</verstretch> |
|||
</sizepolicy> |
|||
</property> |
|||
<property name="maximumSize"> |
|||
<size> |
|||
<width>343</width> |
|||
<height>178</height> |
|||
</size> |
|||
</property> |
|||
<property name="windowTitle"> |
|||
<string>Connect to Peer</string> |
|||
</property> |
|||
<property name="modal"> |
|||
<bool>true</bool> |
|||
</property> |
|||
<layout class="QFormLayout" name="formLayout_2"> |
|||
<item row="1" column="0"> |
|||
<layout class="QFormLayout" name="formLayout"> |
|||
<item row="1" column="0" colspan="2"> |
|||
<widget class="QComboBox" name="host"> |
|||
<property name="minimumSize"> |
|||
<size> |
|||
<width>311</width> |
|||
<height>0</height> |
|||
</size> |
|||
</property> |
|||
<property name="editable"> |
|||
<bool>true</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="2" column="0" colspan="2"> |
|||
<widget class="QLineEdit" name="nodeId"> |
|||
<property name="placeholderText"> |
|||
<string>Node Id</string> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="3" column="0" colspan="2"> |
|||
<widget class="QCheckBox" name="required"> |
|||
<property name="text"> |
|||
<string>Required (Always Connect to this Peer)</string> |
|||
</property> |
|||
<property name="tristate"> |
|||
<bool>false</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="4" column="0" colspan="2"> |
|||
<widget class="QDialogButtonBox" name="buttonBox"> |
|||
<property name="orientation"> |
|||
<enum>Qt::Horizontal</enum> |
|||
</property> |
|||
<property name="standardButtons"> |
|||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</item> |
|||
<item row="0" column="0"> |
|||
<widget class="QLabel" name="formLabel"> |
|||
<property name="text"> |
|||
<string>Enter a peer to which a connection may be made:</string> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</widget> |
|||
<resources/> |
|||
<connections> |
|||
<connection> |
|||
<sender>buttonBox</sender> |
|||
<signal>accepted()</signal> |
|||
<receiver>Connect</receiver> |
|||
<slot>accept()</slot> |
|||
<hints> |
|||
<hint type="sourcelabel"> |
|||
<x>248</x> |
|||
<y>254</y> |
|||
</hint> |
|||
<hint type="destinationlabel"> |
|||
<x>157</x> |
|||
<y>274</y> |
|||
</hint> |
|||
</hints> |
|||
</connection> |
|||
<connection> |
|||
<sender>buttonBox</sender> |
|||
<signal>rejected()</signal> |
|||
<receiver>Connect</receiver> |
|||
<slot>reject()</slot> |
|||
<hints> |
|||
<hint type="sourcelabel"> |
|||
<x>316</x> |
|||
<y>260</y> |
|||
</hint> |
|||
<hint type="destinationlabel"> |
|||
<x>286</x> |
|||
<y>274</y> |
|||
</hint> |
|||
</hints> |
|||
</connection> |
|||
</connections> |
|||
</ui> |
@ -0,0 +1,15 @@ |
|||
# Should be used to run ctest |
|||
# |
|||
# example usage: |
|||
# cmake -DETH_TEST_NAME=TestInterfaceStub -DCTEST_COMMAND=/path/to/ctest -P scripts/runtest.cmake |
|||
|
|||
if (NOT CTEST_COMMAND) |
|||
message(FATAL_ERROR "ctest could not be found!") |
|||
endif() |
|||
|
|||
# verbosity is off, cause BOOST_MESSAGE is not thread safe and output is a trash |
|||
# see https://codecrafter.wordpress.com/2012/11/01/c-unit-test-framework-adapter-part-3/ |
|||
# |
|||
# output might not be usefull cause of thread safety issue |
|||
execute_process(COMMAND ${CTEST_COMMAND} --force-new-ctest-process -C Debug --output-on-failure -j 4 -R "${ETH_TEST_NAME}[.].*") |
|||
|
@ -0,0 +1,35 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) |
|||
include_directories(BEFORE ..) |
|||
include_directories(${Boost_INCLUDE_DIRS}) |
|||
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE ethrpctest) |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
|
|||
add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) |
|||
|
|||
add_dependencies(${EXECUTABLE} BuildInfo.h) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARIES}) |
|||
|
|||
if (READLINE_FOUND) |
|||
target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES}) |
|||
endif() |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_PROGRAM_OPTIONS_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} testutils) |
|||
target_link_libraries(${EXECUTABLE} web3jsonrpc) |
|||
|
|||
if (DEFINED WIN32 AND NOT DEFINED CMAKE_COMPILER_IS_MINGW) |
|||
add_custom_command(TARGET ${EXECUTABLE} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy ${MHD_DLL_RELEASE} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") |
|||
endif() |
|||
|
|||
install( TARGETS ${EXECUTABLE} DESTINATION bin ) |
|||
|
@ -0,0 +1,129 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file CommandLineInterface.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <iostream> |
|||
#include <fstream> |
|||
#include <csignal> |
|||
#include <thread> |
|||
#include <boost/filesystem.hpp> |
|||
#include <jsonrpccpp/server/connectors/httpserver.h> |
|||
#include <libtestutils/Common.h> |
|||
#include <libtestutils/BlockChainLoader.h> |
|||
#include <libtestutils/FixedClient.h> |
|||
#include <libtestutils/FixedWebThreeServer.h> |
|||
#include "CommandLineInterface.h" |
|||
#include "BuildInfo.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::test; |
|||
namespace po = boost::program_options; |
|||
|
|||
bool CommandLineInterface::parseArguments(int argc, char** argv) |
|||
{ |
|||
// Declare the supported options.
|
|||
po::options_description desc("Allowed options"); |
|||
desc.add_options() |
|||
("help", "Show help message and exit") |
|||
("json", po::value<vector<string>>()->required(), "input file") |
|||
("test", po::value<vector<string>>()->required(), "test case name"); |
|||
|
|||
// All positional options should be interpreted as input files
|
|||
po::positional_options_description p; |
|||
|
|||
// parse the compiler arguments
|
|||
try |
|||
{ |
|||
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args); |
|||
|
|||
if (m_args.count("help")) |
|||
{ |
|||
cout << desc; |
|||
return false; |
|||
} |
|||
|
|||
po::notify(m_args); |
|||
} |
|||
catch (po::error const& _exception) |
|||
{ |
|||
cout << _exception.what() << endl; |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool CommandLineInterface::processInput() |
|||
{ |
|||
string infile = m_args["json"].as<vector<string>>()[0]; |
|||
|
|||
auto path = boost::filesystem::path(infile); |
|||
if (!boost::filesystem::exists(path)) |
|||
{ |
|||
cout << "Non existant input file \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
string test = m_args["test"].as<vector<string>>()[0]; |
|||
Json::Value j = dev::test::loadJsonFromFile(path.string()); |
|||
|
|||
if (j[test].empty()) |
|||
{ |
|||
cout << "Non existant test case \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
if (!j[test].isObject()) |
|||
{ |
|||
cout << "Incorrect JSON file \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
m_json = j[test]; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool g_exit = false; |
|||
|
|||
void sighandler(int) |
|||
{ |
|||
g_exit = true; |
|||
} |
|||
|
|||
void CommandLineInterface::actOnInput() |
|||
{ |
|||
BlockChainLoader bcl(m_json); |
|||
FixedClient client(bcl.bc(), bcl.state()); |
|||
unique_ptr<FixedWebThreeServer> jsonrpcServer; |
|||
auto server = new jsonrpc::HttpServer(8080, "", "", 2); |
|||
jsonrpcServer.reset(new FixedWebThreeServer(*server, {}, &client)); |
|||
jsonrpcServer->StartListening(); |
|||
|
|||
signal(SIGABRT, &sighandler); |
|||
signal(SIGTERM, &sighandler); |
|||
signal(SIGINT, &sighandler); |
|||
|
|||
while (!g_exit) |
|||
this_thread::sleep_for(chrono::milliseconds(1000)); |
|||
} |
@ -0,0 +1,46 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** CommandLineInterface.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
#pragma once |
|||
|
|||
#include <json/json.h> |
|||
#include <boost/program_options.hpp> |
|||
|
|||
class CommandLineInterface |
|||
{ |
|||
public: |
|||
CommandLineInterface() {} |
|||
|
|||
/// Parse command line arguments and return false if we should not continue
|
|||
bool parseArguments(int argc, char** argv); |
|||
/// Parse input file and check if test exists
|
|||
bool processInput(); |
|||
/// Start FixedJsonRpcServer
|
|||
void actOnInput(); |
|||
|
|||
private: |
|||
|
|||
/// Compiler arguments variable map
|
|||
boost::program_options::variables_map m_args; |
|||
|
|||
/// loaded json test case
|
|||
Json::Value m_json; |
|||
}; |
|||
|
@ -0,0 +1,34 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** main.cpp
|
|||
* @author Marek Kotewicz <c@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "CommandLineInterface.h" |
|||
|
|||
int main(int argc, char** argv) |
|||
{ |
|||
CommandLineInterface cli; |
|||
if (!cli.parseArguments(argc, argv)) |
|||
return 1; |
|||
if (!cli.processInput()) |
|||
return 1; |
|||
cli.actOnInput(); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,89 @@ |
|||
/*
|
|||
This file is part of ethash. |
|||
|
|||
ethash 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. |
|||
|
|||
ethash 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 ethash. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file io.c
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
#include "io.h" |
|||
#include <string.h> |
|||
#include <stdio.h> |
|||
|
|||
// silly macro to save some typing
|
|||
#define PASS_ARR(c_) (c_), sizeof(c_) |
|||
|
|||
static bool ethash_io_write_file(char const *dirname, |
|||
char const* filename, |
|||
size_t filename_length, |
|||
void const* data, |
|||
size_t data_size) |
|||
{ |
|||
bool ret = false; |
|||
char *fullname = ethash_io_create_filename(dirname, filename, filename_length); |
|||
if (!fullname) { |
|||
return false; |
|||
} |
|||
FILE *f = fopen(fullname, "wb"); |
|||
if (!f) { |
|||
goto free_name; |
|||
} |
|||
if (data_size != fwrite(data, 1, data_size, f)) { |
|||
goto close; |
|||
} |
|||
|
|||
ret = true; |
|||
close: |
|||
fclose(f); |
|||
free_name: |
|||
free(fullname); |
|||
return ret; |
|||
} |
|||
|
|||
bool ethash_io_write(char const *dirname, |
|||
ethash_params const* params, |
|||
ethash_blockhash_t seedhash, |
|||
void const* cache, |
|||
uint8_t **data, |
|||
size_t *data_size) |
|||
{ |
|||
char info_buffer[DAG_MEMO_BYTESIZE]; |
|||
// allocate the bytes
|
|||
uint8_t *temp_data_ptr = malloc(params->full_size); |
|||
if (!temp_data_ptr) { |
|||
goto end; |
|||
} |
|||
ethash_compute_full_data(temp_data_ptr, params, cache); |
|||
|
|||
if (!ethash_io_write_file(dirname, PASS_ARR(DAG_FILE_NAME), temp_data_ptr, params->full_size)) { |
|||
goto fail_free; |
|||
} |
|||
|
|||
ethash_io_serialize_info(ETHASH_REVISION, seedhash, info_buffer); |
|||
if (!ethash_io_write_file(dirname, PASS_ARR(DAG_MEMO_NAME), info_buffer, DAG_MEMO_BYTESIZE)) { |
|||
goto fail_free; |
|||
} |
|||
|
|||
*data = temp_data_ptr; |
|||
*data_size = params->full_size; |
|||
return true; |
|||
|
|||
fail_free: |
|||
free(temp_data_ptr); |
|||
end: |
|||
return false; |
|||
} |
|||
|
|||
#undef PASS_ARR |
@ -0,0 +1,116 @@ |
|||
/*
|
|||
This file is part of ethash. |
|||
|
|||
ethash 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. |
|||
|
|||
ethash 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 ethash. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file io.h
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
#pragma once |
|||
#include <stdlib.h> |
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include "ethash.h" |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
typedef struct ethash_blockhash { uint8_t b[32]; } ethash_blockhash_t; |
|||
|
|||
static const char DAG_FILE_NAME[] = "full"; |
|||
static const char DAG_MEMO_NAME[] = "full.info"; |
|||
// MSVC thinks that "static const unsigned int" is not a compile time variable. Sorry for the #define :(
|
|||
#define DAG_MEMO_BYTESIZE 36 |
|||
|
|||
/// Possible return values of @see ethash_io_prepare
|
|||
enum ethash_io_rc { |
|||
ETHASH_IO_FAIL = 0, ///< There has been an IO failure
|
|||
ETHASH_IO_MEMO_MISMATCH, ///< Memo file either did not exist or there was content mismatch
|
|||
ETHASH_IO_MEMO_MATCH, ///< Memo file existed and contents matched. No need to do anything
|
|||
}; |
|||
|
|||
/**
|
|||
* Prepares io for ethash |
|||
* |
|||
* Create the DAG directory if it does not exist, and check if the memo file matches. |
|||
* If it does not match then it's deleted to pave the way for @ref ethash_io_write() |
|||
* |
|||
* @param dirname A null terminated c-string of the path of the ethash |
|||
* data directory. If it does not exist it's created. |
|||
* @param seedhash The seedhash of the current block number |
|||
* @return For possible return values @see enum ethash_io_rc |
|||
*/ |
|||
enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash); |
|||
/**
|
|||
* Fully computes data and writes it to the file on disk. |
|||
* |
|||
* This function should be called after @see ethash_io_prepare() and only if |
|||
* its return value is @c ETHASH_IO_MEMO_MISMATCH. Will write both the full data |
|||
* and the memo file. |
|||
* |
|||
* @param[in] dirname A null terminated c-string of the path of the ethash |
|||
* data directory. Has to exist. |
|||
* @param[in] params An ethash_params object containing the full size |
|||
* and the cache size |
|||
* @param[in] seedhash The seedhash of the current block number |
|||
* @param[in] cache The cache data. Would have usually been calulated by |
|||
* @see ethash_prep_light(). |
|||
* @param[out] data Pass a pointer to uint8_t by reference here. If the |
|||
* function is succesfull then this point to the allocated |
|||
* data calculated by @see ethash_prep_full(). Memory |
|||
* ownership is transfered to the callee. Remember that |
|||
* you eventually need to free this with a call to free(). |
|||
* @param[out] data_size Pass a size_t by value. If the function is succesfull |
|||
* then this will contain the number of bytes allocated |
|||
* for @a data. |
|||
* @return True for success and false in case of failure. |
|||
*/ |
|||
bool ethash_io_write(char const *dirname, |
|||
ethash_params const* params, |
|||
ethash_blockhash_t seedhash, |
|||
void const* cache, |
|||
uint8_t **data, |
|||
size_t *data_size); |
|||
|
|||
static inline void ethash_io_serialize_info(uint32_t revision, |
|||
ethash_blockhash_t seed_hash, |
|||
char *output) |
|||
{ |
|||
// if .info is only consumed locally we don't really care about endianess
|
|||
memcpy(output, &revision, 4); |
|||
memcpy(output + 4, &seed_hash, 32); |
|||
} |
|||
|
|||
static inline char *ethash_io_create_filename(char const *dirname, |
|||
char const* filename, |
|||
size_t filename_length) |
|||
{ |
|||
// in C the cast is not needed, but a C++ compiler will complain for invalid conversion
|
|||
char *name = (char*)malloc(strlen(dirname) + filename_length); |
|||
if (!name) { |
|||
return NULL; |
|||
} |
|||
|
|||
name[0] = '\0'; |
|||
strcat(name, dirname); |
|||
strcat(name, filename); |
|||
return name; |
|||
} |
|||
|
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
@ -0,0 +1,76 @@ |
|||
/*
|
|||
This file is part of ethash. |
|||
|
|||
ethash 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. |
|||
|
|||
ethash 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 ethash. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file io_posix.c
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "io.h" |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <errno.h> |
|||
#include <libgen.h> |
|||
#include <stdio.h> |
|||
#include <unistd.h> |
|||
|
|||
enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) |
|||
{ |
|||
char read_buffer[DAG_MEMO_BYTESIZE]; |
|||
char expect_buffer[DAG_MEMO_BYTESIZE]; |
|||
enum ethash_io_rc ret = ETHASH_IO_FAIL; |
|||
|
|||
// assert directory exists, full owner permissions and read/search for others
|
|||
int rc = mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); |
|||
if (rc == -1 && errno != EEXIST) { |
|||
goto end; |
|||
} |
|||
|
|||
char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); |
|||
if (!memofile) { |
|||
goto end; |
|||
} |
|||
|
|||
// try to open memo file
|
|||
FILE *f = fopen(memofile, "rb"); |
|||
if (!f) { |
|||
// file does not exist, so no checking happens. All is fine.
|
|||
ret = ETHASH_IO_MEMO_MISMATCH; |
|||
goto free_memo; |
|||
} |
|||
|
|||
if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { |
|||
goto close; |
|||
} |
|||
|
|||
ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); |
|||
if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { |
|||
// we have different memo contents so delete the memo file
|
|||
if (unlink(memofile) != 0) { |
|||
goto close; |
|||
} |
|||
ret = ETHASH_IO_MEMO_MISMATCH; |
|||
} |
|||
|
|||
ret = ETHASH_IO_MEMO_MATCH; |
|||
|
|||
close: |
|||
fclose(f); |
|||
free_memo: |
|||
free(memofile); |
|||
end: |
|||
return ret; |
|||
} |
@ -0,0 +1,73 @@ |
|||
/*
|
|||
This file is part of ethash. |
|||
|
|||
ethash 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. |
|||
|
|||
ethash 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 ethash. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file io_win32.c
|
|||
* @author Lefteris Karapetsas <lefteris@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "io.h" |
|||
#include <direct.h> |
|||
#include <errno.h> |
|||
#include <stdio.h> |
|||
|
|||
enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) |
|||
{ |
|||
char read_buffer[DAG_MEMO_BYTESIZE]; |
|||
char expect_buffer[DAG_MEMO_BYTESIZE]; |
|||
enum ethash_io_rc ret = ETHASH_IO_FAIL; |
|||
|
|||
// assert directory exists
|
|||
int rc = _mkdir(dirname); |
|||
if (rc == -1 && errno != EEXIST) { |
|||
goto end; |
|||
} |
|||
|
|||
char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); |
|||
if (!memofile) { |
|||
goto end; |
|||
} |
|||
|
|||
// try to open memo file
|
|||
FILE *f = fopen(memofile, "rb"); |
|||
if (!f) { |
|||
// file does not exist, so no checking happens. All is fine.
|
|||
ret = ETHASH_IO_MEMO_MISMATCH; |
|||
goto free_memo; |
|||
} |
|||
|
|||
if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { |
|||
goto close; |
|||
} |
|||
|
|||
ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); |
|||
if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { |
|||
// we have different memo contents so delete the memo file
|
|||
if (_unlink(memofile) != 0) { |
|||
goto close; |
|||
} |
|||
ret = ETHASH_IO_MEMO_MISMATCH; |
|||
} |
|||
|
|||
ret = ETHASH_IO_MEMO_MATCH; |
|||
|
|||
close: |
|||
fclose(f); |
|||
free_memo: |
|||
free(memofile); |
|||
end: |
|||
return ret; |
|||
} |
@ -0,0 +1,27 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ABI.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "ABI.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
@ -0,0 +1,64 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ABI.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/FixedHash.h> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
template <class T> struct ABISerialiser {}; |
|||
template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } }; |
|||
template <> struct ABISerialiser<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; |
|||
template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; |
|||
template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } }; |
|||
|
|||
inline bytes abiInAux() { return {}; } |
|||
template <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u) |
|||
{ |
|||
return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...); |
|||
} |
|||
|
|||
template <class ... T> bytes abiIn(std::string _id, T const& ... _t) |
|||
{ |
|||
return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); |
|||
} |
|||
|
|||
template <class T> struct ABIDeserialiser {}; |
|||
template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<string32> { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } }; |
|||
|
|||
template <class T> T abiOut(bytes const& _data) |
|||
{ |
|||
bytesConstRef o(&_data); |
|||
return ABIDeserialiser<T>::deserialise(o); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,399 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ClientBase.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <libdevcore/StructuredLogger.h> |
|||
#include "ClientBase.h" |
|||
#include "BlockChain.h" |
|||
#include "Executive.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
State ClientBase::asOf(BlockNumber _h) const |
|||
{ |
|||
if (_h == PendingBlock) |
|||
return postMine(); |
|||
else if (_h == LatestBlock) |
|||
return preMine(); |
|||
return asOf(bc().numberHash(_h)); |
|||
} |
|||
|
|||
void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
prepareForTransaction(); |
|||
|
|||
u256 n = postMine().transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
m_tq.attemptImport(t.rlp()); |
|||
|
|||
StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); |
|||
cnote << "New transaction " << t; |
|||
} |
|||
|
|||
Address ClientBase::submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
prepareForTransaction(); |
|||
|
|||
u256 n = postMine().transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); |
|||
m_tq.attemptImport(t.rlp()); |
|||
|
|||
StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); |
|||
cnote << "New transaction " << t; |
|||
|
|||
return right160(sha3(rlpList(t.sender(), t.nonce()))); |
|||
} |
|||
|
|||
// TODO: remove try/catch, allow exceptions
|
|||
ExecutionResult ClientBase::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) |
|||
{ |
|||
ExecutionResult ret; |
|||
try |
|||
{ |
|||
State temp = asOf(_blockNumber); |
|||
u256 n = temp.transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
ret = temp.execute(bc().lastHashes(), t, Permanence::Reverted); |
|||
} |
|||
catch (...) |
|||
{ |
|||
// TODO: Some sort of notification of failure.
|
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
ExecutionResult ClientBase::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) |
|||
{ |
|||
ExecutionResult ret; |
|||
try |
|||
{ |
|||
State temp = asOf(_blockNumber); |
|||
u256 n = temp.transactionsFrom(toAddress(_secret)); |
|||
// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret));
|
|||
|
|||
Transaction t(_value, _gasPrice, _gas, _data, n, _secret); |
|||
ret = temp.execute(bc().lastHashes(), t, Permanence::Reverted); |
|||
} |
|||
catch (...) |
|||
{ |
|||
// TODO: Some sort of notification of failure.
|
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
u256 ClientBase::balanceAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).balance(_a); |
|||
} |
|||
|
|||
u256 ClientBase::countAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).transactionsFrom(_a); |
|||
} |
|||
|
|||
u256 ClientBase::stateAt(Address _a, u256 _l, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).storage(_a, _l); |
|||
} |
|||
|
|||
bytes ClientBase::codeAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).code(_a); |
|||
} |
|||
|
|||
map<u256, u256> ClientBase::storageAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).storage(_a); |
|||
} |
|||
|
|||
// TODO: remove try/catch, allow exceptions
|
|||
LocalisedLogEntries ClientBase::logs(unsigned _watchId) const |
|||
{ |
|||
LogFilter f; |
|||
try |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
f = m_filters.at(m_watches.at(_watchId).id).filter; |
|||
} |
|||
catch (...) |
|||
{ |
|||
return LocalisedLogEntries(); |
|||
} |
|||
return logs(f); |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::logs(LogFilter const& _f) const |
|||
{ |
|||
LocalisedLogEntries ret; |
|||
unsigned begin = min<unsigned>(bc().number() + 1, (unsigned)_f.latest()); |
|||
unsigned end = min(bc().number(), min(begin, (unsigned)_f.earliest())); |
|||
|
|||
// Handle pending transactions differently as they're not on the block chain.
|
|||
if (begin > bc().number()) |
|||
{ |
|||
State temp = postMine(); |
|||
for (unsigned i = 0; i < temp.pending().size(); ++i) |
|||
{ |
|||
// Might have a transaction that contains a matching log.
|
|||
TransactionReceipt const& tr = temp.receipt(i); |
|||
auto th = temp.pending()[i].sha3(); |
|||
LogEntries le = _f.matches(tr); |
|||
if (le.size()) |
|||
for (unsigned j = 0; j < le.size(); ++j) |
|||
ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, th)); |
|||
} |
|||
begin = bc().number(); |
|||
} |
|||
|
|||
set<unsigned> matchingBlocks; |
|||
for (auto const& i: _f.bloomPossibilities()) |
|||
for (auto u: bc().withBlockBloom(i, end, begin)) |
|||
matchingBlocks.insert(u); |
|||
|
|||
unsigned falsePos = 0; |
|||
for (auto n: matchingBlocks) |
|||
{ |
|||
int total = 0; |
|||
auto h = bc().numberHash(n); |
|||
auto receipts = bc().receipts(h).receipts; |
|||
for (size_t i = 0; i < receipts.size(); i++) |
|||
{ |
|||
TransactionReceipt receipt = receipts[i]; |
|||
if (_f.matches(receipt.bloom())) |
|||
{ |
|||
auto info = bc().info(h); |
|||
auto th = transaction(info.hash, i).sha3(); |
|||
LogEntries le = _f.matches(receipt); |
|||
if (le.size()) |
|||
{ |
|||
total += le.size(); |
|||
for (unsigned j = 0; j < le.size(); ++j) |
|||
ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, th)); |
|||
} |
|||
} |
|||
|
|||
if (!total) |
|||
falsePos++; |
|||
} |
|||
} |
|||
|
|||
cdebug << matchingBlocks.size() << "searched from" << (end - begin) << "skipped; " << falsePos << "false +ves"; |
|||
return ret; |
|||
} |
|||
|
|||
unsigned ClientBase::installWatch(LogFilter const& _f, Reaping _r) |
|||
{ |
|||
h256 h = _f.sha3(); |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
if (!m_filters.count(h)) |
|||
{ |
|||
cwatch << "FFF" << _f << h.abridged(); |
|||
m_filters.insert(make_pair(h, _f)); |
|||
} |
|||
} |
|||
return installWatch(h, _r); |
|||
} |
|||
|
|||
unsigned ClientBase::installWatch(h256 _h, Reaping _r) |
|||
{ |
|||
unsigned ret; |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; |
|||
m_watches[ret] = ClientWatch(_h, _r); |
|||
cwatch << "+++" << ret << _h.abridged(); |
|||
} |
|||
#if INITIAL_STATE_AS_CHANGES |
|||
auto ch = logs(ret); |
|||
if (ch.empty()) |
|||
ch.push_back(InitialChange); |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
swap(m_watches[ret].changes, ch); |
|||
} |
|||
#endif |
|||
return ret; |
|||
} |
|||
|
|||
bool ClientBase::uninstallWatch(unsigned _i) |
|||
{ |
|||
cwatch << "XXX" << _i; |
|||
|
|||
Guard l(x_filtersWatches); |
|||
|
|||
auto it = m_watches.find(_i); |
|||
if (it == m_watches.end()) |
|||
return false; |
|||
auto id = it->second.id; |
|||
m_watches.erase(it); |
|||
|
|||
auto fit = m_filters.find(id); |
|||
if (fit != m_filters.end()) |
|||
if (!--fit->second.refCount) |
|||
{ |
|||
cwatch << "*X*" << fit->first << ":" << fit->second.filter; |
|||
m_filters.erase(fit); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::peekWatch(unsigned _watchId) const |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
|
|||
// cwatch << "peekWatch" << _watchId;
|
|||
auto& w = m_watches.at(_watchId); |
|||
// cwatch << "lastPoll updated to " << chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|||
w.lastPoll = chrono::system_clock::now(); |
|||
return w.changes; |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::checkWatch(unsigned _watchId) |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
LocalisedLogEntries ret; |
|||
|
|||
// cwatch << "checkWatch" << _watchId;
|
|||
auto& w = m_watches.at(_watchId); |
|||
// cwatch << "lastPoll updated to " << chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|||
std::swap(ret, w.changes); |
|||
w.lastPoll = chrono::system_clock::now(); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
h256 ClientBase::hashFromNumber(unsigned _number) const |
|||
{ |
|||
return bc().numberHash(_number); |
|||
} |
|||
|
|||
BlockInfo ClientBase::blockInfo(h256 _hash) const |
|||
{ |
|||
return BlockInfo(bc().block(_hash)); |
|||
} |
|||
|
|||
BlockDetails ClientBase::blockDetails(h256 _hash) const |
|||
{ |
|||
return bc().details(_hash); |
|||
} |
|||
|
|||
Transaction ClientBase::transaction(h256 _transactionHash) const |
|||
{ |
|||
return Transaction(bc().transaction(_transactionHash), CheckSignature::Range); |
|||
} |
|||
|
|||
Transaction ClientBase::transaction(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
if (_i < b[1].itemCount()) |
|||
return Transaction(b[1][_i].data(), CheckSignature::Range); |
|||
else |
|||
return Transaction(); |
|||
} |
|||
|
|||
Transactions ClientBase::transactions(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
Transactions res; |
|||
for (unsigned i = 0; i < b[1].itemCount(); i++) |
|||
res.emplace_back(b[1][i].data(), CheckSignature::Range); |
|||
return res; |
|||
} |
|||
|
|||
TransactionHashes ClientBase::transactionHashes(h256 _blockHash) const |
|||
{ |
|||
return bc().transactionHashes(_blockHash); |
|||
} |
|||
|
|||
BlockInfo ClientBase::uncle(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
if (_i < b[2].itemCount()) |
|||
return BlockInfo::fromHeader(b[2][_i].data()); |
|||
else |
|||
return BlockInfo(); |
|||
} |
|||
|
|||
UncleHashes ClientBase::uncleHashes(h256 _blockHash) const |
|||
{ |
|||
return bc().uncleHashes(_blockHash); |
|||
} |
|||
|
|||
unsigned ClientBase::transactionCount(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
return b[1].itemCount(); |
|||
} |
|||
|
|||
unsigned ClientBase::uncleCount(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
return b[2].itemCount(); |
|||
} |
|||
|
|||
unsigned ClientBase::number() const |
|||
{ |
|||
return bc().number(); |
|||
} |
|||
|
|||
Transactions ClientBase::pending() const |
|||
{ |
|||
return postMine().pending(); |
|||
} |
|||
|
|||
|
|||
StateDiff ClientBase::diff(unsigned _txi, h256 _block) const |
|||
{ |
|||
State st = asOf(_block); |
|||
return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); |
|||
} |
|||
|
|||
StateDiff ClientBase::diff(unsigned _txi, BlockNumber _block) const |
|||
{ |
|||
State st = asOf(_block); |
|||
return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); |
|||
} |
|||
|
|||
Addresses ClientBase::addresses(BlockNumber _block) const |
|||
{ |
|||
Addresses ret; |
|||
for (auto const& i: asOf(_block).addresses()) |
|||
ret.push_back(i.first); |
|||
return ret; |
|||
} |
|||
|
|||
u256 ClientBase::gasLimitRemaining() const |
|||
{ |
|||
return postMine().gasLimitRemaining(); |
|||
} |
|||
|
|||
Address ClientBase::address() const |
|||
{ |
|||
return preMine().address(); |
|||
} |
@ -0,0 +1,170 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ClientBase.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include "Interface.h" |
|||
#include "LogFilter.h" |
|||
|
|||
namespace dev { |
|||
|
|||
namespace eth { |
|||
|
|||
struct InstalledFilter |
|||
{ |
|||
InstalledFilter(LogFilter const& _f): filter(_f) {} |
|||
|
|||
LogFilter filter; |
|||
unsigned refCount = 1; |
|||
LocalisedLogEntries changes; |
|||
}; |
|||
|
|||
static const h256 PendingChangedFilter = u256(0); |
|||
static const h256 ChainChangedFilter = u256(1); |
|||
|
|||
static const LogEntry SpecialLogEntry = LogEntry(Address(), h256s(), bytes()); |
|||
static const LocalisedLogEntry InitialChange(SpecialLogEntry, 0); |
|||
|
|||
struct ClientWatch |
|||
{ |
|||
ClientWatch(): lastPoll(std::chrono::system_clock::now()) {} |
|||
explicit ClientWatch(h256 _id, Reaping _r): id(_id), lastPoll(_r == Reaping::Automatic ? std::chrono::system_clock::now() : std::chrono::system_clock::time_point::max()) {} |
|||
|
|||
h256 id; |
|||
#if INITIAL_STATE_AS_CHANGES |
|||
LocalisedLogEntries changes = LocalisedLogEntries{ InitialChange }; |
|||
#else |
|||
LocalisedLogEntries changes; |
|||
#endif |
|||
mutable std::chrono::system_clock::time_point lastPoll = std::chrono::system_clock::now(); |
|||
}; |
|||
|
|||
struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; |
|||
#define cwatch dev::LogOutputStream<dev::eth::WatchChannel, true>() |
|||
struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; }; |
|||
struct WorkOutChannel: public LogChannel { static const char* name() { return "<W<"; } static const int verbosity = 16; }; |
|||
struct WorkChannel: public LogChannel { static const char* name() { return "-W-"; } static const int verbosity = 16; }; |
|||
#define cwork dev::LogOutputStream<dev::eth::WorkChannel, true>() |
|||
#define cworkin dev::LogOutputStream<dev::eth::WorkInChannel, true>() |
|||
#define cworkout dev::LogOutputStream<dev::eth::WorkOutChannel, true>() |
|||
|
|||
class ClientBase: public dev::eth::Interface |
|||
{ |
|||
public: |
|||
ClientBase() {} |
|||
virtual ~ClientBase() {} |
|||
|
|||
/// Submits the given message-call transaction.
|
|||
virtual void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; |
|||
|
|||
/// Submits a new contract-creation transaction.
|
|||
/// @returns the new contract's address (assuming it all goes through).
|
|||
virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; |
|||
|
|||
/// Makes the given call. Nothing is recorded into the state.
|
|||
virtual ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; |
|||
|
|||
virtual ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; |
|||
|
|||
using Interface::balanceAt; |
|||
using Interface::countAt; |
|||
using Interface::stateAt; |
|||
using Interface::codeAt; |
|||
using Interface::storageAt; |
|||
|
|||
virtual u256 balanceAt(Address _a, BlockNumber _block) const override; |
|||
virtual u256 countAt(Address _a, BlockNumber _block) const override; |
|||
virtual u256 stateAt(Address _a, u256 _l, BlockNumber _block) const override; |
|||
virtual bytes codeAt(Address _a, BlockNumber _block) const override; |
|||
virtual std::map<u256, u256> storageAt(Address _a, BlockNumber _block) const override; |
|||
|
|||
virtual LocalisedLogEntries logs(unsigned _watchId) const override; |
|||
virtual LocalisedLogEntries logs(LogFilter const& _filter) const override; |
|||
|
|||
/// Install, uninstall and query watches.
|
|||
virtual unsigned installWatch(LogFilter const& _filter, Reaping _r = Reaping::Automatic) override; |
|||
virtual unsigned installWatch(h256 _filterId, Reaping _r = Reaping::Automatic) override; |
|||
virtual bool uninstallWatch(unsigned _watchId) override; |
|||
virtual LocalisedLogEntries peekWatch(unsigned _watchId) const override; |
|||
virtual LocalisedLogEntries checkWatch(unsigned _watchId) override; |
|||
|
|||
virtual h256 hashFromNumber(unsigned _number) const override; |
|||
virtual eth::BlockInfo blockInfo(h256 _hash) const override; |
|||
virtual eth::BlockDetails blockDetails(h256 _hash) const override; |
|||
virtual eth::Transaction transaction(h256 _transactionHash) const override; |
|||
virtual eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; |
|||
virtual eth::Transactions transactions(h256 _blockHash) const override; |
|||
virtual eth::TransactionHashes transactionHashes(h256 _blockHash) const override; |
|||
virtual eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; |
|||
virtual eth::UncleHashes uncleHashes(h256 _blockHash) const override; |
|||
virtual unsigned transactionCount(h256 _blockHash) const override; |
|||
virtual unsigned uncleCount(h256 _blockHash) const override; |
|||
virtual unsigned number() const override; |
|||
virtual eth::Transactions pending() const override; |
|||
|
|||
using Interface::diff; |
|||
virtual StateDiff diff(unsigned _txi, h256 _block) const override; |
|||
virtual StateDiff diff(unsigned _txi, BlockNumber _block) const override; |
|||
|
|||
using Interface::addresses; |
|||
virtual Addresses addresses(BlockNumber _block) const override; |
|||
virtual u256 gasLimitRemaining() const override; |
|||
|
|||
/// Set the coinbase address
|
|||
virtual void setAddress(Address _us) = 0; |
|||
|
|||
/// Get the coinbase address
|
|||
virtual Address address() const override; |
|||
|
|||
/// TODO: consider moving it to a separate interface
|
|||
|
|||
virtual void setMiningThreads(unsigned _threads) override { (void)_threads; BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::setMiningThreads")); } |
|||
virtual unsigned miningThreads() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningThreads")); } |
|||
virtual void startMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::startMining")); } |
|||
virtual void stopMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::stopMining")); } |
|||
virtual bool isMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::isMining")); } |
|||
virtual eth::MineProgress miningProgress() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningProgress")); } |
|||
virtual std::pair<h256, u256> getWork() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::getWork")); } |
|||
virtual bool submitWork(eth::ProofOfWork::Proof const&) override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::submitWork")); } |
|||
|
|||
State asOf(BlockNumber _h) const; |
|||
|
|||
protected: |
|||
/// The interface that must be implemented in any class deriving this.
|
|||
/// {
|
|||
virtual BlockChain const& bc() const = 0; |
|||
virtual State asOf(h256 const& _h) const = 0; |
|||
virtual State preMine() const = 0; |
|||
virtual State postMine() const = 0; |
|||
virtual void prepareForTransaction() = 0; |
|||
/// }
|
|||
|
|||
TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain.
|
|||
|
|||
// filters
|
|||
mutable Mutex x_filtersWatches; ///< Our lock.
|
|||
std::map<h256, InstalledFilter> m_filters; ///< The dictionary of filters that are active.
|
|||
std::map<unsigned, ClientWatch> m_watches; ///< Each and every watch - these reference a filter.
|
|||
}; |
|||
|
|||
}} |
@ -0,0 +1,135 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Assembly.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "AssemblyItem.h" |
|||
#include <fstream> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const |
|||
{ |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
case Tag: // 1 byte for the JUMPDEST
|
|||
return 1; |
|||
case PushString: |
|||
return 33; |
|||
case Push: |
|||
return 1 + max<unsigned>(1, dev::bytesRequired(m_data)); |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
return 4; // worst case: a 16MB program
|
|||
case PushTag: |
|||
case PushData: |
|||
case PushSub: |
|||
return 1 + _addressLength; |
|||
default: |
|||
break; |
|||
} |
|||
BOOST_THROW_EXCEPTION(InvalidOpcode()); |
|||
} |
|||
|
|||
int AssemblyItem::deposit() const |
|||
{ |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
return instructionInfo(instruction()).ret - instructionInfo(instruction()).args; |
|||
case Push: |
|||
case PushString: |
|||
case PushTag: |
|||
case PushData: |
|||
case PushSub: |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
return 1; |
|||
case Tag: |
|||
return 0; |
|||
default:; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
string AssemblyItem::getJumpTypeAsString() const |
|||
{ |
|||
switch (m_jumpType) |
|||
{ |
|||
case JumpType::IntoFunction: |
|||
return "[in]"; |
|||
case JumpType::OutOfFunction: |
|||
return "[out]"; |
|||
case JumpType::Ordinary: |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) |
|||
{ |
|||
case Operation: |
|||
_out << " " << instructionInfo(_item.instruction()).name; |
|||
if (_item.instruction() == eth::Instruction::JUMP || _item.instruction() == eth::Instruction::JUMPI) |
|||
_out << "\t" << _item.getJumpTypeAsString(); |
|||
break; |
|||
case Push: |
|||
_out << " PUSH " << hex << _item.data(); |
|||
break; |
|||
case PushString: |
|||
_out << " PushString" << hex << (unsigned)_item.data(); |
|||
break; |
|||
case PushTag: |
|||
_out << " PushTag " << _item.data(); |
|||
break; |
|||
case Tag: |
|||
_out << " Tag " << _item.data(); |
|||
break; |
|||
case PushData: |
|||
_out << " PushData " << hex << (unsigned)_item.data(); |
|||
break; |
|||
case PushSub: |
|||
_out << " PushSub " << hex << h256(_item.data()).abridged(); |
|||
break; |
|||
case PushSubSize: |
|||
_out << " PushSubSize " << hex << h256(_item.data()).abridged(); |
|||
break; |
|||
case PushProgramSize: |
|||
_out << " PushProgramSize"; |
|||
break; |
|||
case UndefinedItem: |
|||
_out << " ???"; |
|||
break; |
|||
default: |
|||
BOOST_THROW_EXCEPTION(InvalidOpcode()); |
|||
} |
|||
return _out; |
|||
} |
|||
|
|||
ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) |
|||
{ |
|||
for (AssemblyItem const& i: _i) |
|||
_out << i; |
|||
return _out; |
|||
} |
@ -0,0 +1,93 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Assembly.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <iostream> |
|||
#include <sstream> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Assertions.h> |
|||
#include <libevmcore/SourceLocation.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include "Exceptions.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData }; |
|||
|
|||
class Assembly; |
|||
|
|||
class AssemblyItem |
|||
{ |
|||
public: |
|||
enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; |
|||
|
|||
AssemblyItem(u256 _push): m_type(Push), m_data(_push) {} |
|||
AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {} |
|||
AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {} |
|||
|
|||
AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } |
|||
AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } |
|||
|
|||
AssemblyItemType type() const { return m_type; } |
|||
u256 const& data() const { return m_data; } |
|||
void setType(AssemblyItemType const _type) { m_type = _type; } |
|||
void setData(u256 const& _data) { m_data = _data; } |
|||
|
|||
/// @returns the instruction of this item (only valid if type() == Operation)
|
|||
Instruction instruction() const { return Instruction(byte(m_data)); } |
|||
|
|||
/// @returns true iff the type and data of the items are equal.
|
|||
bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; } |
|||
bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); } |
|||
|
|||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
|||
/// the value of a jump tag takes @a _addressLength bytes.
|
|||
unsigned bytesRequired(unsigned _addressLength) const; |
|||
int deposit() const; |
|||
|
|||
bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } |
|||
void setLocation(SourceLocation const& _location) { m_location = _location; } |
|||
SourceLocation const& getLocation() const { return m_location; } |
|||
|
|||
void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } |
|||
JumpType getJumpType() const { return m_jumpType; } |
|||
std::string getJumpTypeAsString() const; |
|||
|
|||
private: |
|||
AssemblyItemType m_type; |
|||
u256 m_data; |
|||
SourceLocation m_location; |
|||
JumpType m_jumpType = JumpType::Ordinary; |
|||
}; |
|||
|
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
using AssemblyItemsConstRef = vector_ref<AssemblyItem const>; |
|||
|
|||
std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item); |
|||
std::ostream& operator<<(std::ostream& _out, AssemblyItemsConstRef _i); |
|||
inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _i) { return operator<<(_out, AssemblyItemsConstRef(&_i)); } |
|||
|
|||
} |
|||
} |
@ -0,0 +1,558 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file CommonSubexpressionEliminator.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#include <functional> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <libevmcore/CommonSubexpressionEliminator.h> |
|||
#include <libevmcore/AssemblyItem.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems() |
|||
{ |
|||
optimizeBreakingItem(); |
|||
|
|||
map<int, ExpressionClasses::Id> initialStackContents; |
|||
map<int, ExpressionClasses::Id> targetStackContents; |
|||
int minHeight = m_stackHeight + 1; |
|||
if (!m_stackElements.empty()) |
|||
minHeight = min(minHeight, m_stackElements.begin()->first); |
|||
for (int height = minHeight; height <= 0; ++height) |
|||
initialStackContents[height] = initialStackElement(height); |
|||
for (int height = minHeight; height <= m_stackHeight; ++height) |
|||
targetStackContents[height] = stackElement(height); |
|||
|
|||
// Debug info:
|
|||
//stream(cout, initialStackContents, targetStackContents);
|
|||
|
|||
AssemblyItems items = CSECodeGenerator(m_expressionClasses, m_storeOperations).generateCode( |
|||
initialStackContents, |
|||
targetStackContents |
|||
); |
|||
if (m_breakingItem) |
|||
items.push_back(*m_breakingItem); |
|||
return items; |
|||
} |
|||
|
|||
ostream& CommonSubexpressionEliminator::stream( |
|||
ostream& _out, |
|||
map<int, ExpressionClasses::Id> _initialStack, |
|||
map<int, ExpressionClasses::Id> _targetStack |
|||
) const |
|||
{ |
|||
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) |
|||
{ |
|||
auto const& expr = m_expressionClasses.representative(_id); |
|||
_out << " " << dec << _id << ": " << *expr.item; |
|||
if (expr.sequenceNumber) |
|||
_out << "@" << dec << expr.sequenceNumber; |
|||
_out << "("; |
|||
for (ExpressionClasses::Id arg: expr.arguments) |
|||
_out << dec << arg << ","; |
|||
_out << ")" << endl; |
|||
}; |
|||
|
|||
_out << "Optimizer analysis:" << endl; |
|||
_out << "Final stack height: " << dec << m_stackHeight << endl; |
|||
_out << "Equivalence classes: " << endl; |
|||
for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) |
|||
streamExpressionClass(_out, eqClass); |
|||
|
|||
_out << "Initial stack: " << endl; |
|||
for (auto const& it: _initialStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamExpressionClass(_out, it.second); |
|||
} |
|||
_out << "Target stack: " << endl; |
|||
for (auto const& it: _targetStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamExpressionClass(_out, it.second); |
|||
} |
|||
|
|||
return _out; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _copyItem) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
{ |
|||
assertThrow(_item.deposit() == 1, InvalidDeposit, ""); |
|||
setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}, _copyItem)); |
|||
} |
|||
else |
|||
{ |
|||
Instruction instruction = _item.instruction(); |
|||
InstructionInfo info = instructionInfo(instruction); |
|||
if (SemanticInformation::isDupInstruction(_item)) |
|||
setStackElement( |
|||
m_stackHeight + 1, |
|||
stackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1)) |
|||
); |
|||
else if (SemanticInformation::isSwapInstruction(_item)) |
|||
swapStackElements( |
|||
m_stackHeight, |
|||
m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1) |
|||
); |
|||
else if (instruction != Instruction::POP) |
|||
{ |
|||
vector<ExpressionClasses::Id> arguments(info.args); |
|||
for (int i = 0; i < info.args; ++i) |
|||
arguments[i] = stackElement(m_stackHeight - i); |
|||
if (_item.instruction() == Instruction::SSTORE) |
|||
storeInStorage(arguments[0], arguments[1]); |
|||
else if (_item.instruction() == Instruction::SLOAD) |
|||
setStackElement(m_stackHeight + _item.deposit(), loadFromStorage(arguments[0])); |
|||
else if (_item.instruction() == Instruction::MSTORE) |
|||
storeInMemory(arguments[0], arguments[1]); |
|||
else if (_item.instruction() == Instruction::MLOAD) |
|||
setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0])); |
|||
else |
|||
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem)); |
|||
} |
|||
m_stackHeight += _item.deposit(); |
|||
} |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::optimizeBreakingItem() |
|||
{ |
|||
if (!m_breakingItem || *m_breakingItem != AssemblyItem(Instruction::JUMPI)) |
|||
return; |
|||
|
|||
using Id = ExpressionClasses::Id; |
|||
static AssemblyItem s_jump = Instruction::JUMP; |
|||
|
|||
Id condition = stackElement(m_stackHeight - 1); |
|||
Id zero = m_expressionClasses.find(u256(0)); |
|||
if (m_expressionClasses.knownToBeDifferent(condition, zero)) |
|||
{ |
|||
feedItem(Instruction::SWAP1, true); |
|||
feedItem(Instruction::POP, true); |
|||
m_breakingItem = &s_jump; |
|||
return; |
|||
} |
|||
Id negatedCondition = m_expressionClasses.find(Instruction::ISZERO, {condition}); |
|||
if (m_expressionClasses.knownToBeDifferent(negatedCondition, zero)) |
|||
{ |
|||
feedItem(Instruction::POP, true); |
|||
feedItem(Instruction::POP, true); |
|||
m_breakingItem = nullptr; |
|||
} |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) |
|||
{ |
|||
m_stackElements[_stackHeight] = _class; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) |
|||
{ |
|||
assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements."); |
|||
// ensure they are created
|
|||
stackElement(_stackHeightA); |
|||
stackElement(_stackHeightB); |
|||
|
|||
swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]); |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::stackElement(int _stackHeight) |
|||
{ |
|||
if (m_stackElements.count(_stackHeight)) |
|||
return m_stackElements.at(_stackHeight); |
|||
// Stack element not found (not assigned yet), create new equivalence class.
|
|||
return m_stackElements[_stackHeight] = initialStackElement(_stackHeight); |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _stackHeight) |
|||
{ |
|||
assertThrow(_stackHeight <= 0, OptimizerException, "Initial stack element of positive height requested."); |
|||
assertThrow(_stackHeight > -16, StackTooDeepException, ""); |
|||
// This is a special assembly item that refers to elements pre-existing on the initial stack.
|
|||
return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) |
|||
{ |
|||
if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value) |
|||
// do not execute the storage if we know that the value is already there
|
|||
return; |
|||
m_sequenceNumber++; |
|||
decltype(m_storageContent) storageContents; |
|||
// copy over values at points where we know that they are different from _slot
|
|||
for (auto const& storageItem: m_storageContent) |
|||
if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot)) |
|||
storageContents.insert(storageItem); |
|||
m_storageContent = move(storageContents); |
|||
ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber); |
|||
m_storeOperations.push_back(StoreOperation(StoreOperation::Storage, _slot, m_sequenceNumber, id)); |
|||
m_storageContent[_slot] = _value; |
|||
// increment a second time so that we get unique sequence numbers for writes
|
|||
m_sequenceNumber++; |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionClasses::Id _slot) |
|||
{ |
|||
if (m_storageContent.count(_slot)) |
|||
return m_storageContent.at(_slot); |
|||
else |
|||
return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber); |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) |
|||
{ |
|||
if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) |
|||
// do not execute the store if we know that the value is already there
|
|||
return; |
|||
m_sequenceNumber++; |
|||
decltype(m_memoryContent) memoryContents; |
|||
// copy over values at points where we know that they are different from _slot by at least 32
|
|||
for (auto const& memoryItem: m_memoryContent) |
|||
if (m_expressionClasses.knownToBeDifferentBy32(memoryItem.first, _slot)) |
|||
memoryContents.insert(memoryItem); |
|||
m_memoryContent = move(memoryContents); |
|||
ExpressionClasses::Id id = m_expressionClasses.find(Instruction::MSTORE, {_slot, _value}, true, m_sequenceNumber); |
|||
m_storeOperations.push_back(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id)); |
|||
m_memoryContent[_slot] = _value; |
|||
// increment a second time so that we get unique sequence numbers for writes
|
|||
m_sequenceNumber++; |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionClasses::Id _slot) |
|||
{ |
|||
if (m_memoryContent.count(_slot)) |
|||
return m_memoryContent.at(_slot); |
|||
else |
|||
return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber); |
|||
} |
|||
|
|||
CSECodeGenerator::CSECodeGenerator( |
|||
ExpressionClasses& _expressionClasses, |
|||
vector<CSECodeGenerator::StoreOperation> const& _storeOperations |
|||
): |
|||
m_expressionClasses(_expressionClasses) |
|||
{ |
|||
for (auto const& store: _storeOperations) |
|||
m_storeOperations[make_pair(store.target, store.slot)].push_back(store); |
|||
} |
|||
|
|||
AssemblyItems CSECodeGenerator::generateCode( |
|||
map<int, ExpressionClasses::Id> const& _initialStack, |
|||
map<int, ExpressionClasses::Id> const& _targetStackContents |
|||
) |
|||
{ |
|||
m_stack = _initialStack; |
|||
for (auto const& item: m_stack) |
|||
if (!m_classPositions.count(item.second)) |
|||
m_classPositions[item.second] = item.first; |
|||
|
|||
// @todo: provide information about the positions of copies of class elements
|
|||
|
|||
// generate the dependency graph starting from final storage and memory writes and target stack contents
|
|||
for (auto const& p: m_storeOperations) |
|||
addDependencies(p.second.back().expression); |
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
m_finalClasses.insert(targetItem.second); |
|||
addDependencies(targetItem.second); |
|||
} |
|||
|
|||
// store all needed sequenced expressions
|
|||
set<pair<unsigned, ExpressionClasses::Id>> sequencedExpressions; |
|||
for (auto const& p: m_neededBy) |
|||
for (auto id: {p.first, p.second}) |
|||
if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) |
|||
sequencedExpressions.insert(make_pair(seqNr, id)); |
|||
|
|||
// Perform all operations on storage and memory in order, if they are needed.
|
|||
for (auto const& seqAndId: sequencedExpressions) |
|||
if (!m_classPositions.count(seqAndId.second)) |
|||
generateClassElement(seqAndId.second, true); |
|||
|
|||
// generate the target stack elements
|
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
int position = generateClassElement(targetItem.second); |
|||
assertThrow(position != c_invalidPosition, OptimizerException, ""); |
|||
if (position == targetItem.first) |
|||
continue; |
|||
if (position < targetItem.first) |
|||
// it is already at its target, we need another copy
|
|||
appendDup(position); |
|||
else |
|||
appendOrRemoveSwap(position); |
|||
appendOrRemoveSwap(targetItem.first); |
|||
} |
|||
|
|||
// remove surplus elements
|
|||
while (removeStackTopIfPossible()) |
|||
{ |
|||
// no-op
|
|||
} |
|||
|
|||
// check validity
|
|||
int finalHeight = 0; |
|||
if (!_targetStackContents.empty()) |
|||
// have target stack, so its height should be the final height
|
|||
finalHeight = (--_targetStackContents.end())->first; |
|||
else if (!_initialStack.empty()) |
|||
// no target stack, only erase the initial stack
|
|||
finalHeight = _initialStack.begin()->first - 1; |
|||
else |
|||
// neither initial no target stack, no change in height
|
|||
finalHeight = 0; |
|||
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height."); |
|||
return m_generatedItems; |
|||
} |
|||
|
|||
void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) |
|||
{ |
|||
if (m_neededBy.count(_c)) |
|||
return; // we already computed the dependencies for _c
|
|||
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); |
|||
for (ExpressionClasses::Id argument: expr.arguments) |
|||
{ |
|||
addDependencies(argument); |
|||
m_neededBy.insert(make_pair(argument, _c)); |
|||
} |
|||
if (expr.item->type() == Operation && ( |
|||
expr.item->instruction() == Instruction::SLOAD || |
|||
expr.item->instruction() == Instruction::MLOAD |
|||
)) |
|||
{ |
|||
// this loads an unknown value from storage or memory and thus, in addition to its
|
|||
// arguments, depends on all store operations to addresses where we do not know that
|
|||
// they are different that occur before this load
|
|||
StoreOperation::Target target = expr.item->instruction() == Instruction::SLOAD ? |
|||
StoreOperation::Storage : StoreOperation::Memory; |
|||
ExpressionClasses::Id slotToLoadFrom = expr.arguments.at(0); |
|||
for (auto const& p: m_storeOperations) |
|||
{ |
|||
if (p.first.first != target) |
|||
continue; |
|||
ExpressionClasses::Id slot = p.first.second; |
|||
StoreOperations const& storeOps = p.second; |
|||
if (storeOps.front().sequenceNumber > expr.sequenceNumber) |
|||
continue; |
|||
if ( |
|||
(target == StoreOperation::Memory && m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom)) || |
|||
(target == StoreOperation::Storage && m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom)) |
|||
) |
|||
continue; |
|||
// note that store and load never have the same sequence number
|
|||
ExpressionClasses::Id latestStore = storeOps.front().expression; |
|||
for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) |
|||
if (it->sequenceNumber < expr.sequenceNumber) |
|||
latestStore = it->expression; |
|||
addDependencies(latestStore); |
|||
m_neededBy.insert(make_pair(latestStore, _c)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced) |
|||
{ |
|||
// do some cleanup
|
|||
removeStackTopIfPossible(); |
|||
|
|||
if (m_classPositions.count(_c)) |
|||
{ |
|||
assertThrow( |
|||
m_classPositions[_c] != c_invalidPosition, |
|||
OptimizerException, |
|||
"Element already removed but still needed." |
|||
); |
|||
return m_classPositions[_c]; |
|||
} |
|||
ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c); |
|||
assertThrow( |
|||
_allowSequenced || expr.sequenceNumber == 0, |
|||
OptimizerException, |
|||
"Sequence constrained operation requested out of sequence." |
|||
); |
|||
ExpressionClasses::Ids const& arguments = expr.arguments; |
|||
for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments)) |
|||
generateClassElement(arg); |
|||
|
|||
// The arguments are somewhere on the stack now, so it remains to move them at the correct place.
|
|||
// This is quite difficult as sometimes, the values also have to removed in this process
|
|||
// (if canBeRemoved() returns true) and the two arguments can be equal. For now, this is
|
|||
// implemented for every single case for combinations of up to two arguments manually.
|
|||
if (arguments.size() == 1) |
|||
{ |
|||
if (canBeRemoved(arguments[0], _c)) |
|||
appendOrRemoveSwap(classElementPosition(arguments[0])); |
|||
else |
|||
appendDup(classElementPosition(arguments[0])); |
|||
} |
|||
else if (arguments.size() == 2) |
|||
{ |
|||
if (canBeRemoved(arguments[1], _c)) |
|||
{ |
|||
appendOrRemoveSwap(classElementPosition(arguments[1])); |
|||
if (arguments[0] == arguments[1]) |
|||
appendDup(m_stackHeight); |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendOrRemoveSwap(m_stackHeight - 1); |
|||
appendOrRemoveSwap(classElementPosition(arguments[0])); |
|||
} |
|||
else |
|||
appendDup(classElementPosition(arguments[0])); |
|||
} |
|||
else |
|||
{ |
|||
if (arguments[0] == arguments[1]) |
|||
{ |
|||
appendDup(classElementPosition(arguments[0])); |
|||
appendDup(m_stackHeight); |
|||
} |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendOrRemoveSwap(classElementPosition(arguments[0])); |
|||
appendDup(classElementPosition(arguments[1])); |
|||
appendOrRemoveSwap(m_stackHeight - 1); |
|||
} |
|||
else |
|||
{ |
|||
appendDup(classElementPosition(arguments[1])); |
|||
appendDup(classElementPosition(arguments[0])); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
assertThrow( |
|||
arguments.size() <= 2, |
|||
OptimizerException, |
|||
"Opcodes with more than two arguments not implemented yet." |
|||
); |
|||
for (size_t i = 0; i < arguments.size(); ++i) |
|||
assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." ); |
|||
|
|||
while (SemanticInformation::isCommutativeOperation(*expr.item) && |
|||
!m_generatedItems.empty() && |
|||
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1)) |
|||
// this will not append a swap but remove the one that is already there
|
|||
appendOrRemoveSwap(m_stackHeight - 1); |
|||
for (auto arg: arguments) |
|||
if (canBeRemoved(arg, _c)) |
|||
m_classPositions[arg] = c_invalidPosition; |
|||
for (size_t i = 0; i < arguments.size(); ++i) |
|||
m_stack.erase(m_stackHeight - i); |
|||
appendItem(*expr.item); |
|||
if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1) |
|||
{ |
|||
m_stack[m_stackHeight] = _c; |
|||
return m_classPositions[_c] = m_stackHeight; |
|||
} |
|||
else |
|||
{ |
|||
assertThrow( |
|||
instructionInfo(expr.item->instruction()).ret == 0, |
|||
OptimizerException, |
|||
"Invalid number of return values." |
|||
); |
|||
return m_classPositions[_c] = c_invalidPosition; |
|||
} |
|||
} |
|||
|
|||
int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const |
|||
{ |
|||
assertThrow( |
|||
m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition, |
|||
OptimizerException, |
|||
"Element requested but is not present." |
|||
); |
|||
return m_classPositions.at(_id); |
|||
} |
|||
|
|||
bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result) |
|||
{ |
|||
// Returns false if _element is finally needed or is needed by a class that has not been
|
|||
// computed yet. Note that m_classPositions also includes classes that were deleted in the meantime.
|
|||
if (m_finalClasses.count(_element)) |
|||
return false; |
|||
|
|||
auto range = m_neededBy.equal_range(_element); |
|||
for (auto it = range.first; it != range.second; ++it) |
|||
if (it->second != _result && !m_classPositions.count(it->second)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
bool CSECodeGenerator::removeStackTopIfPossible() |
|||
{ |
|||
if (m_stack.empty()) |
|||
return false; |
|||
assertThrow(m_stack.count(m_stackHeight), OptimizerException, ""); |
|||
ExpressionClasses::Id top = m_stack[m_stackHeight]; |
|||
if (!canBeRemoved(top)) |
|||
return false; |
|||
m_generatedItems.push_back(AssemblyItem(Instruction::POP)); |
|||
m_stack.erase(m_stackHeight); |
|||
m_stackHeight--; |
|||
return true; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendDup(int _fromPosition) |
|||
{ |
|||
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); |
|||
int instructionNum = 1 + m_stackHeight - _fromPosition; |
|||
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); |
|||
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); |
|||
appendItem(AssemblyItem(dupInstruction(instructionNum))); |
|||
m_stack[m_stackHeight] = m_stack[_fromPosition]; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition) |
|||
{ |
|||
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); |
|||
if (_fromPosition == m_stackHeight) |
|||
return; |
|||
int instructionNum = m_stackHeight - _fromPosition; |
|||
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); |
|||
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); |
|||
appendItem(AssemblyItem(swapInstruction(instructionNum))); |
|||
// The value of a class can be present in multiple locations on the stack. We only update the
|
|||
// "canonical" one that is tracked by m_classPositions
|
|||
if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight) |
|||
m_classPositions[m_stack[m_stackHeight]] = _fromPosition; |
|||
if (m_classPositions[m_stack[_fromPosition]] == _fromPosition) |
|||
m_classPositions[m_stack[_fromPosition]] = m_stackHeight; |
|||
swap(m_stack[m_stackHeight], m_stack[_fromPosition]); |
|||
if (m_generatedItems.size() >= 2 && |
|||
SemanticInformation::isSwapInstruction(m_generatedItems.back()) && |
|||
*(m_generatedItems.end() - 2) == m_generatedItems.back()) |
|||
{ |
|||
m_generatedItems.pop_back(); |
|||
m_generatedItems.pop_back(); |
|||
} |
|||
} |
|||
|
|||
void CSECodeGenerator::appendItem(AssemblyItem const& _item) |
|||
{ |
|||
m_generatedItems.push_back(_item); |
|||
m_stackHeight += _item.deposit(); |
|||
} |
@ -0,0 +1,228 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file CommonSubexpressionEliminator.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <set> |
|||
#include <tuple> |
|||
#include <ostream> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcore/Exceptions.h> |
|||
#include <libevmcore/ExpressionClasses.h> |
|||
#include <libevmcore/SemanticInformation.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class AssemblyItem; |
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
|
|||
/**
|
|||
* Optimizer step that performs common subexpression elimination and stack reorganisation, |
|||
* i.e. it tries to infer equality among expressions and compute the values of two expressions |
|||
* known to be equal only once. |
|||
* |
|||
* The general workings are that for each assembly item that is fed into the eliminator, an |
|||
* equivalence class is derived from the operation and the equivalence class of its arguments. |
|||
* DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these |
|||
* classes are determined. |
|||
* |
|||
* When the list of optimized items is requested, they are generated in a bottom-up fashion, |
|||
* adding code for equivalence classes that were not yet computed. |
|||
*/ |
|||
class CommonSubexpressionEliminator |
|||
{ |
|||
public: |
|||
struct StoreOperation |
|||
{ |
|||
enum Target { Memory, Storage }; |
|||
StoreOperation( |
|||
Target _target, |
|||
ExpressionClasses::Id _slot, |
|||
unsigned _sequenceNumber, |
|||
ExpressionClasses::Id _expression |
|||
): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} |
|||
Target target; |
|||
ExpressionClasses::Id slot; |
|||
unsigned sequenceNumber; |
|||
ExpressionClasses::Id expression; |
|||
}; |
|||
|
|||
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
|
|||
/// item that must be fed into a new instance of the eliminator.
|
|||
template <class _AssemblyItemIterator> |
|||
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); |
|||
|
|||
/// @returns the resulting items after optimization.
|
|||
AssemblyItems getOptimizedItems(); |
|||
|
|||
/// Streams debugging information to @a _out.
|
|||
std::ostream& stream( |
|||
std::ostream& _out, |
|||
std::map<int, ExpressionClasses::Id> _initialStack = std::map<int, ExpressionClasses::Id>(), |
|||
std::map<int, ExpressionClasses::Id> _targetStack = std::map<int, ExpressionClasses::Id>() |
|||
) const; |
|||
|
|||
private: |
|||
/// Feeds the item into the system for analysis.
|
|||
void feedItem(AssemblyItem const& _item, bool _copyItem = false); |
|||
|
|||
/// Tries to optimize the item that breaks the basic block at the end.
|
|||
void optimizeBreakingItem(); |
|||
|
|||
/// Simplifies the given item using
|
|||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
|||
void setStackElement(int _stackHeight, ExpressionClasses::Id _class); |
|||
/// Swaps the given stack elements in their next sequence number.
|
|||
void swapStackElements(int _stackHeightA, int _stackHeightB); |
|||
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
|||
/// one if it does not exist yet).
|
|||
ExpressionClasses::Id stackElement(int _stackHeight); |
|||
/// @returns the equivalence class id of the special initial stack element at the given height
|
|||
/// (must not be positive).
|
|||
ExpressionClasses::Id initialStackElement(int _stackHeight); |
|||
|
|||
/// Increments the sequence number, deletes all storage information that might be overwritten
|
|||
/// and stores the new value at the given slot.
|
|||
void storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); |
|||
/// Retrieves the current value at the given slot in storage or creates a new special sload class.
|
|||
ExpressionClasses::Id loadFromStorage(ExpressionClasses::Id _slot); |
|||
/// Increments the sequence number, deletes all memory information that might be overwritten
|
|||
/// and stores the new value at the given slot.
|
|||
void storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); |
|||
/// Retrieves the current value at the given slot in memory or creates a new special mload class.
|
|||
ExpressionClasses::Id loadFromMemory(ExpressionClasses::Id _slot); |
|||
|
|||
|
|||
/// Current stack height, can be negative.
|
|||
int m_stackHeight = 0; |
|||
/// Current stack layout, mapping stack height -> equivalence class
|
|||
std::map<int, ExpressionClasses::Id> m_stackElements; |
|||
/// Current sequence number, this is incremented with each modification to storage or memory.
|
|||
unsigned m_sequenceNumber = 1; |
|||
/// Knowledge about storage content.
|
|||
std::map<ExpressionClasses::Id, ExpressionClasses::Id> m_storageContent; |
|||
/// Knowledge about memory content. Keys are memory addresses, note that the values overlap
|
|||
/// and are not contained here if they are not completely known.
|
|||
std::map<ExpressionClasses::Id, ExpressionClasses::Id> m_memoryContent; |
|||
/// Keeps information about which storage or memory slots were written to at which sequence
|
|||
/// number with what instruction.
|
|||
std::vector<StoreOperation> m_storeOperations; |
|||
/// Structure containing the classes of equivalent expressions.
|
|||
ExpressionClasses m_expressionClasses; |
|||
|
|||
/// The item that breaks the basic block, can be nullptr.
|
|||
/// It is usually appended to the block but can be optimized in some cases.
|
|||
AssemblyItem const* m_breakingItem = nullptr; |
|||
}; |
|||
|
|||
/**
|
|||
* Unit that generates code from current stack layout, target stack layout and information about |
|||
* the equivalence classes. |
|||
*/ |
|||
class CSECodeGenerator |
|||
{ |
|||
public: |
|||
using StoreOperation = CommonSubexpressionEliminator::StoreOperation; |
|||
using StoreOperations = std::vector<StoreOperation>; |
|||
|
|||
/// Initializes the code generator with the given classes and store operations.
|
|||
/// The store operations have to be sorted by sequence number in ascending order.
|
|||
CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); |
|||
|
|||
/// @returns the assembly items generated from the given requirements
|
|||
/// @param _initialStack current contents of the stack (up to stack height of zero)
|
|||
/// @param _targetStackContents final contents of the stack, by stack height relative to initial
|
|||
/// @note should only be called once on each object.
|
|||
AssemblyItems generateCode( |
|||
std::map<int, ExpressionClasses::Id> const& _initialStack, |
|||
std::map<int, ExpressionClasses::Id> const& _targetStackContents |
|||
); |
|||
|
|||
private: |
|||
/// Recursively discovers all dependencies to @a m_requests.
|
|||
void addDependencies(ExpressionClasses::Id _c); |
|||
|
|||
/// Produce code that generates the given element if it is not yet present.
|
|||
/// @returns the stack position of the element or c_invalidPosition if it does not actually
|
|||
/// generate a value on the stack.
|
|||
/// @param _allowSequenced indicates that sequence-constrained operations are allowed
|
|||
int generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced = false); |
|||
/// @returns the position of the representative of the given id on the stack.
|
|||
/// @note throws an exception if it is not on the stack.
|
|||
int classElementPosition(ExpressionClasses::Id _id) const; |
|||
|
|||
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
|
|||
bool canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result = ExpressionClasses::Id(-1)); |
|||
|
|||
/// Appends code to remove the topmost stack element if it can be removed.
|
|||
bool removeStackTopIfPossible(); |
|||
|
|||
/// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position.
|
|||
void appendDup(int _fromPosition); |
|||
/// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position.
|
|||
/// @note this might also remove the last item if it exactly the same swap instruction.
|
|||
void appendOrRemoveSwap(int _fromPosition); |
|||
/// Appends the given assembly item.
|
|||
void appendItem(AssemblyItem const& _item); |
|||
|
|||
static const int c_invalidPosition = -0x7fffffff; |
|||
|
|||
AssemblyItems m_generatedItems; |
|||
/// Current height of the stack relative to the start.
|
|||
int m_stackHeight = 0; |
|||
/// If (b, a) is in m_requests then b is needed to compute a.
|
|||
std::multimap<ExpressionClasses::Id, ExpressionClasses::Id> m_neededBy; |
|||
/// Current content of the stack.
|
|||
std::map<int, ExpressionClasses::Id> m_stack; |
|||
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
|
|||
std::map<ExpressionClasses::Id, int> m_classPositions; |
|||
|
|||
/// The actual eqivalence class items and how to compute them.
|
|||
ExpressionClasses& m_expressionClasses; |
|||
/// Keeps information about which storage or memory slots were written to by which operations.
|
|||
/// The operations are sorted ascendingly by sequence number.
|
|||
std::map<std::pair<StoreOperation::Target, ExpressionClasses::Id>, StoreOperations> m_storeOperations; |
|||
/// The set of equivalence classes that should be present on the stack at the end.
|
|||
std::set<ExpressionClasses::Id> m_finalClasses; |
|||
}; |
|||
|
|||
template <class _AssemblyItemIterator> |
|||
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( |
|||
_AssemblyItemIterator _iterator, |
|||
_AssemblyItemIterator _end |
|||
) |
|||
{ |
|||
for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator) |
|||
feedItem(*_iterator); |
|||
if (_iterator != _end) |
|||
m_breakingItem = &(*_iterator++); |
|||
return _iterator; |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,419 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file ExpressionClasses.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Container for equivalence classes of expressions for use in common subexpression elimination. |
|||
*/ |
|||
|
|||
#include <libevmcore/ExpressionClasses.h> |
|||
#include <utility> |
|||
#include <tuple> |
|||
#include <functional> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <libevmcore/Assembly.h> |
|||
#include <libevmcore/CommonSubexpressionEliminator.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
|
|||
bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression const& _other) const |
|||
{ |
|||
auto type = item->type(); |
|||
auto otherType = _other.item->type(); |
|||
return std::tie(type, item->data(), arguments, sequenceNumber) < |
|||
std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber); |
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::find( |
|||
AssemblyItem const& _item, |
|||
Ids const& _arguments, |
|||
bool _copyItem, |
|||
unsigned _sequenceNumber |
|||
) |
|||
{ |
|||
Expression exp; |
|||
exp.id = Id(-1); |
|||
exp.item = &_item; |
|||
exp.arguments = _arguments; |
|||
exp.sequenceNumber = _sequenceNumber; |
|||
|
|||
if (SemanticInformation::isCommutativeOperation(_item)) |
|||
sort(exp.arguments.begin(), exp.arguments.end()); |
|||
|
|||
auto it = m_expressions.find(exp); |
|||
if (it != m_expressions.end()) |
|||
return it->id; |
|||
|
|||
if (_copyItem) |
|||
{ |
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_item)); |
|||
exp.item = m_spareAssemblyItem.back().get(); |
|||
} |
|||
|
|||
ExpressionClasses::Id id = tryToSimplify(exp); |
|||
if (id < m_representatives.size()) |
|||
exp.id = id; |
|||
else |
|||
{ |
|||
exp.id = m_representatives.size(); |
|||
m_representatives.push_back(exp); |
|||
} |
|||
m_expressions.insert(exp); |
|||
return exp.id; |
|||
} |
|||
|
|||
bool ExpressionClasses::knownToBeDifferent(ExpressionClasses::Id _a, ExpressionClasses::Id _b) |
|||
{ |
|||
// Try to simplify "_a - _b" and return true iff the value is a non-zero constant.
|
|||
map<unsigned, Expression const*> matchGroups; |
|||
Pattern constant(Push); |
|||
constant.setMatchGroup(1, matchGroups); |
|||
Id difference = find(Instruction::SUB, {_a, _b}); |
|||
return constant.matches(representative(difference), *this) && constant.d() != u256(0); |
|||
} |
|||
|
|||
bool ExpressionClasses::knownToBeDifferentBy32(ExpressionClasses::Id _a, ExpressionClasses::Id _b) |
|||
{ |
|||
// Try to simplify "_a - _b" and return true iff the value is at least 32 away from zero.
|
|||
map<unsigned, Expression const*> matchGroups; |
|||
Pattern constant(Push); |
|||
constant.setMatchGroup(1, matchGroups); |
|||
Id difference = find(Instruction::SUB, {_a, _b}); |
|||
if (!constant.matches(representative(difference), *this)) |
|||
return false; |
|||
// forbidden interval is ["-31", 31]
|
|||
return constant.d() + 31 > u256(62); |
|||
} |
|||
|
|||
string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const |
|||
{ |
|||
Expression const& expr = representative(_id); |
|||
stringstream str; |
|||
str << dec << expr.id << ":" << *expr.item << "("; |
|||
for (Id arg: expr.arguments) |
|||
str << fullDAGToString(arg) << ","; |
|||
str << ")"; |
|||
return str.str(); |
|||
} |
|||
|
|||
class Rules: public boost::noncopyable |
|||
{ |
|||
public: |
|||
Rules(); |
|||
void resetMatchGroups() { m_matchGroups.clear(); } |
|||
vector<pair<Pattern, function<Pattern()>>> rules() const { return m_rules; } |
|||
|
|||
private: |
|||
using Expression = ExpressionClasses::Expression; |
|||
map<unsigned, Expression const*> m_matchGroups; |
|||
vector<pair<Pattern, function<Pattern()>>> m_rules; |
|||
}; |
|||
|
|||
Rules::Rules() |
|||
{ |
|||
// Multiple occurences of one of these inside one rule must match the same equivalence class.
|
|||
// Constants.
|
|||
Pattern A(Push); |
|||
Pattern B(Push); |
|||
Pattern C(Push); |
|||
// Anything.
|
|||
Pattern X; |
|||
Pattern Y; |
|||
Pattern Z; |
|||
A.setMatchGroup(1, m_matchGroups); |
|||
B.setMatchGroup(2, m_matchGroups); |
|||
C.setMatchGroup(3, m_matchGroups); |
|||
X.setMatchGroup(4, m_matchGroups); |
|||
Y.setMatchGroup(5, m_matchGroups); |
|||
Z.setMatchGroup(6, m_matchGroups); |
|||
|
|||
m_rules = vector<pair<Pattern, function<Pattern()>>>{ |
|||
// arithmetics on constants
|
|||
{{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, |
|||
{{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, |
|||
{{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, |
|||
{{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() / B.d(); }}, |
|||
{{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) / u2s(B.d())); }}, |
|||
{{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() % B.d(); }}, |
|||
{{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) % u2s(B.d())); }}, |
|||
{{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, |
|||
{{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, |
|||
{{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }}, |
|||
{{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, |
|||
{{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, |
|||
{{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, |
|||
{{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, |
|||
{{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, |
|||
{{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, |
|||
{{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, |
|||
{{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, |
|||
{{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, |
|||
{{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, |
|||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, |
|||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, |
|||
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { |
|||
if (A.d() >= 31) |
|||
return B.d(); |
|||
unsigned testBit = unsigned(A.d()) * 8 + 7; |
|||
u256 mask = (u256(1) << testBit) - 1; |
|||
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); |
|||
}}, |
|||
|
|||
// invariants involving known constants
|
|||
{{Instruction::ADD, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::MUL, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::DIV, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::OR, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::XOR, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, |
|||
{{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, |
|||
// operations involving an expression and itself
|
|||
{{Instruction::AND, {X, X}}, [=]{ return X; }}, |
|||
{{Instruction::OR, {X, X}}, [=]{ return X; }}, |
|||
{{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, |
|||
{{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, |
|||
|
|||
{{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, |
|||
}; |
|||
// Associative operations
|
|||
for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{ |
|||
{Instruction::ADD, plus<u256>()}, |
|||
{Instruction::MUL, multiplies<u256>()}, |
|||
{Instruction::AND, bit_and<u256>()}, |
|||
{Instruction::OR, bit_or<u256>()}, |
|||
{Instruction::XOR, bit_xor<u256>()} |
|||
}) |
|||
{ |
|||
auto op = opFun.first; |
|||
auto fun = opFun.second; |
|||
// Moving constants to the outside, order matters here!
|
|||
// we need actions that return expressions (or patterns?) here, and we need also reversed rules
|
|||
// (X+A)+B -> X+(A+B)
|
|||
m_rules += vector<pair<Pattern, function<Pattern()>>>{{ |
|||
{op, {{op, {X, A}}, B}}, |
|||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } |
|||
}, { |
|||
// X+(Y+A) -> (X+Y)+A
|
|||
{op, {{op, {X, A}}, Y}}, |
|||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } |
|||
}, { |
|||
// For now, we still need explicit commutativity for the inner pattern
|
|||
{op, {{op, {A, X}}, B}}, |
|||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } |
|||
}, { |
|||
{op, {{op, {A, X}}, Y}}, |
|||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } |
|||
}}; |
|||
} |
|||
// move constants across subtractions
|
|||
m_rules += vector<pair<Pattern, function<Pattern()>>>{ |
|||
{ |
|||
// X - A -> X + (-A)
|
|||
{Instruction::SUB, {X, A}}, |
|||
[=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } |
|||
}, { |
|||
// (X + A) - Y -> (X - Y) + A
|
|||
{Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, |
|||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } |
|||
}, { |
|||
// (A + X) - Y -> (X - Y) + A
|
|||
{Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, |
|||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } |
|||
}, { |
|||
// X - (Y + A) -> (X - Y) + (-A)
|
|||
{Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, |
|||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } |
|||
}, { |
|||
// X - (A + Y) -> (X - Y) + (-A)
|
|||
{Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, |
|||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } |
|||
} |
|||
}; |
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) |
|||
{ |
|||
static Rules rules; |
|||
|
|||
if (_expr.item->type() != Operation) |
|||
return -1; |
|||
|
|||
for (auto const& rule: rules.rules()) |
|||
{ |
|||
rules.resetMatchGroups(); |
|||
if (rule.first.matches(_expr, *this)) |
|||
{ |
|||
// Debug info
|
|||
//cout << "Simplifying " << *_expr.item << "(";
|
|||
//for (Id arg: _expr.arguments)
|
|||
// cout << fullDAGToString(arg) << ", ";
|
|||
//cout << ")" << endl;
|
|||
//cout << "with rule " << rule.first.toString() << endl;
|
|||
//ExpressionTemplate t(rule.second());
|
|||
//cout << "to " << rule.second().toString() << endl;
|
|||
return rebuildExpression(ExpressionTemplate(rule.second())); |
|||
} |
|||
} |
|||
|
|||
if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) |
|||
{ |
|||
Expression expr = _expr; |
|||
swap(expr.arguments[0], expr.arguments[1]); |
|||
return tryToSimplify(expr, true); |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate const& _template) |
|||
{ |
|||
if (_template.hasId) |
|||
return _template.id; |
|||
|
|||
Ids arguments; |
|||
for (ExpressionTemplate const& t: _template.arguments) |
|||
arguments.push_back(rebuildExpression(t)); |
|||
return find(_template.item, arguments); |
|||
} |
|||
|
|||
|
|||
Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments): |
|||
m_type(Operation), |
|||
m_requireDataMatch(true), |
|||
m_data(_instruction), |
|||
m_arguments(_arguments) |
|||
{ |
|||
} |
|||
|
|||
void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups) |
|||
{ |
|||
m_matchGroup = _group; |
|||
m_matchGroups = &_matchGroups; |
|||
} |
|||
|
|||
bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const |
|||
{ |
|||
if (!matchesBaseItem(*_expr.item)) |
|||
return false; |
|||
if (m_matchGroup) |
|||
{ |
|||
if (!m_matchGroups->count(m_matchGroup)) |
|||
(*m_matchGroups)[m_matchGroup] = &_expr; |
|||
else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) |
|||
return false; |
|||
} |
|||
assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); |
|||
for (size_t i = 0; i < m_arguments.size(); ++i) |
|||
if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
string Pattern::toString() const |
|||
{ |
|||
stringstream s; |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
s << instructionInfo(Instruction(unsigned(m_data))).name; |
|||
break; |
|||
case Push: |
|||
s << "PUSH " << hex << m_data; |
|||
break; |
|||
case UndefinedItem: |
|||
s << "ANY"; |
|||
break; |
|||
default: |
|||
s << "t=" << dec << m_type << " d=" << hex << m_data; |
|||
break; |
|||
} |
|||
if (!m_requireDataMatch) |
|||
s << " ~"; |
|||
if (m_matchGroup) |
|||
s << "[" << dec << m_matchGroup << "]"; |
|||
s << "("; |
|||
for (Pattern const& p: m_arguments) |
|||
s << p.toString() << ", "; |
|||
s << ")"; |
|||
return s.str(); |
|||
} |
|||
|
|||
bool Pattern::matchesBaseItem(AssemblyItem const& _item) const |
|||
{ |
|||
if (m_type == UndefinedItem) |
|||
return true; |
|||
if (m_type != _item.type()) |
|||
return false; |
|||
if (m_requireDataMatch && m_data != _item.data()) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
Pattern::Expression const& Pattern::matchGroupValue() const |
|||
{ |
|||
assertThrow(m_matchGroup > 0, OptimizerException, ""); |
|||
assertThrow(!!m_matchGroups, OptimizerException, ""); |
|||
assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); |
|||
return *(*m_matchGroups)[m_matchGroup]; |
|||
} |
|||
|
|||
|
|||
ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern) |
|||
{ |
|||
if (_pattern.matchGroup()) |
|||
{ |
|||
hasId = true; |
|||
id = _pattern.id(); |
|||
} |
|||
else |
|||
{ |
|||
hasId = false; |
|||
item = _pattern.toAssemblyItem(); |
|||
} |
|||
for (auto const& arg: _pattern.arguments()) |
|||
arguments.push_back(ExpressionTemplate(arg)); |
|||
} |
|||
|
|||
string ExpressionTemplate::toString() const |
|||
{ |
|||
stringstream s; |
|||
if (hasId) |
|||
s << id; |
|||
else |
|||
s << item; |
|||
s << "("; |
|||
for (auto const& arg: arguments) |
|||
s << arg.toString(); |
|||
s << ")"; |
|||
return s.str(); |
|||
} |
@ -0,0 +1,168 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file ExpressionClasses.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Container for equivalence classes of expressions for use in common subexpression elimination. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <memory> |
|||
#include <libdevcore/Common.h> |
|||
#include <libevmcore/AssemblyItem.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class Pattern; |
|||
struct ExpressionTemplate; |
|||
|
|||
/**
|
|||
* Collection of classes of equivalent expressions that can also determine the class of an expression. |
|||
* Identifiers are contiguously assigned to new classes starting from zero. |
|||
*/ |
|||
class ExpressionClasses |
|||
{ |
|||
public: |
|||
using Id = unsigned; |
|||
using Ids = std::vector<Id>; |
|||
|
|||
struct Expression |
|||
{ |
|||
Id id; |
|||
AssemblyItem const* item; |
|||
Ids arguments; |
|||
unsigned sequenceNumber; ///< Storage modification sequence, only used for SLOAD/SSTORE instructions.
|
|||
/// Behaves as if this was a tuple of (item->type(), item->data(), arguments, sequenceNumber).
|
|||
bool operator<(Expression const& _other) const; |
|||
}; |
|||
|
|||
/// Retrieves the id of the expression equivalence class resulting from the given item applied to the
|
|||
/// given classes, might also create a new one.
|
|||
/// @param _copyItem if true, copies the assembly item to an internal storage instead of just
|
|||
/// keeping a pointer.
|
|||
/// The @a _sequenceNumber indicates the current storage or memory access sequence.
|
|||
Id find( |
|||
AssemblyItem const& _item, |
|||
Ids const& _arguments = {}, |
|||
bool _copyItem = true, |
|||
unsigned _sequenceNumber = 0 |
|||
); |
|||
/// @returns the canonical representative of an expression class.
|
|||
Expression const& representative(Id _id) const { return m_representatives.at(_id); } |
|||
/// @returns the number of classes.
|
|||
Id size() const { return m_representatives.size(); } |
|||
|
|||
/// @returns true if the values of the given classes are known to be different (on every input).
|
|||
/// @note that this function might still return false for some different inputs.
|
|||
bool knownToBeDifferent(Id _a, Id _b); |
|||
/// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32.
|
|||
bool knownToBeDifferentBy32(Id _a, Id _b); |
|||
|
|||
std::string fullDAGToString(Id _id) const; |
|||
|
|||
private: |
|||
/// Tries to simplify the given expression.
|
|||
/// @returns its class if it possible or Id(-1) otherwise.
|
|||
/// @param _secondRun is set to true for the second run where arguments of commutative expressions are reversed
|
|||
Id tryToSimplify(Expression const& _expr, bool _secondRun = false); |
|||
|
|||
/// Rebuilds an expression from a (matched) pattern.
|
|||
Id rebuildExpression(ExpressionTemplate const& _template); |
|||
|
|||
std::vector<std::pair<Pattern, std::function<Pattern()>>> createRules() const; |
|||
|
|||
/// Expression equivalence class representatives - we only store one item of an equivalence.
|
|||
std::vector<Expression> m_representatives; |
|||
/// All expression ever encountered.
|
|||
std::set<Expression> m_expressions; |
|||
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem; |
|||
}; |
|||
|
|||
/**
|
|||
* Pattern to match against an expression. |
|||
* Also stores matched expressions to retrieve them later, for constructing new expressions using |
|||
* ExpressionTemplate. |
|||
*/ |
|||
class Pattern |
|||
{ |
|||
public: |
|||
using Expression = ExpressionClasses::Expression; |
|||
using Id = ExpressionClasses::Id; |
|||
|
|||
// Matches a specific constant value.
|
|||
Pattern(unsigned _value): Pattern(u256(_value)) {} |
|||
// Matches a specific constant value.
|
|||
Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {} |
|||
// Matches a specific assembly item type or anything if not given.
|
|||
Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} |
|||
// Matches a given instruction with given arguments
|
|||
Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {}); |
|||
/// Sets this pattern to be part of the match group with the identifier @a _group.
|
|||
/// Inside one rule, all patterns in the same match group have to match expressions from the
|
|||
/// same expression equivalence class.
|
|||
void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups); |
|||
unsigned matchGroup() const { return m_matchGroup; } |
|||
bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; |
|||
|
|||
AssemblyItem toAssemblyItem() const { return AssemblyItem(m_type, m_data); } |
|||
std::vector<Pattern> arguments() const { return m_arguments; } |
|||
|
|||
/// @returns the id of the matched expression if this pattern is part of a match group.
|
|||
Id id() const { return matchGroupValue().id; } |
|||
/// @returns the data of the matched expression if this pattern is part of a match group.
|
|||
u256 d() const { return matchGroupValue().item->data(); } |
|||
|
|||
std::string toString() const; |
|||
|
|||
private: |
|||
bool matchesBaseItem(AssemblyItem const& _item) const; |
|||
Expression const& matchGroupValue() const; |
|||
|
|||
AssemblyItemType m_type; |
|||
bool m_requireDataMatch = false; |
|||
u256 m_data = 0; |
|||
std::vector<Pattern> m_arguments; |
|||
unsigned m_matchGroup = 0; |
|||
std::map<unsigned, Expression const*>* m_matchGroups = nullptr; |
|||
}; |
|||
|
|||
/**
|
|||
* Template for a new expression that can be built from matched patterns. |
|||
*/ |
|||
struct ExpressionTemplate |
|||
{ |
|||
using Expression = ExpressionClasses::Expression; |
|||
using Id = ExpressionClasses::Id; |
|||
explicit ExpressionTemplate(Pattern const& _pattern); |
|||
std::string toString() const; |
|||
bool hasId = false; |
|||
/// Id of the matched expression, if available.
|
|||
Id id = Id(-1); |
|||
// Otherwise, assembly item.
|
|||
AssemblyItem item = UndefinedItem; |
|||
std::vector<ExpressionTemplate> arguments; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file SemanticInformation.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Helper to provide semantic information about assembly items. |
|||
*/ |
|||
|
|||
#include <libevmcore/SemanticInformation.h> |
|||
#include <libevmcore/AssemblyItem.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) |
|||
{ |
|||
default: |
|||
case UndefinedItem: |
|||
case Tag: |
|||
return true; |
|||
case Push: |
|||
case PushString: |
|||
case PushTag: |
|||
case PushSub: |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
case PushData: |
|||
return false; |
|||
case Operation: |
|||
{ |
|||
if (isSwapInstruction(_item) || isDupInstruction(_item)) |
|||
return false; |
|||
if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) |
|||
return true; // GAS and PC assume a specific order of opcodes
|
|||
if (_item.instruction() == Instruction::MSIZE) |
|||
return true; // msize is modified already by memory access, avoid that for now
|
|||
if (_item.instruction() == Instruction::SHA3) |
|||
return true; //@todo: we have to compare sha3's not based on their memory addresses but on the memory content.
|
|||
InstructionInfo info = instructionInfo(_item.instruction()); |
|||
if (_item.instruction() == Instruction::SSTORE) |
|||
return false; |
|||
if (_item.instruction() == Instruction::MSTORE) |
|||
return false; |
|||
//@todo: We do not handle the following memory instructions for now:
|
|||
// calldatacopy, codecopy, extcodecopy, mstore8,
|
|||
// msize (note that msize also depends on memory read access)
|
|||
|
|||
// the second requirement will be lifted once it is implemented
|
|||
return info.sideEffects || info.args > 2; |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
switch (_item.instruction()) |
|||
{ |
|||
case Instruction::ADD: |
|||
case Instruction::MUL: |
|||
case Instruction::EQ: |
|||
case Instruction::AND: |
|||
case Instruction::OR: |
|||
case Instruction::XOR: |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; |
|||
} |
|||
|
|||
bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; |
|||
} |
|||
|
|||
bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) |
|||
{ |
|||
return _item == AssemblyItem(Instruction::JUMP) || _item == AssemblyItem(Instruction::JUMPI); |
|||
} |
@ -0,0 +1,50 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum 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. |
|||
|
|||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file SemanticInformation.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Helper to provide semantic information about assembly items. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class AssemblyItem; |
|||
|
|||
/**
|
|||
* Helper functions to provide context-independent information about assembly items. |
|||
*/ |
|||
struct SemanticInformation |
|||
{ |
|||
/// @returns true if the given items starts a new block for common subexpression analysis.
|
|||
static bool breaksCSEAnalysisBlock(AssemblyItem const& _item); |
|||
/// @returns true if the item is a two-argument operation whose value does not depend on the
|
|||
/// order of its arguments.
|
|||
static bool isCommutativeOperation(AssemblyItem const& _item); |
|||
static bool isDupInstruction(AssemblyItem const& _item); |
|||
static bool isSwapInstruction(AssemblyItem const& _item); |
|||
static bool isJumpInstruction(AssemblyItem const& _item); |
|||
}; |
|||
|
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue