Skip to content

Commit

Permalink
Merge pull request #141 from nevillegrech/inliner_improvements
Browse files Browse the repository at this point in the history
Function inliner refactor
  • Loading branch information
sifislag committed Jun 18, 2024
2 parents 35cc700 + fd31be1 commit 8bdfe26
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 38 deletions.
11 changes: 11 additions & 0 deletions clientlib/flows.dl
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,17 @@ localFlowAnalysis.TransferBoundary(block) :- IsBlock(block).

#define LocalFlows localFlowAnalysis.Flows

/**
Local flows excluding PHIs
To be used when more precise, local inferrences are needed
*/
.init altLocalFlowAnalysis = LocalFlowAnalysis

altLocalFlowAnalysis.TransferStmt(stmt) :- FlowOp(op), Statement_Opcode(stmt, op), op != "PHI".
altLocalFlowAnalysis.TransferBoundary(block) :- IsBlock(block).

#define LocalFlowsExclPHI altLocalFlowAnalysis.Flows

// x controls whether y is executed.
.decl Controls(x:Statement, y:Block)

Expand Down
161 changes: 130 additions & 31 deletions clientlib/function_inliner.dl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "tac-transformers/abstract_function_inliner.dl"
#include "memory_modeling/memory_modeling.dl"

#include "storage_modeling/storage_modeling.dl"

