Skip to content

Commit

Permalink
Implement changes in the codegen, standard-json interface etc. for th…
Browse files Browse the repository at this point in the history
…e downstream toolchain

- Change the version dump.
- Disable EVM bytecode generation.
- Report recursive functions in a new standard-json output field called
  "extraMetadata" so that zksolc can lower them correctly.
- Disable the optimiser in the legacy pipeline.
- Generate a simpler, but equivalent, control flow in the try-catch lowering in
  the legacy pipeline to not confuse the zksolc translator.
- Generate a dispatch table in the lowering of indirect internal function call
  in the legacy pipeline.
- Define ZKEVM yul intrinsics.
  • Loading branch information
abinavpp committed Oct 11, 2024
1 parent 7893614 commit a13ad92
Show file tree
Hide file tree
Showing 73 changed files with 1,326 additions and 18 deletions.
1 change: 1 addition & 0 deletions cmake/EthBuildInfo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function(create_build_info NAME)
-DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}"
-DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}"
-DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}"
-DSOL_VERSION_ZKSYNC="${SOL_VERSION_ZKSYNC}"
-P "${ETH_SCRIPTS_DIR}/buildinfo.cmake"
)
include_directories("${PROJECT_BINARY_DIR}/include")
Expand Down
5 changes: 4 additions & 1 deletion cmake/scripts/buildinfo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ if (EXISTS ${ETH_SOURCE_DIR}/prerelease.txt)
file(READ ${ETH_SOURCE_DIR}/prerelease.txt SOL_VERSION_PRERELEASE)
string(STRIP "${SOL_VERSION_PRERELEASE}" SOL_VERSION_PRERELEASE)
else()
string(TIMESTAMP SOL_VERSION_PRERELEASE "develop.%Y.%m.%d" UTC)
string(REPLACE .0 . SOL_VERSION_PRERELEASE "${SOL_VERSION_PRERELEASE}")
endif()

Expand Down Expand Up @@ -64,6 +63,10 @@ set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}")
set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM)
set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}")

if (NOT SOL_VERSION_ZKSYNC)
set(SOL_VERSION_ZKSYNC "undefined")
endif()

set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp")
set(OUTFILE "${ETH_DST_DIR}/BuildInfo.h")

Expand Down
1 change: 1 addition & 0 deletions cmake/templates/BuildInfo.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
#define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@"
#define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@"
#define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@"
#define SOL_VERSION_ZKSYNC "@SOL_VERSION_ZKSYNC@"
2 changes: 1 addition & 1 deletion libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier)

Assembly& Assembly::optimise(OptimiserSettings const& _settings)
{
optimiseInternal(_settings, {});
(void) _settings;
return *this;
}

Expand Down
1 change: 1 addition & 0 deletions libevmasm/LinkerObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ void LinkerObject::link(std::map<std::string, h160> const& _libraryAddresses)

