Browse Source

Merge pull request #1923 from yann300/gasEstimation

Mix - Gas estimation
cl-refactor
Gav Wood 10 years ago
parent
commit
34b83c2096
  1. 68
      mix/CodeModel.cpp
  2. 44
      mix/CodeModel.h
  3. 15
      mix/qml/Application.qml
  4. 12
      mix/qml/CodeEditorView.qml
  5. 15
      mix/qml/WebCodeEditor.qml
  6. 6
      mix/qml/html/cm/solarized.css
  7. 107
      mix/qml/html/codeeditor.js

68
mix/CodeModel.cpp

@ -33,6 +33,8 @@
#include <libsolidity/CompilerStack.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libsolidity/InterfaceHandler.h>
#include <libsolidity/StructuralGasEstimator.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libevmcore/Instruction.h>
#include <libethcore/CommonJS.h>
#include "QContractDefinition.h"
@ -50,6 +52,7 @@ const std::set<std::string> c_predefinedContracts =
namespace
{
using namespace dev::eth;
using namespace dev::solidity;
class CollectLocalsVisitor: public ASTConstVisitor
@ -305,6 +308,7 @@ void CodeModel::runCompilationJob(int _jobId)
}
}
cs.compile(false);
gasEstimation(cs);
collectContracts(cs, sourceNames);
}
catch (dev::Exception const& _exception)
@ -327,6 +331,44 @@ void CodeModel::runCompilationJob(int _jobId)
emit stateChanged();
}
void CodeModel::gasEstimation(solidity::CompilerStack const& _cs)
{
if (m_gasCostsMaps)
m_gasCostsMaps->deleteLater();
m_gasCostsMaps = new GasMapWrapper(this);
for (std::string n: _cs.getContractNames())
{
ContractDefinition const& contractDefinition = _cs.getContractDefinition(n);
QString sourceName = QString::fromStdString(*contractDefinition.getLocation().sourceName);
if (!m_gasCostsMaps->contains(sourceName))
m_gasCostsMaps->insert(sourceName, QVariantList());
if (!contractDefinition.isFullyImplemented())
continue;
dev::solidity::SourceUnit const& sourceUnit = _cs.getAST(*contractDefinition.getLocation().sourceName);
AssemblyItems const* items = _cs.getRuntimeAssemblyItems(n);
StructuralGasEstimator estimator;
std::map<ASTNode const*, GasMeter::GasConsumption> gasCosts = estimator.breakToStatementLevel(estimator.performEstimation(*items, std::vector<ASTNode const*>({&sourceUnit})), {&sourceUnit});
for (auto gasItem = gasCosts.begin(); gasItem != gasCosts.end(); ++gasItem)
{
SourceLocation const& location = gasItem->first->getLocation();
GasMeter::GasConsumption cost = gasItem->second;
std::stringstream v;
v << cost.value;
m_gasCostsMaps->push(sourceName, location.start, location.end, QString::fromStdString(v.str()), cost.isInfinite);
}
}
}
QVariantList CodeModel::gasCostByDocumentId(QString const& _documentId) const
{
if (m_gasCostsMaps)
return m_gasCostsMaps->gasCostsByDocId(_documentId);
else
return QVariantList();
}
void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs, std::vector<std::string> const& _sourceNames)
{
Guard pl(x_pendingContracts);
@ -516,3 +558,29 @@ QString CodeModel::resolveFunctionName(dev::SourceLocation const& _location)
}
return QString();
}
void GasMapWrapper::push(QString _source, int _start, int _end, QString _value, bool _isInfinite)
{
GasMap* gas = new GasMap(_start, _end, _value, _isInfinite, this);
m_gasMaps.find(_source).value().push_back(QVariant::fromValue(gas));
}
bool GasMapWrapper::contains(QString _key)
{
return m_gasMaps.contains(_key);
}
void GasMapWrapper::insert(QString _source, QVariantList _variantList)
{
m_gasMaps.insert(_source, _variantList);
}
QVariantList GasMapWrapper::gasCostsByDocId(QString _source)
{
auto gasIter = m_gasMaps.find(_source);
if (gasIter != m_gasMaps.end())
return gasIter.value();
else
return QVariantList();
}