/**
Inliner was designed as a component in order to be able to have
Expand All @@ -18,10 +18,58 @@
.comp AnalysisHelpInliner : FunctionInliner {

.override InlineCandidate
//.output InlineCandidate
// .output InlineCandidate, FunctionToInline, IsPublicFunction, FunctionIsInner, SafelyInlinableFunction

.decl HighLevelStatement(stmt: Statement)
DEBUG_OUTPUT(HighLevelStatement)
HighLevelStatement(stmt):-
Statement_Opcode(stmt, op),
(
op = "CALLPRIVATE";
op = "CALL";
op = "STATICCALL";
op = "DELEGATECALL";
op = "REVERT";
op = "THROW";
op = "SSTORE";
op = "CREATE";
op = "CREATE2"
).

HighLevelStatement(stmt):-
Analytics_NonModeledMSTORE(stmt);
LOGStatement(stmt, _);
StructStore(stmt, _, _, _);
StructAllocation(stmt, _, _);
ArrayAllocationInternal(stmt, _, _, _).

HighLevelStatement(stmt):-
ArrayStore(stmt, arrRep, _),
!ConstArray_Contents(arrRep, _).

HighLevelStatement(ret):-
In_Statement_Opcode(ret, "RETURNPRIVATE"),
In_Statement_Uses(ret, _, pos),
pos >= 1.

.decl Function_HighLevelStmts(fun: Function, numOfStmts: number)
DEBUG_OUTPUT(Function_HighLevelStmts)
Function_HighLevelStmts(fun, numOfStmts):-
In_CallGraphEdge(_, fun),
numOfStmts = count : { Statement_Function(stmt, fun), HighLevelStatement(stmt) }.

InlineCandidate(fun, "VerySmall"):-
In_CallGraphEdge(_, fun),
Function_HighLevelStmts(fun, 1).

// Functions that have a single high-level stmt and no actual returns
// InlineCandidate(fun):-
// In_CallGraphEdge(_, fun),
// 1 = count : { Statement_Function(stmt, fun), HighLevelStatement(stmt) },
// !In_FormalReturnArgs(fun, _, 0).

/* Inline guards to help our analysis */
InlineCandidate(fun):-
InlineCandidate(fun, "WrapperMethod"):-
(In_Statement_Opcode(callerOp, "CALLER"); In_Statement_Opcode(callerOp, "ORIGIN")),
In_Statement_Defines(callerOp, sender, 0),
LocalFlows(sender, formalRet),
Expand All @@ -31,7 +79,8 @@
IsUtilFunc(fun):-
(prefix = "abi_encode_";
prefix = "calldata_";
prefix = "checked_";
// prefix = "checked_";
// don't want to inline checked arithmetic functions
prefix = "extract_";
prefix = "getter_";
prefix = "mapping_index_";
Expand All @@ -48,12 +97,13 @@ IsUtilFunc(fun):-
HighLevelFunctionName(fun, funName),
prefix=substr(funName, 0, strlen(prefix)).

InlineCandidate(fun):-
InlineCandidate(fun, "Util"):-
In_CallGraphEdge(_, fun),
IsUtilFunc(fun).


/* To detect some storage constructs breaking through functions */
InlineCandidate(fun):-
InlineCandidate(fun, "Storage1"):-
(SHA3(_, _, _, var) ; Variable_Value(var, _)),
LocalFlows(var, actualArg),
In_ActualArgs(caller, actualArg, pos),
Expand All @@ -62,63 +112,112 @@ IsUtilFunc(fun):-
LocalFlows(formalArg, storeIndex),
(SLOAD(_, storeIndex, _) ; SSTORE(_, storeIndex, _)).

InlineCandidate(fun):-
InlineCandidate(fun, "Storage2"):-
(SHA3(_, _, _, var) ; Variable_Value(var, _)),
LocalFlows(var, actualArg),
In_ActualArgs(caller, actualArg, pos),
In_CallGraphEdge(caller, fun),
In_FormalArgs(fun, formalArg, pos),
LocalFlows(formalArg, intermIndex),
(SHA3_2ARG(_, _, intermIndex, def); SHA3_1ARG(_, intermIndex, def)),
LocalFlows(def, storeIndex),
(SLOAD(_, storeIndex, _) ; SSTORE(_, storeIndex, _)).

InlineCandidate(fun, "Storage3"):-
// (SHA3_2ARG(_, _, _, def); SHA3_1ARG(_, _, def)),
// LocalFlows(def, intermIndex),
// using internal storage modeling predicate here
// to get higher coverage
Variable_StorageIndex(intermIndex, _),
In_FormalReturnArgs(fun, intermIndex, pos),
In_CallGraphEdge(caller, fun),
In_ActualReturnArgs(caller, intermIndexCaller, pos),
LocalFlows(intermIndexCaller, storeIndex),
(SLOAD(_, storeIndex, _) ; SSTORE(_, storeIndex, _)).

InlineCandidate(fun, "CallData1"):-
//In_Variable_Value(actualArg, _),
//In_ActualArgs(caller, actualArg, pos),
//In_CallGraphEdge(caller, fun),
In_FormalArgs(fun, formalArg, _),
LocalFlows(formalArg, calldataIndex),
LocalFlowsExclPHI(formalArg, calldataIndex),
CALLDATALOAD(_, calldataIndex, _).

InlineCandidate(fun):-
InlineCandidate(fun, "CallData2"):-
CALLDATALOAD(cdl, calldataIndex, _),
In_Variable_Value(calldataIndex, _),
In_Statement_Function(cdl, fun),
!IsPublicFunction(fun).

// Functions that only have a return jump and no actual returns
InlineCandidate(fun):-
In_CallGraphEdge(_, fun),
numOfStatements = count : In_Statement_Function(_, fun),
numOfStatements = 1,
!In_FormalReturnArgs(fun, _, 0).

// Less than 3 blocks
InlineCandidate(fun):-
InlineCandidate(fun, "Small"):-
In_CallGraphEdge(_, fun),
In_FormalArgs(fun, _, _),
numOfBlocks = count : In_InFunction(_, fun),
numOfBlocks <= 3.
numOfBlocks < 3.

InlineCandidate(fun):-
// a function is only called once by a caller that makes a single call
InlineCandidate(fun, "SingleCall"):-
In_IsFunction(fun),
1 = count : In_CallGraphEdge(_, fun),
In_CallGraphEdge(caller, fun),
In_InFunction(caller, callerFun),
1 = count: {In_InFunction(inCaller, callerFun), In_CallGraphEdge(inCaller, _)},
!IsPublicFunction(fun).

InlineCandidate(fun):-
InlineCandidate(fun, "ConsumeMemNoArgs"):-
StatementConsumesMemoryNoArgs(memConsStmt),
In_Statement_Function(memConsStmt, fun).
In_Statement_Function(memConsStmt, fun),
Function_HighLevelStmts(fun, numOfStmts),
numOfStmts < 20.

InlineCandidate(fun):-
InlineCandidate(fun, "Memory1"):-
In_FormalArgs(fun, formalArg, _),
LocalFlows(formalArg, loadAddr),
LocalFlowsExclPHI(formalArg, loadAddr),
MLOAD(_, loadAddr, loaded),
( LocalFlows(loaded, formalRet);
LocalFlows(formalArg, formalRet)),
( LocalFlowsExclPHI(loaded, formalRet);
LocalFlowsExclPHI(formalArg, formalRet)),
In_FormalReturnArgs(fun, formalRet, _),
!Struct_WordWidth(formalArg, _),
// !Struct_WordWidth(formalRet, _),
!VarIsArray(formalArg, _).

InlineCandidate(fun):-
// aimed to catch patterns that compute memory allocation bytes inside a function
InlineCandidate(fun, "Memory2"):-
In_FormalArgs(fun, formalArg, _),
In_FormalReturnArgs(fun, formalRet, index),
LocalFlowsExclPHI(formalArg, formalRet),
In_CallGraphEdge(caller, fun),
In_ActualReturnArgs(caller, actualRet, index),
LocalFlowsExclPHI(actualRet, bumpVar),
MSTORE(mstore, _, bumpVar),
MSTOREFreePtr(mstore).

InlineCandidate(fun, "Memory3"):-
MLOADFreePtr_To(_, loadedAddr),
In_FormalReturnArgs(fun, loadedAddr, _),
!Struct_WordWidth(loadedAddr, _),
!VarIsArray(loadedAddr, _).

InlineCandidate(fun):-
/**
If an arg passed to a function is always the free mem pointer and never a high-level
memory structure, we inline it as it's likely an allocation split in half.
*/
InlineCandidate(fun, "Memory4"):-
CallArgIsNonModeledMemObject(_, fun, argNum),
count: CallArgIsNonModeledMemObject(_, fun, argNum) = count: In_CallGraphEdge(_, fun).

.decl CallArgIsNonModeledMemObject(caller: Block, callee: Function, argNum: number)
CallArgIsNonModeledMemObject(caller, fun, argNum):-
MLOADFreePtr_To(_, loadedAddr),
In_ActualArgs(caller, loadedAddr, argNum),
In_CallGraphEdge(caller, fun),
!Struct_WordWidth(loadedAddr, _),
!VarIsArray(loadedAddr, _).

InlineCandidate(fun, "Memory5"):-
In_FormalArgs(fun, formalArg, _),
LocalFlows(formalArg, storeAddr),
LocalFlowsExclPHI(formalArg, storeAddr),
MSTORE(_, storeAddr, _),
!Struct_WordWidth(formalArg, _),
!VarIsArray(formalArg, _).
Expand All @@ -129,7 +228,7 @@ IsUtilFunc(fun):-
(In_Statement_Opcode(callStmt, "CALL"); In_Statement_Opcode(callStmt, "STATICCALL"); In_Statement_Opcode(callStmt, "DELEGATECALL")),
In_Statement_Function(callStmt, fun).

InlineCandidate(fun):-
InlineCandidate(fun, "ReturnDataNoCall"):-
(In_Statement_Opcode(returnDataStmt, "RETURNDATASIZE"); In_Statement_Opcode(returnDataStmt, "RETURNDATACOPY")),
In_Statement_Function(returnDataStmt, fun),
!FunctionContainsExternalCall(fun).
Expand All @@ -140,7 +239,7 @@ IsUtilFunc(fun):-
StatementUsesMemory(stmt,_),
In_Statement_Function(stmt, fun).

InlineCandidate(fun):-
InlineCandidate(fun, "MemoryLoop"):-
MemoryCopyLoop(memLoop, _, _),
In_InFunction(memLoop, fun),
!FunctionContainsMemConsStmt(fun).
Expand All @@ -152,7 +251,7 @@ IsUtilFunc(fun):-
.output NeedsMoreInlining

NeedsMoreInlining(fun):-
inliner.InlineCandidate(fun),
inliner.InlineCandidate(fun, _),
inliner.SafelyInlinableFunction(fun),
!inliner.FunctionToInline(fun),
!IsPublicFunction(fun).
Expand Down
25 changes: 25 additions & 0 deletions clientlib/memory_modeling/structs.dl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ StatementDominates(stmt1, stmt2):-
Statement_Block(stmt2, block2),
(Dominates(block1, block2) ; LocalStatementPathInBlock(stmt1, stmt2)).

.decl StatementDoesntDominate(stmt1: Statement, stmt2: Statement) inline
StatementDoesntDominate(stmt1, stmt2):-
Statement_Block(stmt1, block),
Statement_Block(stmt2, block),
LocalStatementPathInBlock(stmt2, stmt1).

StatementDoesntDominate(stmt1, stmt2):-
Statement_Block(stmt1, block1),
Statement_Block(stmt2, block2),
block1 != block2,
!Dominates(block1, block2).


/**
Base case for struct allocations
Expand Down Expand Up @@ -111,6 +123,14 @@ StructAllocationCheck(structBase):-
ControlsWith(jmpi, throwBlock, condVar),
ThrowBlock(throwBlock).

// Maybe not the best way to define it, may revisit
.decl StructStoresInDifferentPaths(structBase: Variable)
StructStoresInDifferentPaths(structBaseVar):-
StoreToPossibleStruct(mstore1, structBaseVar, _, _),
StoreToPossibleStruct(mstore2, structBaseVar, _, _),
StatementDoesntDominate(mstore1, mstore2),
StatementDoesntDominate(mstore2, mstore1).

// REVIEW: it requires at least as many InitialStoreToPossibleStruct as the struct's words
// it may possibly not be the case in optimized code
// REVIEW: only infers the structs in cases where it escapes the current method
Expand All @@ -129,6 +149,11 @@ StructAllocation(freePtrUpdStore, structBaseVar, wordWidth):-
// (ActualArgs(_, argOrRet, _) ; FormalReturnArgs(_, argOrRet, _)),
!ProbablyConstantArray(structBaseVar).

StructAllocation(freePtrUpdStore, structBaseVar, wordWidth):-
PossibleStructAllocation(_, freePtrUpdStore, structBaseVar, wordWidth),
StructStoresInDifferentPaths(structBaseVar),
!ProbablyConstantArray(structBaseVar).

/**
Find structs that are likely copies of structs from storage
*/
Expand Down
Loading

0 comments on commit 8bdfe26

Please sign in to comment.