std::string LinkerObject::toHex() const
{
return "The EVM bytecode is unavailable in the ZKsync edition of solc";
std::string hex = solidity::util::toHex(bytecode);
for (auto const& ref: linkReferences)
{
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ set(sources
codegen/ContractCompiler.h
codegen/ExpressionCompiler.cpp
codegen/ExpressionCompiler.h
codegen/ExtraMetadata.cpp
codegen/ExtraMetadata.h
codegen/FuncPtrTracker.cpp
codegen/FuncPtrTracker.h
codegen/LValue.cpp
codegen/LValue.h
codegen/MultiUseYulFunctionCollector.h
Expand Down
11 changes: 11 additions & 0 deletions libsolidity/analysis/FunctionCallGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,21 @@ bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall)
solAssert(functionType, "");

if (functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly)
{
for (FunctionDefinition const* funcPtrRef: m_contract.annotation().intFuncPtrRefs)
{
FunctionType const* funcPtrRefType = funcPtrRef->functionType(/*_internal=*/true);
solAssert(funcPtrRefType, "");
if (!funcPtrRefType->hasEqualParameterTypes(*functionType)
|| !funcPtrRefType->hasEqualReturnTypes(*functionType) || !funcPtrRef->isImplemented())
continue;
m_graph.indirectEdges[m_currentNode].insert(funcPtrRef);
}
// If it's not a direct call, we don't really know which function will be called (it may even
// change at runtime). All we can do is to add an edge to the dispatch which in turn has
// edges to all functions could possibly be called.
add(m_currentNode, CallGraph::SpecialNode::InternalDispatch);
}
else if (functionType->kind() == FunctionType::Kind::Error)
m_graph.usedErrors.insert(&dynamic_cast<ErrorDefinition const&>(functionType->declaration()));

Expand Down
13 changes: 13 additions & 0 deletions libsolidity/ast/ASTAnnotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

#include <libsolutil/SetOnce.h>

#include <libyul/AST.h>

#include <map>
#include <memory>
#include <optional>
Expand Down Expand Up @@ -164,6 +166,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu
util::SetOnce<std::shared_ptr<CallGraph const>> creationCallGraph;
/// A graph with edges representing calls between functions that may happen in a deployed contract.
util::SetOnce<std::shared_ptr<CallGraph const>> deployedCallGraph;
/// Set of internal functions referenced as function pointers
std::set<FunctionDefinition const*, ASTCompareByID<FunctionDefinition>> intFuncPtrRefs;

/// List of contracts whose bytecode is referenced by this contract, e.g. through "new".
/// The Value represents the ast node that referenced the contract.
Expand Down Expand Up @@ -226,6 +230,8 @@ struct InlineAssemblyAnnotation: StatementAnnotation
bool markedMemorySafe = false;
/// True, if the assembly block involves any memory opcode or assigns to variables in memory.
util::SetOnce<bool> hasMemoryEffects;
/// The yul block of the InlineAssembly::operations() after optimizations.
std::shared_ptr<const yul::AST> optimizedOperations;
};

struct BlockAnnotation: StatementAnnotation, ScopableAnnotation
Expand Down Expand Up @@ -342,6 +348,13 @@ struct FunctionCallAnnotation: ExpressionAnnotation
util::SetOnce<FunctionCallKind> kind;
/// If true, this is the external call of a try statement.
bool tryCall = false;

// HACK!
// We track the success tag here for the `TryStatement` lowering. This is to avoid the redundant status check and
// the conditional jump. Such patterns can confuse the zksolc translator.
//
// uint32_t since Assembly::new[Push]Tag() asserts that the tag is 32 bits.
std::optional<uint32_t> tryCallSuccessTag;
};

/// Experimental Solidity annotations.
Expand Down
129 changes: 129 additions & 0 deletions libsolidity/ast/CallGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,132 @@ bool CallGraph::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const

return _lhs < std::get<CallableDeclaration const*>(_rhs)->id();
}

/// Populates reachable cycles from m_src into paths;
class CycleFinder
{
CallGraph const& m_callGraph;
CallableDeclaration const* m_src;
std::set<CallableDeclaration const*> m_processing;
std::set<CallableDeclaration const*> m_processed;
std::vector<CallGraph::Path> m_paths;

/// Populates `m_paths` with cycles reachable from @a _callable
void getCyclesInternal(CallableDeclaration const* _callable, CallGraph::Path& _path)
{
if (m_processed.count(_callable))
return;

auto directCallees = m_callGraph.edges.find(_callable);
auto indirectCallees = m_callGraph.indirectEdges.find(_callable);
// Is _callable a leaf node?
if (directCallees == m_callGraph.edges.end() && indirectCallees == m_callGraph.indirectEdges.end())
{
solAssert(m_processing.count(_callable) == 0, "");
m_processed.insert(_callable);
return;
}

m_processing.insert(_callable);
_path.push_back(_callable);

// Traverse all the direct and indirect callees
std::set<CallGraph::Node, CallGraph::CompareByID> callees;
if (directCallees != m_callGraph.edges.end())
callees.insert(directCallees->second.begin(), directCallees->second.end());
if (indirectCallees != m_callGraph.indirectEdges.end())
callees.insert(indirectCallees->second.begin(), indirectCallees->second.end());
for (auto const& calleeVariant: callees)
{
if (!std::holds_alternative<CallableDeclaration const*>(calleeVariant))
continue;
auto* callee = std::get<CallableDeclaration const*>(calleeVariant);

if (m_processing.count(callee))
{
// Extract the cycle
auto cycleStart = std::find(_path.begin(), _path.end(), callee);
solAssert(cycleStart != _path.end(), "");
m_paths.emplace_back(cycleStart, _path.end());
continue;
}

getCyclesInternal(callee, _path);
}

m_processing.erase(_callable);
m_processed.insert(_callable);
_path.pop_back();
}

public:
CycleFinder(CallGraph const& _callGraph, CallableDeclaration const* _src): m_callGraph(_callGraph), m_src(_src) {}

std::vector<CallGraph::Path> getCycles()
{
CallGraph::Path p;
getCyclesInternal(m_src, p);
return m_paths;
}

void dump(std::ostream& _out)
{
for (CallGraph::Path const& path: m_paths)
{
for (CallableDeclaration const* func: path)
_out << func->name() << " -> ";
_out << "\n";
}
}
};