44
mix/CodeModel.h

@ -32,6 +32,7 @@
#include <libdevcore/Guards.h>
#include <libevmasm/Assembly.h>
#include "SolidityType.h"
#include "QBigInt.h"
class QTextDocument;
@ -127,6 +128,42 @@ struct SourceMap
};
using SourceMaps = QMap<QString, SourceMap>; //by source id
using GasCostsMaps = QMap<QString, QVariantList>; //gas cost by contract name
class GasMapWrapper: public QObject
{
Q_OBJECT
Q_PROPERTY(GasCostsMaps gasMaps MEMBER m_gasMaps CONSTANT)
public:
GasMapWrapper(QObject* _parent): QObject(_parent){}
void push(QString _source, int _start, int _end, QString _value, bool _isInfinite);
bool contains(QString _key);
void insert(QString _source, QVariantList _variantList);
QVariantList gasCostsByDocId(QString _source);
private:
GasCostsMaps m_gasMaps;
};
class GasMap: public QObject
{
Q_OBJECT
Q_PROPERTY(int start MEMBER m_start CONSTANT)
Q_PROPERTY(int end MEMBER m_end CONSTANT)
Q_PROPERTY(QString gas MEMBER m_gas CONSTANT)
Q_PROPERTY(bool isInfinite MEMBER m_isInfinite CONSTANT)
public:
GasMap(int _start, int _end, QString _gas, bool _isInfinite, QObject* _parent): QObject(_parent), m_start(_start), m_end(_end), m_gas(_gas), m_isInfinite(_isInfinite) {}
int m_start;
int m_end;
QString m_gas;
bool m_isInfinite;
};
/// Code compilation model. Compiles contracts in background an provides compiled contract data
class CodeModel: public QObject
@ -168,6 +205,10 @@ public:
bool isContractOrFunctionLocation(dev::SourceLocation const& _location);
/// Get funciton name by location
QString resolveFunctionName(dev::SourceLocation const& _location);
/// Gas estimation for compiled sources
void gasEstimation(solidity::CompilerStack const& _cs);
/// Gas cost by doc id
Q_INVOKABLE QVariantList gasCostByDocumentId(QString const& _documentId) const;
signals:
/// Emited on compilation state change
@ -203,6 +244,7 @@ private:
mutable dev::Mutex x_contractMap;
ContractMap m_contractMap;
SourceMaps m_sourceMaps;
GasMapWrapper* m_gasCostsMaps = 0;
std::unique_ptr<CodeHighlighterSettings> m_codeHighlighterSettings;
QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker;
@ -216,3 +258,5 @@ private:
}
}
//Q_DECLARE_METATYPE(dev::mix::GasMap)

15
mix/qml/Application.qml

@ -116,6 +116,10 @@ ApplicationWindow {
MenuSeparator {}
MenuItem { action: toggleAssemblyDebuggingAction }
}
Menu {
title: qsTr("Tools")
MenuItem { action: gasEstimationAction }
}
Menu {
title: qsTr("Windows")
MenuItem { action: openNextDocumentAction }
@ -409,4 +413,15 @@ ApplicationWindow {
mainContent.codeEditor.goToCompilationError();
}
}
Action {
id: gasEstimationAction
text: qsTr("Display gas estimation")
shortcut: "Ctrl+G"
checkable: true
onTriggered:
{
mainContent.codeEditor.displayGasEstimation(checked);
}
}
}

12
mix/qml/CodeEditorView.qml

