mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Stored combined creation and runtime tags.
Includes a change to Assembly to allow tags from sub-assemblies to be used. Sorry, this get a bit bigger than I thought.
This commit is contained in:
parent
ee3efa67a8
commit
e543bd34c0
@ -308,9 +308,20 @@ void Assembly::injectStart(AssemblyItem const& _i)
|
||||
|
||||
Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
{
|
||||
if (!_enable)
|
||||
return *this;
|
||||
if (_enable)
|
||||
optimiseInternal(_isCreation, _runs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
map<u256, u256> Assembly::optimiseInternal(bool _isCreation, size_t _runs)
|
||||
{
|
||||
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
||||
{
|
||||
map<u256, u256> subTagReplacements = m_subs[subId].optimiseInternal(false, _runs);
|
||||
BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId);
|
||||
}
|
||||
|
||||
map<u256, u256> tagReplacements;
|
||||
unsigned total = 0;
|
||||
for (unsigned count = 1; count > 0; total += count)
|
||||
{
|
||||
@ -319,30 +330,39 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
// This only modifies PushTags, we have to run again to actually remove code.
|
||||
BlockDeduplicator dedup(m_items);
|
||||
if (dedup.deduplicate())
|
||||
{
|
||||
tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end());
|
||||
count++;
|
||||
}
|
||||
|
||||
{
|
||||
// Control flow graph that resets knowledge at path joins.
|
||||
ControlFlowGraph cfg(m_items, false);
|
||||
// Control flow graph optimization has been here before but is disabled because it
|
||||
// assumes we only jump to tags that are pushed. This is not the case anymore with
|
||||
// function types that can be stored in storage.
|
||||
AssemblyItems optimisedItems;
|
||||
for (BasicBlock const& block: cfg.optimisedBlocks())
|
||||
|
||||
auto iter = m_items.begin();
|
||||
while (iter != m_items.end())
|
||||
{
|
||||
// We used to start with the block's initial state but it caused
|
||||
// too many inconsistencies.
|
||||
auto end = iter;
|
||||
while (end != m_items.end())
|
||||
if (SemanticInformation::altersControlFlow(*end++))
|
||||
break;
|
||||
|
||||
KnownState emptyState;
|
||||
CommonSubexpressionEliminator eliminator(emptyState);
|
||||
auto iter = m_items.begin() + block.begin;
|
||||
auto const end = m_items.begin() + block.end;
|
||||
while (iter < end)
|
||||
auto blockIter = iter;
|
||||
auto const blockEnd = end;
|
||||
while (blockIter < blockEnd)
|
||||
{
|
||||
auto orig = iter;
|
||||
iter = eliminator.feedItems(iter, end);
|
||||
auto orig = blockIter;
|
||||
blockIter = eliminator.feedItems(blockIter, blockEnd);
|
||||
bool shouldReplace = false;
|
||||
AssemblyItems optimisedChunk;
|
||||
try
|
||||
{
|
||||
optimisedChunk = eliminator.getOptimizedItems();
|
||||
shouldReplace = (optimisedChunk.size() < size_t(iter - orig));
|
||||
shouldReplace = (optimisedChunk.size() < size_t(blockIter - orig));
|
||||
}
|
||||
catch (StackTooDeepException const&)
|
||||
{
|
||||
@ -361,15 +381,11 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
optimisedItems += optimisedChunk;
|
||||
}
|
||||
else
|
||||
copy(orig, iter, back_inserter(optimisedItems));
|
||||
copy(orig, blockIter, back_inserter(optimisedItems));
|
||||
}
|
||||
iter = end;
|
||||
}
|
||||
|
||||
if (optimisedItems.size() < m_items.size())
|
||||
{
|
||||
m_items = move(optimisedItems);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,10 +396,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
m_items
|
||||
);
|
||||
|
||||
for (auto& sub: m_subs)
|
||||
sub.optimise(true, false, _runs);
|
||||
|
||||
return *this;
|
||||
return tagReplacements;
|
||||
}
|
||||
|
||||
LinkerObject const& Assembly::assemble() const
|
||||
@ -394,8 +407,8 @@ LinkerObject const& Assembly::assemble() const
|
||||
LinkerObject& ret = m_assembledObject;
|
||||
|
||||
unsigned totalBytes = bytesRequired();
|
||||
vector<unsigned> tagPos(m_usedTags);
|
||||
map<unsigned, unsigned> tagRef;
|
||||
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1);
|
||||
map<size_t, pair<size_t, size_t>> tagRef;
|
||||
multimap<h256, unsigned> dataRef;
|
||||
multimap<size_t, size_t> subRef;
|
||||
vector<unsigned> sizeRef; ///< Pointers to code locations where the size of the program is inserted
|
||||
@ -413,8 +426,8 @@ LinkerObject const& Assembly::assemble() const
|
||||
for (AssemblyItem const& i: m_items)
|
||||
{
|
||||
// store position of the invalid jump destination
|
||||
if (i.type() != Tag && tagPos[0] == 0)
|
||||
tagPos[0] = ret.bytecode.size();
|
||||
if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1))
|
||||
m_tagPositionsInBytecode[0] = ret.bytecode.size();
|
||||
|
||||
switch (i.type())
|
||||
{
|
||||
@ -446,7 +459,7 @@ LinkerObject const& Assembly::assemble() const
|
||||
case PushTag:
|
||||
{
|
||||
ret.bytecode.push_back(tagPush);
|
||||
tagRef[ret.bytecode.size()] = (unsigned)i.data();
|
||||
tagRef[ret.bytecode.size()] = i.splitForeignPushTag();
|
||||
ret.bytecode.resize(ret.bytecode.size() + bytesPerTag);
|
||||
break;
|
||||
}
|
||||
@ -484,26 +497,16 @@ LinkerObject const& Assembly::assemble() const
|
||||
ret.bytecode.resize(ret.bytecode.size() + 20);
|
||||
break;
|
||||
case Tag:
|
||||
tagPos[(unsigned)i.data()] = ret.bytecode.size();
|
||||
assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large.");
|
||||
assertThrow(i.data() != 0, AssemblyException, "");
|
||||
assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag.");
|
||||
assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large.");
|
||||
m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size();
|
||||
ret.bytecode.push_back((byte)Instruction::JUMPDEST);
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
||||
}
|
||||
}
|
||||
for (auto const& i: tagRef)
|
||||
{
|
||||
bytesRef r(ret.bytecode.data() + i.first, bytesPerTag);
|
||||
auto tag = i.second;
|
||||
if (tag >= tagPos.size())
|
||||
tag = 0;
|
||||
if (tag == 0)
|
||||
assertThrow(tagPos[tag] != 0, AssemblyException, "");
|
||||
|
||||
toBigEndian(tagPos[tag], r);
|
||||
}
|
||||
|
||||
if (!dataRef.empty() && !subRef.empty())
|
||||
ret.bytecode.push_back(0);
|
||||
@ -519,6 +522,23 @@ LinkerObject const& Assembly::assemble() const
|
||||
}
|
||||
ret.append(m_subs[i].assemble());
|
||||
}
|
||||
for (auto const& i: tagRef)
|
||||
{
|
||||
size_t subId;
|
||||
size_t tagId;
|
||||
tie(subId, tagId) = i.second;
|
||||
assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id");
|
||||
std::vector<size_t> const& tagPositions =
|
||||
subId == size_t(-1) ?
|
||||
m_tagPositionsInBytecode :
|
||||
m_subs[subId].m_tagPositionsInBytecode;
|
||||
assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag.");
|
||||
size_t pos = tagPositions[tagId];
|
||||
assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position.");
|
||||
assertThrow(dev::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space.");
|
||||
bytesRef r(ret.bytecode.data() + i.first, bytesPerTag);
|
||||
toBigEndian(pos, r);
|
||||
}
|
||||
for (auto const& dataItem: m_data)
|
||||
{
|
||||
auto references = dataRef.equal_range(dataItem.first);
|
||||
|
@ -53,7 +53,6 @@ public:
|
||||
AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
|
||||
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
|
||||
|
||||
AssemblyItem append() { return append(newTag()); }
|
||||
void append(Assembly const& _a);
|
||||
void append(Assembly const& _a, int _deposit);
|
||||
AssemblyItem const& append(AssemblyItem const& _i);
|
||||
@ -110,6 +109,10 @@ public:
|
||||
) const;
|
||||
|
||||
protected:
|
||||
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
||||
/// returns the replaced tags.
|
||||
std::map<u256, u256> optimiseInternal(bool _isCreation, size_t _runs);
|
||||
|
||||
std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const;
|
||||
void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
|
||||
unsigned bytesRequired() const;
|
||||
@ -129,6 +132,7 @@ protected:
|
||||
std::map<h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
|
||||
|
||||
mutable LinkerObject m_assembledObject;
|
||||
mutable std::vector<size_t> m_tagPositionsInBytecode;
|
||||
|
||||
int m_deposit = 0;
|
||||
int m_baseDeposit = 0;
|
||||
|
@ -26,6 +26,29 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
|
||||
AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
|
||||
{
|
||||
assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set.");
|
||||
|
||||
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
|
||||
AssemblyItem r = *this;
|
||||
r.m_type = PushTag;
|
||||
r.setPushTagSubIdAndTag(_subId, size_t(m_data));
|
||||
return r;
|
||||
}
|
||||
|
||||
pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
|
||||
{
|
||||
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
|
||||
return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data));
|
||||
}
|
||||
|
||||
void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
|
||||
{
|
||||
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
|
||||
setData(_tag + ((u256(_subId) + 1) << 64));
|
||||
}
|
||||
|
||||
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
{
|
||||
switch (m_type)
|
||||
|
@ -69,6 +69,14 @@ public:
|
||||
|
||||
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); }
|
||||
/// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies.
|
||||
/// @param _subId the identifier of the subassembly the tag is taken from.
|
||||
AssemblyItem toSubAssemblyTag(size_t _subId) const;
|
||||
/// @returns splits the data of the push tag into sub assembly id and actual tag id.
|
||||
/// The sub assembly id of non-foreign push tags is -1.
|
||||
std::pair<size_t, size_t> splitForeignPushTag() const;
|
||||
/// Sets sub-assembly part and tag for a push tag.
|
||||
void setPushTagSubIdAndTag(size_t _subId, size_t _tag);
|
||||
|
||||
AssemblyItemType type() const { return m_type; }
|
||||
u256 const& data() const { return m_data; }
|
||||
|
@ -77,7 +77,6 @@ bool BlockDeduplicator::deduplicate()
|
||||
{
|
||||
//@todo this should probably be optimized.
|
||||
set<size_t, function<bool(size_t, size_t)>> blocksSeen(comparator);
|
||||
map<u256, u256> tagReplacement;
|
||||
for (size_t i = 0; i < m_items.size(); ++i)
|
||||
{
|
||||
if (m_items.at(i).type() != Tag)
|
||||
@ -86,22 +85,40 @@ bool BlockDeduplicator::deduplicate()
|
||||
if (it == blocksSeen.end())
|
||||
blocksSeen.insert(i);
|
||||
else
|
||||
tagReplacement[m_items.at(i).data()] = m_items.at(*it).data();
|
||||
m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data();
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (AssemblyItem& item: m_items)
|
||||
if (item.type() == PushTag && tagReplacement.count(item.data()))
|
||||
{
|
||||
changed = true;
|
||||
item.setData(tagReplacement.at(item.data()));
|
||||
}
|
||||
if (!changed)
|
||||
if (!applyTagReplacement(m_items, m_replacedTags))
|
||||
break;
|
||||
}
|
||||
return iterations > 0;
|
||||
}
|
||||
|
||||
bool BlockDeduplicator::applyTagReplacement(
|
||||
AssemblyItems& _items,
|
||||
map<u256, u256> const& _replacements,
|
||||
size_t _subId
|
||||
)
|
||||
{
|
||||
bool changed = false;
|
||||
for (AssemblyItem& item: _items)
|
||||
if (item.type() == PushTag)
|
||||
{
|
||||
size_t subId;
|
||||
size_t tagId;
|
||||
tie(subId, tagId) = item.splitForeignPushTag();
|
||||
if (subId != _subId)
|
||||
continue;
|
||||
auto it = _replacements.find(tagId);
|
||||
if (it != _replacements.end())
|
||||
{
|
||||
changed = true;
|
||||
item.setPushTagSubIdAndTag(subId, size_t(it->second));
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++()
|
||||
{
|
||||
if (it == end)
|
||||
|
@ -23,9 +23,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -45,6 +48,18 @@ public:
|
||||
BlockDeduplicator(AssemblyItems& _items): m_items(_items) {}
|
||||
/// @returns true if something was changed
|
||||
bool deduplicate();
|
||||
/// @returns the tags that were replaced.
|
||||
std::map<u256, u256> const& replacedTags() const { return m_replacedTags; }
|
||||
|
||||
/// Replaces all PushTag operations insied @a _items that match a key in
|
||||
/// @a _replacements by the respective value. If @a _subID is not -1, only
|
||||
/// apply the replacement for foreign tags from this sub id.
|
||||
/// @returns true iff a replacement was performed.
|
||||
static bool applyTagReplacement(
|
||||
AssemblyItems& _items,
|
||||
std::map<u256, u256> const& _replacements,
|
||||
size_t _subID = size_t(-1)
|
||||
);
|
||||
|
||||
private:
|
||||
/// Iterator that skips tags and skips to the end if (all branches of) the control
|
||||
@ -70,6 +85,7 @@ private:
|
||||
AssemblyItem const* replaceWith;
|
||||
};
|
||||
|
||||
std::map<u256, u256> m_replacedTags;
|
||||
AssemblyItems& m_items;
|
||||
};
|
||||
|
||||
|
@ -89,6 +89,11 @@ struct BasicBlock
|
||||
|
||||
using BasicBlocks = std::vector<BasicBlock>;
|
||||
|
||||
/**
|
||||
* Control flow graph optimizer.
|
||||
* ASSUMES THAT WE ONLY JUMP TO TAGS THAT WERE PREVIOUSLY PUSHED. THIS IS NOT TRUE ANYMORE
|
||||
* NOW THAT FUNCTION TAGS CAN BE STORED IN STORAGE.
|
||||
*/
|
||||
class ControlFlowGraph
|
||||
{
|
||||
public:
|
||||
|
@ -474,7 +474,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
|
||||
requireSize(2);
|
||||
requireDeposit(0, 1);
|
||||
|
||||
auto begin = m_asm.append();
|
||||
auto begin = m_asm.append(m_asm.newTag());
|
||||
m_asm.append(code[0].m_asm);
|
||||
if (us == "WHILE")
|
||||
m_asm.append(Instruction::ISZERO);
|
||||
@ -489,7 +489,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
|
||||
requireDeposit(1, 1);
|
||||
|
||||
m_asm.append(code[0].m_asm, 0);
|
||||
auto begin = m_asm.append();
|
||||
auto begin = m_asm.append(m_asm.newTag());
|
||||
m_asm.append(code[1].m_asm);
|
||||
m_asm.append(Instruction::ISZERO);
|
||||
auto end = m_asm.appendJumpI();
|
||||
|
@ -33,11 +33,13 @@ void Compiler::compileContract(
|
||||
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
|
||||
)
|
||||
{
|
||||
ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize);
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
|
||||
runtimeCompiler.compileContract(_contract, _contracts);
|
||||
|
||||
ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize);
|
||||
m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts);
|
||||
// This might modify m_runtimeContext because it can access runtime functions at
|
||||
// creation time.
|
||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize);
|
||||
m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts);
|
||||
|
||||
if (m_optimize)
|
||||
m_context.optimise(m_optimizeRuns);
|
||||
@ -54,7 +56,8 @@ void Compiler::compileClone(
|
||||
map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
||||
)
|
||||
{
|
||||
ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize);
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
|
||||
ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize);
|
||||
m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts);
|
||||
|
||||
if (m_optimize)
|
||||
|
@ -36,8 +36,8 @@ public:
|
||||
explicit Compiler(bool _optimize = false, unsigned _runs = 200):
|
||||
m_optimize(_optimize),
|
||||
m_optimizeRuns(_runs),
|
||||
m_context(CompilationMode::Creation, &m_runtimeContext),
|
||||
m_runtimeContext(CompilationMode::Runtime)
|
||||
m_runtimeContext(),
|
||||
m_context(&m_runtimeContext)
|
||||
{ }
|
||||
|
||||
void compileContract(
|
||||
@ -71,9 +71,9 @@ public:
|
||||
private:
|
||||
bool const m_optimize;
|
||||
unsigned const m_optimizeRuns;
|
||||
CompilerContext m_context;
|
||||
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
|
||||
CompilerContext m_runtimeContext;
|
||||
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
|
||||
CompilerContext m_context;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -92,22 +92,22 @@ eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const&
|
||||
return m_functionCompilationQueue.entryLabelIfExists(_declaration);
|
||||
}
|
||||
|
||||
eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function)
|
||||
FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
// Libraries do not allow inheritance and their functions can be inlined, so we should not
|
||||
// search the inheritance hierarchy (which will be the wrong one in case the function
|
||||
// is inlined).
|
||||
if (auto scope = dynamic_cast<ContractDefinition const*>(_function.scope()))
|
||||
if (scope->isLibrary())
|
||||
return functionEntryLabel(_function);
|
||||
return _function;
|
||||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
|
||||
return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin());
|
||||
return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin());
|
||||
}
|
||||
|
||||
eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base)
|
||||
FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base)
|
||||
{
|
||||
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
|
||||
return virtualFunctionEntryLabel(_function, superContract(_base));
|
||||
return resolveVirtualFunction(_function, superContract(_base));
|
||||
}
|
||||
|
||||
FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const
|
||||
@ -227,7 +227,7 @@ void CompilerContext::injectVersionStampIntoSub(size_t _subIndex)
|
||||
sub.injectStart(fromBigEndian<u256>(binaryVersion()));
|
||||
}
|
||||
|
||||
eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(
|
||||
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
|
||||
FunctionDefinition const& _function,
|
||||
vector<ContractDefinition const*>::const_iterator _searchStart
|
||||
)
|
||||
@ -242,9 +242,9 @@ eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(
|
||||
!function->isConstructor() &&
|
||||
FunctionType(*function).hasEqualArgumentTypes(functionType)
|
||||
)
|
||||
return functionEntryLabel(*function);
|
||||
return *function;
|
||||
solAssert(false, "Super function " + name + " not found.");
|
||||
return m_asm.newTag(); // not reached
|
||||
return _function; // not reached
|
||||
}
|
||||
|
||||
vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const
|
||||
|
@ -37,10 +37,6 @@ namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
|
||||
/// Depending on the compilation is on the runtime code or the creation code,
|
||||
/// the interpretation of internal function values differ.
|
||||
enum class CompilationMode { Runtime, Creation };
|
||||
|
||||
/**
|
||||
* Context to be shared by all units that compile the same contract.
|
||||
* It stores the generated bytecode and the position of identifiers in memory and on the stack.
|
||||
@ -48,15 +44,13 @@ enum class CompilationMode { Runtime, Creation };
|
||||
class CompilerContext
|
||||
{
|
||||
public:
|
||||
CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) :
|
||||
m_mode(_mode), m_runtimeContext(_runtimeContext)
|
||||
CompilerContext(CompilerContext* _runtimeContext = nullptr) :
|
||||
m_runtimeContext(_runtimeContext)
|
||||
{
|
||||
solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided");
|
||||
solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided");
|
||||
if (m_runtimeContext)
|
||||
m_runtimeSub = registerSubroutine(m_runtimeContext->assembly());
|
||||
}
|
||||
|
||||
bool isCreationPhase() const { return m_mode == CompilationMode::Creation; }
|
||||
|
||||
void addMagicGlobal(MagicVariableDeclaration const& _declaration);
|
||||
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
|
||||
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
|
||||
@ -80,10 +74,10 @@ public:
|
||||
eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
|
||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
|
||||
/// @returns the entry label of the given function and takes overrides into account.
|
||||
eth::AssemblyItem virtualFunctionEntryLabel(FunctionDefinition const& _function);
|
||||
/// @returns the entry label of a function that overrides the given declaration from the most derived class just
|
||||
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
|
||||
/// @returns the function that overrides the given declaration from the most derived class just
|
||||
/// above _base in the current inheritance hierarchy.
|
||||
eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base);
|
||||
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
|
||||
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
|
||||
|
||||
/// @returns the next function in the queue of functions that are still to be compiled
|
||||
@ -123,11 +117,15 @@ public:
|
||||
eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); }
|
||||
/// @returns a new tag without pushing any opcodes or data
|
||||
eth::AssemblyItem newTag() { return m_asm.newTag(); }
|
||||
/// Adds a subroutine to the code (in the data section)
|
||||
/// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list.
|
||||
size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); }
|
||||
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
||||
/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
|
||||
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); }
|
||||
/// on the stack. @returns the pushsub assembly item.
|
||||
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; }
|
||||
void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); }
|
||||
/// Pushes the size of the final program
|
||||
void appendProgramSize() { return m_asm.appendProgramSize(); }
|
||||
void appendProgramSize() { m_asm.appendProgramSize(); }
|
||||
/// Adds data to the data section, pushes a reference to the stack
|
||||
eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); }
|
||||
/// Appends the address (virtual, will be filled in by linker) of a library.
|
||||
@ -159,6 +157,11 @@ public:
|
||||
|
||||
void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); }
|
||||
|
||||
/// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise.
|
||||
CompilerContext* runtimeContext() { return m_runtimeContext; }
|
||||
/// @returns the identifier of the runtime subroutine.
|
||||
size_t runtimeSub() const { return m_runtimeSub; }
|
||||
|
||||
eth::Assembly const& assembly() const { return m_asm; }
|
||||
/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
|
||||
/// wrappers in this class.
|
||||
@ -185,9 +188,9 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
/// @returns the entry label of the given function - searches the inheritance hierarchy
|
||||
/// startig from the given point towards the base.
|
||||
eth::AssemblyItem virtualFunctionEntryLabel(
|
||||
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
|
||||
/// the first function definition that is overwritten by _function.
|
||||
FunctionDefinition const& resolveVirtualFunction(
|
||||
FunctionDefinition const& _function,
|
||||
std::vector<ContractDefinition const*>::const_iterator _searchStart
|
||||
);
|
||||
@ -241,10 +244,10 @@ private:
|
||||
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
|
||||
/// Stack of current visited AST nodes, used for location attachment
|
||||
std::stack<ASTNode const*> m_visitedNodes;
|
||||
/// The current mode of the compilation
|
||||
CompilationMode m_mode;
|
||||
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.
|
||||
CompilerContext *m_runtimeContext;
|
||||
/// The index of the runtime subroutine.
|
||||
size_t m_runtimeSub = -1;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -340,6 +340,19 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
|
||||
m_context << Instruction::OR;
|
||||
}
|
||||
|
||||
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
|
||||
{
|
||||
m_context << m_context.functionEntryLabel(_function).pushTag();
|
||||
// If there is a runtime context, we have to merge both labels into the same
|
||||
// stack slot in case we store it in storage.
|
||||
if (CompilerContext* rtc = m_context.runtimeContext())
|
||||
m_context <<
|
||||
(u256(1) << 32) <<
|
||||
Instruction::MUL <<
|
||||
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
|
||||
Instruction::OR;
|
||||
}
|
||||
|
||||
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded)
|
||||
{
|
||||
// For a type extension, we need to remove all higher-order bits that we might have ignored in
|
||||
|
@ -120,6 +120,10 @@ public:
|
||||
void splitExternalFunctionType(bool _rightAligned);
|
||||
/// Performs the opposite operation of splitExternalFunctionType(_rightAligned)
|
||||
void combineExternalFunctionType(bool _rightAligned);
|
||||
/// Appends code that combines the construction-time (if available) and runtime function
|
||||
/// entry label of the given function into a single stack slot.
|
||||
/// Note: This might cause the compilation queue of the runtime context to be extended.
|
||||
void pushCombinedFunctionEntryLabel(Declaration const& _function);
|
||||
|
||||
/// Appends code for an implicit or explicit type conversion. This includes erasing higher
|
||||
/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
|
||||
|
@ -60,14 +60,13 @@ void ContractCompiler::compileContract(
|
||||
}
|
||||
|
||||
size_t ContractCompiler::compileConstructor(
|
||||
CompilerContext const& _runtimeContext,
|
||||
ContractDefinition const& _contract,
|
||||
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
|
||||
)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||
initializeContext(_contract, _contracts);
|
||||
return packIntoContractCreator(_contract, _runtimeContext);
|
||||
return packIntoContractCreator(_contract);
|
||||
}
|
||||
|
||||
size_t ContractCompiler::compileClone(
|
||||
@ -141,21 +140,31 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
|
||||
appendBaseConstructor(*c);
|
||||
}
|
||||
|
||||
size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext)
|
||||
size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(!!m_runtimeCompiler, "");
|
||||
|
||||
appendInitAndConstructorCode(_contract);
|
||||
|
||||
eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly());
|
||||
// We jump to the deploy routine because we first have to append all missing functions,
|
||||
// which can cause further functions to be added to the runtime context.
|
||||
eth::AssemblyItem deployRoutine = m_context.appendJumpToNew();
|
||||
|
||||
// We have to include copies of functions in the construction time and runtime context
|
||||
// because of absolute jumps.
|
||||
appendMissingFunctions();
|
||||
m_runtimeCompiler->appendMissingFunctions();
|
||||
|
||||
m_context << deployRoutine;
|
||||
|
||||
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
|
||||
m_context.appendSubroutineSize(m_context.runtimeSub());
|
||||
|
||||
// stack contains sub size
|
||||
m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY;
|
||||
m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY;
|
||||
m_context << u256(0) << Instruction::RETURN;
|
||||
|
||||
// note that we have to include the functions again because of absolute jump labels
|
||||
appendMissingFunctions();
|
||||
|
||||
solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), "");
|
||||
return size_t(runtimeSub.data());
|
||||
return m_context.runtimeSub();
|
||||
}
|
||||
|
||||
void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor)
|
||||
@ -516,7 +525,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
|
||||
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
|
||||
_assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag());
|
||||
{
|
||||
functionDef = &m_context.resolveVirtualFunction(*functionDef);
|
||||
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
|
||||
// If there is a runtime context, we have to merge both labels into the same
|
||||
// stack slot in case we store it in storage.
|
||||
if (CompilerContext* rtc = m_context.runtimeContext())
|
||||
{
|
||||
_assembly.append(u256(1) << 32);
|
||||
_assembly.append(Instruction::MUL);
|
||||
_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
|
||||
_assembly.append(Instruction::OR);
|
||||
}
|
||||
}
|
||||
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
|
||||
{
|
||||
solAssert(!variable->isConstant(), "");
|
||||
|
@ -38,11 +38,12 @@ namespace solidity {
|
||||
class ContractCompiler: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise):
|
||||
explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise):
|
||||
m_optimise(_optimise),
|
||||
m_runtimeCompiler(_runtimeCompiler),
|
||||
m_context(_context)
|
||||
{
|
||||
m_context = CompilerContext(_mode, _runtimeContext);
|
||||
m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr);
|
||||
}
|
||||
|
||||
void compileContract(
|
||||
@ -52,7 +53,6 @@ public:
|
||||
/// Compiles the constructor part of the contract.
|
||||
/// @returns the identifier of the runtime sub-assembly.
|
||||
size_t compileConstructor(
|
||||
CompilerContext const& _runtimeContext,
|
||||
ContractDefinition const& _contract,
|
||||
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
||||
);
|
||||
@ -74,7 +74,7 @@ private:
|
||||
/// Adds the code that is run at creation time. Should be run after exchanging the run-time context
|
||||
/// with a new and initialized context. Adds the constructor code.
|
||||
/// @returns the identifier of the runtime sub assembly
|
||||
size_t packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext);
|
||||
size_t packIntoContractCreator(ContractDefinition const& _contract);
|
||||
/// Appends state variable initialisation and constructor code.
|
||||
void appendInitAndConstructorCode(ContractDefinition const& _contract);
|
||||
void appendBaseConstructor(FunctionDefinition const& _constructor);
|
||||
@ -117,6 +117,8 @@ private:
|
||||
static eth::Assembly cloneRuntime();
|
||||
|
||||
bool const m_optimise;
|
||||
/// Pointer to the runtime compiler in case this is a creation compiler.
|
||||
ContractCompiler* m_runtimeCompiler = nullptr;
|
||||
CompilerContext& m_context;
|
||||
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
|
||||
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
|
||||
|
@ -488,6 +488,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
parameterSize += function.selfType()->sizeOnStack();
|
||||
}
|
||||
|
||||
if (m_context.runtimeContext())
|
||||
// We have a runtime context, so we need the creation part.
|
||||
m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
|
||||
else
|
||||
// Extract the runtime part.
|
||||
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
||||
|
||||
m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction);
|
||||
m_context << returnLabel;
|
||||
|
||||
@ -845,9 +852,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
);
|
||||
if (funType->location() == FunctionType::Location::Internal)
|
||||
{
|
||||
m_context << m_context.functionEntryLabel(
|
||||
dynamic_cast<FunctionDefinition const&>(funType->declaration())
|
||||
).pushTag();
|
||||
FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration());
|
||||
utils().pushCombinedFunctionEntryLabel(funDef);
|
||||
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1);
|
||||
}
|
||||
else
|
||||
@ -883,7 +889,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
// us to link against it although we actually do not need it.
|
||||
auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
|
||||
solAssert(!!function, "Function not found in member access");
|
||||
m_context << m_context.functionEntryLabel(*function).pushTag();
|
||||
utils().pushCombinedFunctionEntryLabel(*function);
|
||||
}
|
||||
}
|
||||
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type.get()))
|
||||
@ -915,10 +921,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
if (type.isSuper())
|
||||
{
|
||||
solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
|
||||
m_context << m_context.superFunctionEntryLabel(
|
||||
utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
|
||||
dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
|
||||
type.contractDefinition()
|
||||
).pushTag();
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1203,7 +1209,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
|
||||
}
|
||||
}
|
||||
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag();
|
||||
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
|
||||
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
appendVariable(*variable, static_cast<Expression const&>(_identifier));
|
||||
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
@ -1266,6 +1272,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type
|
||||
{
|
||||
if (_operator == Token::Equal || _operator == Token::NotEqual)
|
||||
{
|
||||
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
|
||||
{
|
||||
if (funType->location() == FunctionType::Location::Internal)
|
||||
{
|
||||
// We have to remove the upper bits (construction time value) because they might
|
||||
// be "unknown" in one of the operands and not in the other.
|
||||
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
||||
m_context << Instruction::SWAP1;
|
||||
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
|
||||
}
|
||||
}
|
||||
m_context << Instruction::EQ;
|
||||
if (_operator == Token::NotEqual)
|
||||
m_context << Instruction::ISZERO;
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <libsolidity/interface/InterfaceHandler.h>
|
||||
#include <libsolidity/formal/Why3Translator.h>
|
||||
|
||||
#include <libevmasm/Exceptions.h>
|
||||
#include <libdevcore/SHA3.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -590,9 +591,19 @@ void CompilerStack::compileContract(
|
||||
compiledContract.runtimeObject = compiler->runtimeObject();
|
||||
_compiledContracts[compiledContract.contract] = &compiler->assembly();
|
||||
|
||||
Compiler cloneCompiler(_optimize, _runs);
|
||||
cloneCompiler.compileClone(_contract, _compiledContracts);
|
||||
compiledContract.cloneObject = cloneCompiler.assembledObject();
|
||||
try
|
||||
{
|
||||
Compiler cloneCompiler(_optimize, _runs);
|
||||
cloneCompiler.compileClone(_contract, _compiledContracts);
|
||||
compiledContract.cloneObject = cloneCompiler.assembledObject();
|
||||
}
|
||||
catch (eth::AssemblyException const& _e)
|
||||
{
|
||||
// In some cases (if the constructor requests a runtime function), it is not
|
||||
// possible to compile the clone.
|
||||
|
||||
// TODO: Report error / warning
|
||||
}
|
||||
}
|
||||
|
||||
std::string CompilerStack::defaultContractName() const
|
||||
|
@ -7777,6 +7777,48 @@ BOOST_AUTO_TEST_CASE(store_function_in_constructor)
|
||||
BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4)));
|
||||
}
|
||||
|
||||
// TODO: store bound internal library functions
|
||||
|
||||
BOOST_AUTO_TEST_CASE(store_internal_unused_function_in_constructor)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
function () internal returns (uint) x;
|
||||
function C () {
|
||||
x = unused;
|
||||
}
|
||||
function unused() internal returns (uint) {
|
||||
return 7;
|
||||
}
|
||||
function t() returns (uint) {
|
||||
return x();
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(store_internal_unused_library_function_in_constructor)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library L { function x() internal returns (uint) { return 7; } }
|
||||
contract C {
|
||||
function () internal returns (uint) x;
|
||||
function C () {
|
||||
x = L.x;
|
||||
}
|
||||
function t() returns (uint) {
|
||||
return x();
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
@ -7799,6 +7841,27 @@ BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime)
|
||||
BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime_equality_check)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
function (uint) internal returns (uint) x;
|
||||
function C() {
|
||||
x = double;
|
||||
}
|
||||
function test() returns (bool) {
|
||||
return x == double;
|
||||
}
|
||||
function double(uint _arg) returns (uint _ret) {
|
||||
_ret = _arg * 2;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("test()") == encodeArgs(true));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_type_library_internal)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
|
@ -136,7 +136,7 @@ bytes compileFirstExpression(
|
||||
FirstExpressionExtractor extractor(*contract);
|
||||
BOOST_REQUIRE(extractor.expression() != nullptr);
|
||||
|
||||
CompilerContext context { CompilationMode::Runtime /* probably simpler */ };
|
||||
CompilerContext context;
|
||||
context.resetVisitedNodes(contract);
|
||||
context.setInheritanceHierarchy(inheritanceHierarchy);
|
||||
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
|
||||
|
Loading…
Reference in New Issue
Block a user