void CallGraph::getReachableFuncs(CallableDeclaration const* _src, std::set<CallableDeclaration const*>& _funcs) const
{
if (_funcs.count(_src))
return;
_funcs.insert(_src);

auto directCallees = edges.find(_src);
auto indirectCallees = indirectEdges.find(_src);
// Is _src a leaf node?
if (directCallees == edges.end() && indirectCallees == indirectEdges.end())
return;

// Traverse all the direct and indirect callees
std::set<CallGraph::Node, CallGraph::CompareByID> callees;
if (directCallees != edges.end())
callees.insert(directCallees->second.begin(), directCallees->second.end());
if (indirectCallees != indirectEdges.end())
callees.insert(indirectCallees->second.begin(), indirectCallees->second.end());

for (auto const& calleeVariant: callees)
{
if (!std::holds_alternative<CallableDeclaration const*>(calleeVariant))
continue;
auto* callee = std::get<CallableDeclaration const*>(calleeVariant);
getReachableFuncs(callee, _funcs);
}
}

std::set<CallableDeclaration const*> CallGraph::getReachableFuncs(CallableDeclaration const* _src) const
{
std::set<CallableDeclaration const*> funcs;
getReachableFuncs(_src, funcs);
return funcs;
}

std::set<CallableDeclaration const*> CallGraph::getReachableCycleFuncs(CallableDeclaration const* _src) const
{
std::set<CallableDeclaration const*> funcs;
CycleFinder cf{*this, _src};
std::vector<CallGraph::Path> paths = cf.getCycles();

for (CallGraph::Path const& path: paths)
{
for (CallableDeclaration const* func: path)
{
funcs.insert(func);
}
}
return funcs;
}
15 changes: 15 additions & 0 deletions libsolidity/ast/CallGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct CallGraph
};

using Node = std::variant<CallableDeclaration const*, SpecialNode>;
using Path = std::vector<CallableDeclaration const*>;

struct CompareByID
{
Expand All @@ -61,6 +62,9 @@ struct CallGraph
/// any calls.
std::map<Node, std::set<Node, CompareByID>, CompareByID> edges;

/// Graph edges for indirect calls
std::map<Node, std::set<Node, CompareByID>, CompareByID> indirectEdges;

/// Contracts that need to be compiled before this one can be compiled.
/// The value is the ast node that created the dependency.
std::map<ContractDefinition const*, ASTNode const*, ASTCompareByID<ContractDefinition>> bytecodeDependency;
Expand All @@ -70,6 +74,17 @@ struct CallGraph

/// Errors that are used by functions present in the graph.
std::set<ErrorDefinition const*, ASTNode::CompareByID> usedErrors;

/// Returns functions reachable from @a _src that belong to a cycle. Note that the cycle can be due to indirect
/// calls.
std::set<CallableDeclaration const*> getReachableCycleFuncs(CallableDeclaration const* _src) const;

/// Returns functions reachable (including the ones from indirect calls) from @a _src.
std::set<CallableDeclaration const*> getReachableFuncs(CallableDeclaration const* _src) const;

private:
/// Populates @a _funcs with the functions reachable (including the ones from indirect calls) from @a _src.
void getReachableFuncs(CallableDeclaration const* _src, std::set<CallableDeclaration const*>& _funcs) const;
};

}
22 changes: 22 additions & 0 deletions libsolidity/codegen/ArrayUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,17 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
_context << Instruction::POP;
}
);

