Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/dialect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
rlcAddLibrary(dialect src/Dialect.cpp src/Types.cpp src/Operations.cpp src/Conversion.cpp src/EmitMain.cpp src/TypeCheck.cpp src/Interfaces.cpp src/SymbolTable.cpp src/ActionArgumentAnalysis.cpp src/LowerActionPass.cpp src/LowerArrayCalls.cpp src/LowerToCf.cpp src/ActionStatementsToCoro.cpp src/OverloadResolver.cpp src/LowerIsOperationsPass.cpp src/InstantiateTemplatesPass.cpp src/LowerAssignPass.cpp src/EmitImplicitAssignPass.cpp src/LowerConstructOpPass.cpp src/EmitImplicitInitPass.cpp src/EmitImplicitDestructorInvocationsPass.cpp src/LowerForFieldOpPass.cpp src/EmitEnumEntitiesPass.cpp src/SortTypeDeclarationsPass.cpp src/AddOutOfBoundsCheckPass.cpp src/PrintIRPass.cpp src/ExtractPreconditionPass.cpp src/LowerAssertsPass.cpp src/AddPreconditionsCheckPass.cpp src/ActionLiveness.cpp src/UncheckedAstToDot.cpp src/RewriteCallSignaturesPass.cpp src/RemoveUselessAllocaPass.cpp src/MembeFunctionsToRegularFunctionsPass.cpp src/LowerInitializerListsPass.cpp src/Enums.cpp src/EmitFuzzTargetPass.cpp)
rlcAddLibrary(dialect src/Dialect.cpp src/Types.cpp src/Operations.cpp src/Conversion.cpp src/EmitMain.cpp src/TypeCheck.cpp src/Interfaces.cpp src/SymbolTable.cpp src/ActionArgumentAnalysis.cpp src/LowerActionPass.cpp src/LowerArrayCalls.cpp src/LowerToCf.cpp src/ActionStatementsToCoro.cpp src/OverloadResolver.cpp src/LowerIsOperationsPass.cpp src/InstantiateTemplatesPass.cpp src/LowerAssignPass.cpp src/EmitImplicitAssignPass.cpp src/LowerConstructOpPass.cpp src/EmitImplicitInitPass.cpp src/EmitImplicitDestructorInvocationsPass.cpp src/LowerForFieldOpPass.cpp src/EmitEnumEntitiesPass.cpp src/SortTypeDeclarationsPass.cpp src/AddOutOfBoundsCheckPass.cpp src/PrintIRPass.cpp src/ExtractPreconditionPass.cpp src/LowerAssertsPass.cpp src/AddPreconditionsCheckPass.cpp src/ActionLiveness.cpp src/UncheckedAstToDot.cpp src/RewriteCallSignaturesPass.cpp src/RemoveUselessAllocaPass.cpp src/MembeFunctionsToRegularFunctionsPass.cpp src/LowerInitializerListsPass.cpp src/Enums.cpp src/EmitFuzzTargetPass.cpp src/DynamicArgumentAnalysisPass.cpp)
target_link_libraries(dialect PUBLIC rlc::utils MLIRSupport MLIRDialect MLIRLLVMDialect MLIRLLVMIRTransforms MLIRControlFlowDialect)

set(tblgen ${LLVM_BINARY_DIR}/bin/mlir-tblgen)
Expand Down
177 changes: 177 additions & 0 deletions lib/dialect/include/rlc/dialect/DynamicArgumentAnalysis.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include <cassert>
#include <cstdint>
#include <ranges>
#include <strings.h>
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/Region.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/ValueRange.h"
#include "mlir/Support/LLVM.h"
#include "rlc/dialect/Operations.hpp"


struct DeducedConstraints {
mlir::Value min;
mlir::Value max;
};

enum TermType {
DEPENDS_ON_UNBOUND,
DEPENDS_ON_OTHER_UNKNOWNS,
KNOWN_VALUE
};

/*
Given a function and a set of known (bound) arguments for the function, we try to find the
minimum and maximum values the unknown (unbound) arguments can take while satisfying the
function's precondition.

We first normalize the precondition to be a disjunction of conjunction of terms, where each
term is a value produced by something other than an AndOp or an OrOp. i.e. we express the
precondition in the form (t_1 AND t_2 AND ...) OR (t_3 AND t_4 AND ...).

Then, to find the minimum and the maximum for a given argument "arg", we emit code as follows:
We emit declarations for aggregate_min and aggregate_max, initialized to minus and plus infinity.

For each conjunction (t_1 AND t_2 AND ...) we classify the terms as
- conditions: terms depending only on known values
- constraints: terms depending on "arg" and no other unknown values
- others: terms depending on other unknown values.

We analyse the minimum and maximum values each constraint implies on "arg" in terms of known
dynamic values. Currently, this returns [-infinity, +infinity] for anything but <, >, >=, <=, ==
constraints where one of the sides is "arg", and calls to CanOp results.

Then, we emit an if statement in the form of
if(condition_1 & condition_2....) {
current_min = -inf
if(imposed_min(constraint_1) > current_min)
current_min = imposed_min(constraint_1)
if(imposed_min(constraint_2) > current_min)
current_min = imposed_min(constraint_2)
...
if ( uninitialized(aggregate_min) || current_min < aggregate_min)
aggregate_min = current_min
}

In other words, we compute
aggregate_min = max([
min([imposed_min(term) for term in conjunction.constraints])
for conjunction in constraint
where evaluate(conjunction.conditions) == true
])

and similarly for the maximum.
*/