@ -168,6 +168,13 @@ Item {
editors.itemAt(i).item.setFontSize(size);
}
function displayGasEstimation(checked)
{
var editor = getEditor(currentDocumentId);
if (editor)
editor.displayGasEstimation(checked);
}
Component.onCompleted: projectModel.codeEditor = codeEditorView;
Connections {
@ -177,6 +184,10 @@ Item {
}
onCompilationComplete: {
sourceInError = "";
var gasCosts = codeModel.gasCostByDocumentId(currentDocumentId);
var editor = getEditor(currentDocumentId);
if (editor)
editor.setGasCosts(gasCosts);
}
}
@ -280,6 +291,7 @@ Item {
messageDialog.doc = editorListModel.get(index);
messageDialog.open();
}
loader.item.displayGasEstimation(gasEstimationAction.checked);
}
}
Component.onCompleted: {

15
mix/qml/WebCodeEditor.qml

@ -83,6 +83,16 @@ Item {
editorBrowser.runJavaScript("setFontSize(" + size + ")", function(result) {});
}
function setGasCosts(gasCosts) {
if (initialized && editorBrowser)
editorBrowser.runJavaScript("setGasCosts('" + JSON.stringify(gasCosts) + "')", function(result) {});
}
function displayGasEstimation(show) {
if (initialized && editorBrowser)
editorBrowser.runJavaScript("displayGasEstimation('" + show + "')", function(result) {});
}
Clipboard
{
id: clipboard
@ -134,7 +144,12 @@ Item {
function compilationComplete()
{
if (editorBrowser)
{
editorBrowser.runJavaScript("compilationComplete()", function(result) { });
parent.displayGasEstimation(gasEstimationAction.checked);
}
}
function compilationError(error, sourceName)

6
mix/qml/html/cm/solarized.css

@ -189,3 +189,9 @@ view-port
}
span.CodeMirror-selectedtext { color: #586e75 !important; }
/* Gas Costs */
.CodeMirror-gasCosts {
border-bottom: double 1px #2aa198;
}

107
mix/qml/html/codeeditor.js

@ -203,5 +203,112 @@ setFontSize = function(size)
editor.refresh();
}
makeGasCostMarker = function(value) {
var marker = document.createElement("div");
marker.style.color = "#822";
marker.innerHTML = value;
marker.className = "CodeMirror-errorannotation-context";
return marker;
};
var gasCosts = null;
setGasCosts = function(_gasCosts)
{
gasCosts = JSON.parse(_gasCosts);
if (showingGasEstimation)
{
displayGasEstimation(false);
displayGasEstimation(true);
}
}
var showingGasEstimation = false;
var gasMarkText = [];
var gasMarkRef = {};
displayGasEstimation = function(show)
{
show = JSON.parse(show);
showingGasEstimation = show;
if (show)
{
var maxGas = 20000;
var step = colorGradient.length / maxGas; // 20000 max gas
clearGasMark();
gasMarkText = [];
gasMarkRef = {};
for (var i in gasCosts)
{
if (gasCosts[i].gas !== "0")
{
var color;
var colorIndex = Math.round(step * gasCosts[i].gas);
if (gasCosts[i].isInfinite || colorIndex > colorGradient.length)
color = colorGradient[colorGradient.length - 1];
else
color = colorGradient[colorIndex];
var className = "CodeMirror-gasCosts" + i;
var line = editor.posFromIndex(gasCosts[i].start)
gasMarkText.push(editor.markText(line, editor.posFromIndex(gasCosts[i].end), { inclusiveLeft: true, inclusiveRight: true, handleMouseEvents: true, className: className, css: "background-color:" + color }));
gasMarkRef[className] = { line: line.line, value: gasCosts[i] };
}
}
CodeMirror.on(editor.getWrapperElement(), "mouseover", listenMouseOver);
}
else
{
CodeMirror.off(editor.getWrapperElement(), "mouseover", listenMouseOver);
clearGasMark();
if (gasAnnotation)
{
gasAnnotation.clear();
gasAnnotation = null;
}
}
}
function clearGasMark()
{
if (gasMarkText)
for (var k in gasMarkText)
gasMarkText[k].clear();
}
var gasAnnotation;
function listenMouseOver(e)
{
var node = e.target || e.srcElement;
if (node)
{
if (node.className && node.className.indexOf("CodeMirror-gasCosts") !== -1)
{
if (gasAnnotation)
gasAnnotation.clear();
var cl = getGasCostClass(node);
var gasTitle = gasMarkRef[cl].value.isInfinite ? "infinite" : gasMarkRef[cl].value.gas;
gasTitle = gasTitle + " gas";
gasAnnotation = editor.addLineWidget(gasMarkRef[cl].line + 1, makeGasCostMarker(gasTitle), { coverGutter: false, above: true });
}
else if (gasAnnotation)
{
gasAnnotation.clear();
gasAnnotation = null;
}
}
}
function getGasCostClass(node)
{
var classes = node.className.split(" ");
for (var k in classes)
{
if (classes[k].indexOf("CodeMirror-gasCosts") !== -1)
return classes[k];
}
return "";
}
// blue => red ["#1515ED", "#1714EA", "#1914E8", "#1B14E6", "#1D14E4", "#1F14E2", "#2214E0", "#2414DE", "#2614DC", "#2813DA", "#2A13D8", "#2D13D6", "#2F13D4", "#3113D2", "#3313D0", "#3513CE", "#3713CC", "#3A12CA", "#3C12C8", "#3E12C6", "#4012C4", "#4212C2", "#4512C0", "#4712BE", "#4912BC", "#4B11BA", "#4D11B8", "#4F11B6", "#5211B4", "#5411B2", "#5611B0", "#5811AE", "#5A11AC", "#5D11AA", "#5F10A7", "#6110A5", "#6310A3", "#6510A1", "#67109F", "#6A109D", "#6C109B", "#6E1099", "#700F97", "#720F95", "#750F93", "#770F91", "#790F8F", "#7B0F8D", "#7D0F8B", "#7F0F89", "#820E87", "#840E85", "#860E83", "#880E81", "#8A0E7F", "#8D0E7D", "#8F0E7B", "#910E79", "#930D77", "#950D75", "#970D73", "#9A0D71", "#9C0D6F", "#9E0D6D", "#A00D6B", "#A20D69", "#A50D67", "#A70C64", "#A90C62", "#AB0C60", "#AD0C5E", "#AF0C5C", "#B20C5A", "#B40C58", "#B60C56", "#B80B54", "#BA0B52", "#BD0B50", "#BF0B4E", "#C10B4C", "#C30B4A", "#C50B48", "#C70B46", "#CA0A44", "#CC0A42", "#CE0A40", "#D00A3E", "#D20A3C", "#D50A3A", "#D70A38", "#D90A36", "#DB0934", "#DD0932", "#DF0930", "#E2092E", "#E4092C", "#E6092A", "#E80928", "#EA0926", "#ED0924"]
/* green => red */ var colorGradient = ["#429C27", "#439A26", "#449926", "#469726", "#479626", "#489525", "#4A9325", "#4B9225", "#4D9025", "#4E8F25", "#4F8E24", "#518C24", "#528B24", "#548924", "#558824", "#568723", "#588523", "#598423", "#5B8223", "#5C8122", "#5D8022", "#5F7E22", "#607D22", "#627B22", "#637A21", "#647921", "#667721", "#677621", "#697421", "#6A7320", "#6B7220", "#6D7020", "#6E6F20", "#706E20", "#716C1F", "#726B1F", "#74691F", "#75681F", "#76671E", "#78651E", "#79641E", "#7B621E", "#7C611E", "#7D601D", "#7F5E1D", "#805D1D", "#825B1D", "#835A1D", "#84591C", "#86571C", "#87561C", "#89541C", "#8A531B", "#8B521B", "#8D501B", "#8E4F1B", "#904D1B", "#914C1A", "#924B1A", "#94491A", "#95481A", "#97461A", "#984519", "#994419", "#9B4219", "#9C4119", "#9E4019", "#9F3E18", "#A03D18", "#A23B18", "#A33A18", "#A43917", "#A63717", "#A73617", "#A93417", "#AA3317", "#AB3216", "#AD3016", "#AE2F16", "#B02D16", "#B12C16", "#B22B15", "#B42915", "#B52815", "#B72615", "#B82514", "#B92414", "#BB2214", "#BC2114", "#BE1F14", "#BF1E13", "#C01D13", "#C21B13", "#C31A13", "#C51813", "#C61712", "#C71612", "#C91412", "#CA1312", "#CC1212"]
editor.setOption("extraKeys", extraKeys);

Loading…
Cancel
Save