if (auto* structType = dynamic_cast<StructType const*>(_sourceType.baseType()))
{
if (structType->recursive())
{
std::string name{"$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier()};
auto tag = m_context.lowLevelFunctionTagIfExists(name);
solAssert(tag != evmasm::AssemblyItem(evmasm::UndefinedItem), "");
m_context.addRecursiveLowLevelFunc({name, tag.data().convert_to<uint32_t>(), /*ins=*/3, /*outs=*/1});
}
}
}

void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
Expand Down Expand Up @@ -598,6 +609,17 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const
solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
);

if (auto* structType = dynamic_cast<StructType const*>(_typeIn.baseType()))
{
if (structType->recursive())
{
std::string name{"$clearArray_" + _typeIn.identifier()};
auto tag = m_context.lowLevelFunctionTagIfExists(name);
solAssert(tag != evmasm::AssemblyItem(evmasm::UndefinedItem), "");
m_context.addRecursiveLowLevelFunc({name, tag.data().convert_to<uint32_t>(), /*ins=*/2, /*outs=*/0});
}
}
}

void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
Expand Down
5 changes: 5 additions & 0 deletions libsolidity/codegen/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <libsolidity/codegen/Compiler.h>

#include <libsolidity/codegen/ContractCompiler.h>
#include <libsolidity/codegen/ExtraMetadata.h>

#include <libevmasm/Assembly.h>

#include <range/v3/algorithm/none_of.hpp>
Expand Down Expand Up @@ -64,6 +66,9 @@ void Compiler::compileContract(

m_context.optimise(m_optimiserSettings);

ExtraMetadataRecorder extraMetadataRecorder{m_context, m_runtimeContext};
m_extraMetadata = extraMetadataRecorder.run(_contract);

solAssert(m_context.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called.");
solAssert(m_runtimeContext.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called.");
}
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/codegen/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ class Compiler

std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); }
std::string runtimeGeneratedYulUtilityCode() const { return m_runtimeContext.generatedYulUtilityCode(); }
Json extraMetadata() const { return m_extraMetadata; }

private:
Json m_extraMetadata;
OptimiserSettings const m_optimiserSettings;
CompilerContext m_runtimeContext;
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
Expand Down
12 changes: 11 additions & 1 deletion libsolidity/codegen/CompilerContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
return it->second;
}

evmasm::AssemblyItem CompilerContext::lowLevelFunctionTagIfExists(std::string const& _name)
{
auto it = m_lowLevelFunctions.find(_name);
if (it == m_lowLevelFunctions.end())
return evmasm::AssemblyItem(evmasm::UndefinedItem);
else
return it->second;
}

void CompilerContext::appendMissingLowLevelFunctions()
{
while (!m_lowLevelFunctionGenerationQueue.empty())
Expand Down Expand Up @@ -516,13 +525,14 @@ void CompilerContext::appendInlineAssembly(
if (errorReporter.hasErrorsWarningsOrInfos())
reportError("Failed to analyze inline assembly block.");

solAssert(!errorReporter.hasErrorsWarningsOrInfos(), "Failed to analyze inline assembly block.");
std::shared_ptr<yul::CodeTransformContext> yulContext;
yul::CodeGenerator::assemble(
toBeAssembledAST->root(),
analysisInfo,
*m_asm,
m_evmVersion,
std::nullopt,
yulContext,
identifierAccess.generateCode,
_system,
_optimiserSettings.optimizeStackAllocation
Expand Down
Loading

0 comments on commit a13ad92

Please sign in to comment.