/*
memberAddress is the "path" from the arg to the value we want to pick.
Example: arg = arg2, memberAddress = [2, 1] maps to MemberAccess(MemberAccess(arg,2), 1)
*/
struct UnboundValue {
mlir::BlockArgument argument;
llvm::SmallVector<uint64_t> memberAddress;

/*
Returns whether this unbound value corressponds to the expression.
*/
bool matches(mlir::Value expr) {
mlir::Value current = expr;
// walk the member address in reverse, test if it leads to the argument.
for(uint64_t & index : std::ranges::reverse_view(memberAddress)) {
auto definingOp = current.getDefiningOp();
if( not llvm::detail::isPresent(definingOp))
return false;
if(auto memberAccess = mlir::dyn_cast<mlir::rlc::MemberAccess>(definingOp)) {
if (memberAccess.getMemberIndex() != index) {
return false;
}
current = memberAccess.getValue();
} else {
return false;
}
}
return current == argument;
}

/*
Returns true if this unbound value is a recursive member of the given expression.
*/
bool isMemberOf(mlir::Value expr) {
mlir::Value current = expr;
llvm::SmallVector<uint64_t> indices;

while(true) {
if(current == argument) {
for(size_t i = 0; i < indices.size(); i++) {
if(indices[i] != memberAddress[i])
return false;
}
return true;
}

auto definingOp = current.getDefiningOp();
if( not llvm::detail::isPresent(definingOp))
return false;
if(auto memberAccess = mlir::dyn_cast<mlir::rlc::MemberAccess>(definingOp)) {
indices.insert(indices.begin(), memberAccess.getMemberIndex());
current = memberAccess.getValue();
} else return false;
}
}

mlir::Type getType() {
auto type = argument.getType();
for (auto index : memberAddress) {
type = type.cast<mlir::rlc::EntityType>().getBody()[index];
}
return type;
}
};

class DynamicArgumentAnalysis
{
public:
explicit DynamicArgumentAnalysis(mlir::rlc::FunctionOp op, mlir::ValueRange boundArgs, mlir::Value argPicker, bool analysePreconditions, mlir::OpBuilder builder, mlir::Location loc);
mlir::Value pickArg(int argIndex);

private:
DeducedConstraints deduceIntegerUnboundValueConstraints(UnboundValue unbound);
llvm::SmallVector<llvm::SmallVector<mlir::Value>> expandToDNF(mlir::Value constraint);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that DNF will be something that will come up a lot, you can wrap the vectors into a class like and put this in a header of their own

class DisjunctiveNormalFormExpression {
public:
<getters>

private:
llvm::SmallVector<llvm::SmallVector<mlir::Value>> Content;
};

to be honest i would not be even opposed to have a operation called DNFExpression and have this analysis as a pass that mutates the ir and turns every precondition into a DNF

TermType decideTermType(mlir::Value term, UnboundValue unbound);
mlir::Value compute(mlir::Value expression);
DeducedConstraints findImposedConstraints(mlir::Value constraint, UnboundValue unbound);
DeducedConstraints findImposedConstraints(mlir::Operation *binaryOperation, UnboundValue unbound);
DeducedConstraints findImposedConstraints(mlir::rlc::CallOp call, UnboundValue unbound);

mlir::Value pickIntegerUnboundValue(UnboundValue unbound);
mlir::Value pickUnboundValue(UnboundValue unbound);

mlir::rlc::FunctionOp function;
mlir::Region& precondition;

llvm::SmallVector<std::pair<UnboundValue, mlir::Value>> bindings;
/*
If the passed value matches an UnboundValue that has a binding,
return the value bound to it.
Otherwise returns nullptr.
*/
mlir::Value getBoundValue(mlir::Value expr) {
for(auto binding : bindings) {
if(binding.first.matches(expr))
return binding.second;
}
return nullptr;
}

bool analysePreconditions;
mlir::Value argPicker;
mlir::OpBuilder builder;
mlir::Location loc;
llvm::SmallVector<llvm::SmallVector<mlir::Value>> conjunctions;
};
Loading