diff --git a/lib/dialect/CMakeLists.txt b/lib/dialect/CMakeLists.txt index b769902a..dc7274e6 100644 --- a/lib/dialect/CMakeLists.txt +++ b/lib/dialect/CMakeLists.txt @@ -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) diff --git a/lib/dialect/include/rlc/dialect/DynamicArgumentAnalysis.hpp b/lib/dialect/include/rlc/dialect/DynamicArgumentAnalysis.hpp new file mode 100644 index 00000000..55821d9c --- /dev/null +++ b/lib/dialect/include/rlc/dialect/DynamicArgumentAnalysis.hpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#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 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(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 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(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().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> expandToDNF(mlir::Value constraint); + 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> 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> conjunctions; +}; diff --git a/lib/dialect/src/DynamicArgumentAnalysisPass.cpp b/lib/dialect/src/DynamicArgumentAnalysisPass.cpp new file mode 100644 index 00000000..d6ab5dce --- /dev/null +++ b/lib/dialect/src/DynamicArgumentAnalysisPass.cpp @@ -0,0 +1,577 @@ +#include +#include +#include +#include +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.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/Pass/Pass.h" +#include "mlir/Support/LLVM.h" +#include "rlc/dialect/Dialect.h" +#include "rlc/dialect/Operations.hpp" +#include "rlc/dialect/DynamicArgumentAnalysis.hpp" +#include "rlc/dialect/Types.hpp" + +DynamicArgumentAnalysis::DynamicArgumentAnalysis(mlir::rlc::FunctionOp op, mlir::ValueRange boundArgs, mlir::Value argPicker, bool analysePreconditions, mlir::OpBuilder builder, mlir::Location loc): + function(op), + precondition(op.getPrecondition()), + analysePreconditions(analysePreconditions), + argPicker(argPicker), + builder(builder), + loc(loc) { + for (auto boundArg : llvm::enumerate(boundArgs)) { + UnboundValue arg {op.getPrecondition().getArgument(boundArg.index()), {}}; + bindings.emplace_back(std::pair(arg, boundArg.value())); + } + + auto yield = mlir::dyn_cast(precondition.getBlocks().front().back()); + assert(yield->getNumOperands() == 1); + conjunctions = expandToDNF(yield->getOperand(0)); +} + + +void helper( + llvm::SmallVector> &conjunctions, + llvm::SmallVector currentConjunction, + mlir::Region &precondition + ) { + for (auto expr : llvm::enumerate(currentConjunction)) { + auto definingOp = expr.value().getDefiningOp(); + + // the expr is defined outside of the precondition, it doesn't need to be expanded. + if(definingOp->getParentRegion() != precondition) { + continue; + } + + // TODO add other stopping conditions? + + if(auto andOp = llvm::dyn_cast(definingOp)) { + currentConjunction.erase(currentConjunction.begin() + expr.index()); + currentConjunction.append({andOp.getLhs(), andOp.getRhs()}); + helper(conjunctions, currentConjunction, precondition); + return; + } + + if(auto orOp = llvm::dyn_cast(definingOp)) { + currentConjunction.erase(currentConjunction.begin() + expr.index()); + auto rhs = orOp.getRhs(); + auto lhs = orOp.getLhs(); + + currentConjunction.emplace_back(rhs); + helper(conjunctions, currentConjunction, precondition); + + currentConjunction.pop_back(); + currentConjunction.emplace_back(lhs); + helper(conjunctions, currentConjunction, precondition); + return; + } + } + conjunctions.emplace_back(currentConjunction); +} + +/* + Expands the given value into a disjunction of conjunctions of terms. + A term is a value that's not the result of on AndOp or an OrOp. +*/ +llvm::SmallVector> DynamicArgumentAnalysis::expandToDNF(mlir::Value constraint) { + llvm::SmallVector> conjunctions; + helper(conjunctions, {constraint}, precondition); + return conjunctions; +} + +TermType DynamicArgumentAnalysis::decideTermType(mlir::Value term, UnboundValue unbound) { + if(unbound.isMemberOf(term)) + return DEPENDS_ON_UNBOUND; + + auto boundValue = getBoundValue(term); + if(boundValue != nullptr) + return KNOWN_VALUE; + + if(term.isa()) + return DEPENDS_ON_OTHER_UNKNOWNS; + + + auto definingOp = term.getDefiningOp(); + + if(auto casted = llvm::dyn_cast(definingOp)) + return KNOWN_VALUE; + + if(definingOp->getParentRegion() != precondition) + return KNOWN_VALUE; + + bool dependsOnArg = false; + bool dependsOnOtherUnkowns = false; + + for(auto operand : definingOp->getOperands()) { + auto operandType = decideTermType(operand, unbound); + if(operandType == DEPENDS_ON_UNBOUND) + dependsOnArg = true; + else if (operandType == DEPENDS_ON_OTHER_UNKNOWNS) + dependsOnOtherUnkowns = true; + } + + if (dependsOnArg) { return DEPENDS_ON_UNBOUND; } + if (dependsOnOtherUnkowns) { return DEPENDS_ON_OTHER_UNKNOWNS; }; + return KNOWN_VALUE; +} + +mlir::Value DynamicArgumentAnalysis::compute(mlir::Value expression) { + auto boundValue = getBoundValue(expression); + if(boundValue != nullptr) + return boundValue; + + assert(not expression.isa() && "The expression to be computed depends on an unbound argument."); + + if (expression.getDefiningOp()->getParentRegion() != precondition) + return expression; + + int resultIndex = -1; + for(auto result : llvm::enumerate(expression.getDefiningOp()->getResults())) { + if(result.value() == expression) + resultIndex = result.index(); + } + assert(resultIndex != -1); + + auto newOp = builder.clone(*expression.getDefiningOp()); + builder.setInsertionPoint(newOp); + for(auto operand : llvm::enumerate(newOp->getOperands())) { + auto newOperand = compute(operand.value()); + newOp->setOperand(operand.index(), newOperand); + } + builder.setInsertionPointAfter(newOp); + return newOp->getResult(resultIndex); +} + +//TODO +const int64_t min_int = -800; +const int64_t max_int = 800; + +DeducedConstraints DynamicArgumentAnalysis::findImposedConstraints(mlir::Operation *binaryOperation, UnboundValue unbound) { + if (unbound.matches(binaryOperation->getOperand(0))) { + auto rhs = compute(binaryOperation->getOperand(1)); + + if (mlir::isa(binaryOperation)) + { + auto one = builder.create(loc, (int64_t)1); + return { + builder.create(loc, min_int), + builder.create(loc, rhs, one) + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + builder.create(loc, min_int), + rhs + }; + } + + if (mlir::isa(binaryOperation)) + { + auto one = builder.create(loc, (int64_t)1); + return { + builder.create(loc, rhs, one), + builder.create(loc, max_int) + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + rhs, + builder.create(loc, max_int) + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + rhs, + rhs + }; + } + } + + if(unbound.matches(binaryOperation->getOperand(1))) { + auto lhs = compute(binaryOperation->getOperand(0)); + + if (mlir::isa(binaryOperation)) + { + auto one = builder.create(loc, (int64_t)1); + return { + builder.create(loc, lhs, one), + builder.create(loc, max_int), + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + lhs, + builder.create(loc, max_int), + }; + } + + if (mlir::isa(binaryOperation)) + { + auto one = builder.create(loc, (int64_t)1); + return { + builder.create(loc, min_int), + builder.create(loc, lhs, one) + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + builder.create(loc, min_int), + lhs + }; + } + + if (mlir::isa(binaryOperation)) + { + return { + lhs, + lhs + }; + } + } + return { + builder.create(loc, min_int), + builder.create(loc, max_int) + }; +} + +DeducedConstraints DynamicArgumentAnalysis::findImposedConstraints(mlir::rlc::CallOp call, UnboundValue unbound) { + // LARGE TODO think about this part. + if( mlir::rlc::CanOp can = llvm::dyn_cast(call.getCallee().getDefiningOp())) { + auto underlyingFunction = llvm::dyn_cast(*can.getCallee().getDefiningOp()); + llvm::SmallVector knownArgsOfUnderlyingFunction; + + int argIndex = -1; + for(auto current : llvm::enumerate(call.getArgs())) { + // decide which arguments are known (assuming (in general) known arguments are before unknown arguments.) + if( not current.value().isa()) { + auto newExpr = compute(current.value()); + knownArgsOfUnderlyingFunction.emplace_back(newExpr); + continue; + } + // and find the index of the argument we're interested in. + if(unbound.argument == current.value()) { + argIndex = current.index(); + break; + } + } + assert(argIndex != -1 && "Expected to find the argument."); + DynamicArgumentAnalysis analysis(underlyingFunction, knownArgsOfUnderlyingFunction, argPicker, analysePreconditions, builder, loc); + for(auto binding: bindings) { + if(binding.first.memberAddress.size() != 0) { + // the binding does not just correspond to an argument. + auto indexOfArg = std::distance( + call.getArgs().begin(), + llvm::find(call.getArgs(), unbound.argument)); + // add an equivalent binding using the callee's arg. + UnboundValue newUnboundValue {underlyingFunction.getPrecondition().getArgument(indexOfArg), binding.first.memberAddress}; + analysis.bindings.emplace_back(std::pair(newUnboundValue, binding.second)); + } + } + UnboundValue correspongindUnboundValue {underlyingFunction.getPrecondition().getArgument(argIndex), unbound.memberAddress}; + return analysis.deduceIntegerUnboundValueConstraints(correspongindUnboundValue); + } + return { + builder.create(loc, min_int), + builder.create(loc, max_int) + }; +} + + +DeducedConstraints DynamicArgumentAnalysis::findImposedConstraints(mlir::Value constraint, UnboundValue unbound) { + auto *definingOp = constraint.getDefiningOp(); + if (definingOp->getOperands().size() == 2) { + return findImposedConstraints(definingOp, unbound); + } + + if( mlir::rlc::CallOp call = llvm::dyn_cast(definingOp)) { + return findImposedConstraints(call, unbound); + } + + return { + builder.create(loc, min_int), + builder.create(loc, max_int) + }; +} + +/* + Emits `if (value > target) target = value` +*/ +void assignIfGreaterthan(mlir::Value value, mlir::Value target, mlir::OpBuilder builder, mlir::Location loc) { + auto ifStatement = builder.create(loc); + builder.createBlock(&ifStatement.getElseBranch()); + builder.create(loc); + + builder.createBlock(&ifStatement.getCondition()); + auto g = builder.create(loc, value, target); + builder.create(loc, g.getResult()); + + builder.createBlock(&ifStatement.getTrueBranch()); + builder.create(loc, target, value); + builder.create(loc); + builder.setInsertionPointAfter(ifStatement); +} + +/* + Emits `if (value < target) target = value` +*/ +void assignIfLessThan(mlir::Value value, mlir::Value target, mlir::OpBuilder builder, mlir::Location loc) { + auto ifStatement = builder.create(loc); + builder.createBlock(&ifStatement.getElseBranch()); + builder.create(loc); + + builder.createBlock(&ifStatement.getCondition()); + auto g = builder.create(loc, value, target); + builder.create(loc, g.getResult()); + + builder.createBlock(&ifStatement.getTrueBranch()); + builder.create(loc, target, value); + builder.create(loc); + builder.setInsertionPointAfter(ifStatement); +} + +void maybeAssignMin(mlir::Value currentMin, mlir::Value aggregateMin, mlir::Value minInt, mlir::OpBuilder builder, mlir::Location loc) { + auto maybeAssignMin = builder.create(loc); + builder.createBlock(&maybeAssignMin.getElseBranch()); + builder.create(loc); + + builder.createBlock(&maybeAssignMin.getCondition()); + auto minUninitialized = builder.create(loc, aggregateMin, minInt); + auto currentMinIsUninitialized = builder.create(loc, currentMin, minInt); + auto currentMinIsInitialized = builder.create(loc, currentMinIsUninitialized.getResult()); + auto currentMinIsSmaller = builder.create(loc, currentMin, aggregateMin); + auto cond = builder.create(loc, minUninitialized, currentMinIsSmaller); + auto cond2 = builder.create(loc, currentMinIsInitialized, cond); + builder.create(loc, cond2.getResult()); + + builder.createBlock(&maybeAssignMin.getTrueBranch()); + builder.create(loc, aggregateMin, currentMin); + builder.create(loc); + builder.setInsertionPointAfter(maybeAssignMin); +} + +void maybeAssignMax(mlir::Value currentMax, mlir::Value aggregateMax, mlir::Value maxInt, mlir::OpBuilder builder, mlir::Location loc) { + auto maybeAssignMax= builder.create(loc); + builder.createBlock(&maybeAssignMax.getElseBranch()); + builder.create(loc); + + builder.createBlock(&maybeAssignMax.getCondition()); + auto maxUninitialized = builder.create(loc, aggregateMax, maxInt); + auto currentMaxIsUninitialized = builder.create(loc, currentMax, maxInt); + auto currentMaxIsInitialized = builder.create(loc, currentMaxIsUninitialized.getResult()); + auto currentMaxIsGreater = builder.create(loc, currentMax, aggregateMax); + auto cond = builder.create(loc, maxUninitialized, currentMaxIsGreater); + auto cond2 = builder.create(loc, currentMaxIsInitialized, cond); + builder.create(loc, cond2.getResult()); + + builder.createBlock(&maybeAssignMax.getTrueBranch()); + builder.create(loc, aggregateMax, currentMax); + builder.create(loc); + builder.setInsertionPointAfter(maybeAssignMax); +} + +DeducedConstraints DynamicArgumentAnalysis::deduceIntegerUnboundValueConstraints(UnboundValue unbound) { + auto type = unbound.getType(); + assert(type.isa() && "Expected an integer."); + + auto minVal = builder.create(loc, type); + auto minInt = builder.create(loc, min_int); + builder.create(loc, minVal, minInt); + + auto maxVal = builder.create(loc, type); + auto maxInt = builder.create(loc, max_int); + builder.create(loc, maxVal, maxInt); + + for (auto conjunction : conjunctions) { + llvm::SmallVector constraints; + llvm::SmallVector conditions; + // categorize the terms in the conjunction + for(auto term : conjunction) { + TermType type = decideTermType(term, unbound); + if(type == DEPENDS_ON_UNBOUND) { + constraints.emplace_back(term); + } else if (type == KNOWN_VALUE){ + conditions.emplace_back(term); + } + } + + + // if there are any constraints on unknown args, emit an if statement for this conjunction. + if(constraints.size() > 0) { + auto minForThisConjunction = builder.create(loc, type); + builder.create(loc, minForThisConjunction, minInt); + + auto maxForThisConjunction = builder.create(loc, type); + builder.create(loc, maxForThisConjunction, maxInt); + + // if the conditions are met, the constraints are active. + auto ifStatement = builder.create(loc); + builder.createBlock(&ifStatement.getElseBranch()); + builder.create(loc); + + builder.createBlock(&ifStatement.getCondition()); + mlir::Value condition = builder.create(loc, true); + for( auto c : conditions) { + auto computed = compute(c); + condition = builder.create(loc, condition, computed); + } + + builder.create(loc, condition); + + builder.createBlock(&ifStatement.getTrueBranch()); + for(auto constraint : constraints) { + auto imposedConstraints = findImposedConstraints(constraint, unbound); + + // if the minimum imposed by this constraint is greater than the current minimum, set the current minimum. + assignIfGreaterthan(imposedConstraints.min, minForThisConjunction, builder, loc); + + // if the maximum imposed by this constraint is less than the current maximum, set the current maximum. + assignIfLessThan(imposedConstraints.max, maxForThisConjunction, builder, loc); + } + + // if minVal is not initialized, or if the min imposed by this conjunction is smaller than it, set minVal + // to the min imposed by this conjunction. + maybeAssignMin(minForThisConjunction, minVal, minInt, builder, loc); + + // similarly for maxVal. + maybeAssignMax(maxForThisConjunction, maxVal, maxInt, builder, loc); + + builder.create(loc); + builder.setInsertionPointAfter(ifStatement); + } + } + return {minVal, maxVal}; +} + +mlir::Value DynamicArgumentAnalysis::pickIntegerUnboundValue(UnboundValue unbound) { + if(analysePreconditions) { + auto deduced = deduceIntegerUnboundValueConstraints(unbound); + auto call = builder.create( + loc, + argPicker, + false, + mlir::ValueRange({deduced.min, deduced.max}) + ); + return call.getResult(0); + } + auto min = builder.create(loc, min_int); + auto max = builder.create(loc, max_int); + auto call = builder.create( + loc, + argPicker, + false, + mlir::ValueRange({min, max}) + ); + return call->getResult(0); +} + +mlir::Value DynamicArgumentAnalysis::pickUnboundValue(UnboundValue unbound) { + auto type = unbound.getType(); + if(type.isa()) { + return pickIntegerUnboundValue(unbound); + } + + if(auto entityType = mlir::dyn_cast(type)) { + auto entity = builder.create(loc, entityType); + for( auto memberType : llvm::enumerate(entityType.getBody())) { + llvm::SmallVector newMemberAddress(unbound.memberAddress); + newMemberAddress.emplace_back(memberType.index()); + UnboundValue newUnboundValue {unbound.argument, newMemberAddress}; + auto value = pickUnboundValue(newUnboundValue); + auto access = builder.create(loc, entity, memberType.index()); + if(value.getType().isa()) { + builder.create(loc, access, value); + } else { + // find and use the assign operator for this data type. + auto module = access->getParentOfType(); + mlir::Value assign = nullptr; + for (auto op : module.getOps()) { + if(op.getUnmangledName().equals(mlir::rlc::builtinOperatorName()) + && op.getArgumentTypes().size() > 0 + && op.getArgumentTypes()[0] == memberType.value()) { + assign = op.getResult(); + break; + } + } + assert(assign != nullptr && "Can't find the assignment operator for the data type."); + builder.create(loc, assign, false, mlir::ValueRange({access, value})); + } + + // bind the value of this struct field while we pick the next field. + bindings.emplace_back(std::pair(newUnboundValue, value)); + } + return entity; + } + + assert(false && "Can only handle unbound values of type int and struct."); + return nullptr; +} + +mlir::Value DynamicArgumentAnalysis::pickArg(int argIndex) { + auto arg = function.getPrecondition().getArgument(argIndex); + return pickUnboundValue({arg, {}}); +} + +namespace mlir::rlc +{ +#define GEN_PASS_DECL_DYNAMICARGUMENTANALYSISPASS +#define GEN_PASS_DEF_DYNAMICARGUMENTANALYSISPASS +#include "rlc/dialect/Passes.inc" + struct DynamicArgumentAnalysisPass + : public impl::DynamicArgumentAnalysisPassBase + { + using impl::DynamicArgumentAnalysisPassBase::DynamicArgumentAnalysisPassBase; + void getDependentDialects(mlir::DialectRegistry& registry) const override + { + registry.insert(); + } + + void runOnOperation() override + { + ModuleOp module = getOperation(); + + mlir::Value argPicker; + for (auto op : module.getOps()) { + if(op.getUnmangledName().equals("fuzzer_pick_argument")) { + argPicker = op.getResult(); + break; + } + } + + llvm::SmallVector pickedArgOps; + module.walk([&](mlir::rlc::PickedArgOp op) { + pickedArgOps.emplace_back(op); + }); + + for (auto pickedArgOp : pickedArgOps) + { + mlir::OpBuilder builder(pickedArgOp); + mlir::Location loc = pickedArgOp->getLoc(); + + auto function = llvm::dyn_cast(pickedArgOp.getFunction().getDefiningOp()); + assert( function && "Expected a FunctionOp"); + DynamicArgumentAnalysis analysis(function, pickedArgOp.getKnownArgs(), argPicker, analysePreconditions, builder, loc); + auto pickedArg = analysis.pickArg(pickedArgOp.getArgumentIndex()); + + pickedArgOp.getResult().replaceAllUsesWith(pickedArg); + pickedArgOp->erase(); + } + } + }; +} // namespace mlir::rlc \ No newline at end of file diff --git a/lib/dialect/src/EmitFuzzTargetPass.cpp b/lib/dialect/src/EmitFuzzTargetPass.cpp index c5d1d8a1..e7930900 100644 --- a/lib/dialect/src/EmitFuzzTargetPass.cpp +++ b/lib/dialect/src/EmitFuzzTargetPass.cpp @@ -31,6 +31,7 @@ limitations under the License. #include "mlir/IR/OperationSupport.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/Region.h" +#include "mlir/IR/TypeRange.h" #include "mlir/IR/Value.h" #include "mlir/IR/ValueRange.h" #include "mlir/Pass/Pass.h" @@ -126,75 +127,86 @@ static mlir::Value emitChosenActionDeclaration( mlir::rlc::ActionFunction action, mlir::Value actionEntity, mlir::Value availableSubactionsVector, + bool avoidUnavailableSubactions, mlir::rlc::ModuleBuilder &moduleBuilder, mlir::OpBuilder builder ) { auto ip = builder.saveInsertionPoint(); + mlir::Value chosenAction; + if(avoidUnavailableSubactions) { + auto clearAvailableSubactions = findFunction(action->getParentOfType(), "fuzzer_clear_available_subactions"); + auto addAvailableSubaction = findFunction(action->getParentOfType(), "fuzzer_add_available_subaction"); + auto pickSubaction = findFunction(action->getParentOfType(), "fuzzer_pick_subaction"); - auto clearAvailableSubactions = findFunction(action->getParentOfType(), "fuzzer_clear_available_subactions"); - auto addAvailableSubaction = findFunction(action->getParentOfType(), "fuzzer_add_available_subaction"); - auto pickSubaction = findFunction(action->getParentOfType(), "fuzzer_pick_subaction"); - - // availableSubactions.clear() - builder.create( - action->getLoc(), - clearAvailableSubactions, - false, - mlir::ValueRange({availableSubactionsVector}) - ); - - // for each subaction, - // if(subAction.resumptionIndex == actionEntity.resumptionIndex) - // availableSubactions.push(subactionID) - int64_t index = 0; - for(auto subactionFunction : action.getActions()) { - auto ifStatement = builder.create(action->getLoc()); - builder.createBlock(&ifStatement.getCondition()); - auto storedResumptionPoint = builder.create( + // availableSubactions.clear() + builder.create( action->getLoc(), - actionEntity, - 0 + clearAvailableSubactions, + false, + mlir::ValueRange({availableSubactionsVector}) ); - // the subactionFunction is available if the stored resumptionIndex matches any of its acitonStatements' resumptionIndex. - auto actionStatements = moduleBuilder.actionFunctionValueToActionStatement(subactionFunction); - mlir::Value lastOperand = - builder.create(action.getLoc(), false); - for(auto *actionStatement : actionStatements) { - auto cast = mlir::dyn_cast(actionStatement); - auto subactionResumptionPoint = builder.create(action.getLoc(), (int64_t) cast.getResumptionPoint()); - auto eq = builder.create(action->getLoc(), storedResumptionPoint, subactionResumptionPoint); - lastOperand = builder.create(action.getLoc(), lastOperand, eq.getResult()); + // for each subaction, + // if(subAction.resumptionIndex == actionEntity.resumptionIndex) + // availableSubactions.push(subactionID) + int64_t index = 0; + for(auto subactionFunction : action.getActions()) { + auto ifStatement = builder.create(action->getLoc()); + builder.createBlock(&ifStatement.getCondition()); + auto storedResumptionPoint = builder.create( + action->getLoc(), + actionEntity, + 0 + ); + + // the subactionFunction is available if the stored resumptionIndex matches any of its acitonStatements' resumptionIndex. + auto actionStatements = moduleBuilder.actionFunctionValueToActionStatement(subactionFunction); + mlir::Value lastOperand = + builder.create(action.getLoc(), false); + for(auto *actionStatement : actionStatements) { + auto cast = mlir::dyn_cast(actionStatement); + auto subactionResumptionPoint = builder.create(action.getLoc(), (int64_t) cast.getResumptionPoint()); + auto eq = builder.create(action->getLoc(), storedResumptionPoint, subactionResumptionPoint); + lastOperand = builder.create(action.getLoc(), lastOperand, eq.getResult()); + } + + builder.create(ifStatement.getLoc(), lastOperand); + + builder.createBlock(&ifStatement.getTrueBranch()); + auto subactionIndex = builder.create(action->getLoc(), index); + builder.create( + action->getLoc(), + addAvailableSubaction, + false, + mlir::ValueRange{availableSubactionsVector, subactionIndex.getResult()} + ); + builder.create(action->getLoc()); + + // construct the false branch that does nothing + auto *falseBranch = builder.createBlock(&ifStatement.getElseBranch()); + builder.create(ifStatement.getLoc()); + + builder.setInsertionPointAfter(ifStatement); + index++; } - - builder.create(ifStatement.getLoc(), lastOperand); - builder.createBlock(&ifStatement.getTrueBranch()); - auto subactionIndex = builder.create(action->getLoc(), index); - builder.create( + // let chosenAction = pick_subaction(availableSubactions) + chosenAction = builder.create( action->getLoc(), - addAvailableSubaction, + pickSubaction, false, - mlir::ValueRange{availableSubactionsVector, subactionIndex.getResult()} - ); - builder.create(action->getLoc()); - - // construct the false branch that does nothing - auto *falseBranch = builder.createBlock(&ifStatement.getElseBranch()); - builder.create(ifStatement.getLoc()); - - builder.setInsertionPointAfter(ifStatement); - index++; + mlir::ValueRange{availableSubactionsVector} + )->getResult(0); + } else { + auto getInput = findFunction(action->getParentOfType(), "fuzzer_get_input"); + auto numSubactions = builder.create(action->getLoc(), (int64_t)action.getActions().size()); + chosenAction = builder.create( + action.getLoc(), + getInput, + false, + mlir::ValueRange{numSubactions} + )->getResult(0); } - - // let chosenAction = pick_subaction(availableSubactions) - auto chosenAction = builder.create( - action->getLoc(), - pickSubaction, - false, - mlir::ValueRange{availableSubactionsVector} - )->getResult(0); - builder.restoreInsertionPoint(ip); return chosenAction; } @@ -207,76 +219,39 @@ static mlir::Value emitChosenActionDeclaration( where arg1,arg2,... are the arguments of the subaction. */ static llvm::SmallVector emitSubactionArgumentDeclarations( - mlir::Value subaction, + mlir::Value subactionFunction, mlir::Value actionEntity, - mlir::Value pickArgument, mlir::Value print, mlir::Location loc, mlir::OpBuilder builder, mlir::rlc::ModuleBuilder &moduleBuilder ) { auto ip = builder.saveInsertionPoint(); - auto actionStatements = moduleBuilder.actionFunctionValueToActionStatement(subaction); - llvm::SmallVector arguments; // declare the arguments - auto inputs = mlir::dyn_cast(subaction.getType()).getInputs(); + auto inputs = mlir::dyn_cast(subactionFunction.getType()).getInputs(); // The first input is the actionEntity, which does not need to be declared here. - auto inputsExcludingActionEntity = llvm::drop_begin(inputs); + auto inputsExcludingActionEntity = llvm::drop_begin(llvm::enumerate(inputs)); + // keeps track of arguments that already have been bound. + // the action entity is bound before picking the first input, every input becomes bound when it's picked. + llvm::SmallVector boundArguments {actionEntity}; for(auto inputType : inputsExcludingActionEntity) { - - assert(inputType.isa() && "Fuzzing can only handle integer arguments for now."); - - auto argDecl = builder.create( + auto pickedArg = builder.create( loc, - inputType + mlir::TypeRange({ + inputType.value() + }), + subactionFunction, + (uint8_t)inputType.index(), + boundArguments ); - arguments.emplace_back(argDecl.getResult()); + // print the value picked for the argument for debugging purposes. + builder.create(loc, print, false, pickedArg.getResult()); + arguments.emplace_back(pickedArg.getResult()); + boundArguments.emplace_back(pickedArg.getResult()); } - // for each action statement, if the resumeIndex matches that of the action statement, assign arguments respecting the action statement's constraints. - auto storedResumptionPoint = builder.create( - loc, - actionEntity, - 0 - ); - for(auto *actionStatement : actionStatements) { - auto cast = mlir::dyn_cast(*actionStatement); - auto ifStatement = builder.create(loc); - builder.createBlock(&ifStatement.getCondition()); - auto subactionResumptionPoint = builder.create(loc, (int64_t) cast.getResumptionPoint()); - auto eq = builder.create(loc, storedResumptionPoint, subactionResumptionPoint); - builder.create(loc, eq.getResult()); - - builder.createBlock(&ifStatement.getTrueBranch()); - mlir::rlc::ActionArgumentAnalysis analysis(cast); - - for(auto input : llvm::enumerate(cast.getPrecondition().getArguments())) { - auto input_min = builder.create( - loc, - analysis.getBoundsOf(input.value()).getMin() - ); - auto input_max = builder.create( - loc, - analysis.getBoundsOf(input.value()).getMax() - ); - auto call = builder.create( - loc, - pickArgument, - false, - mlir::ValueRange({input_min.getResult(), input_max.getResult()}) - ); - // print the value picked for the argument for debugging purposes. - builder.create(loc, print, false, call.getResult(0)); - builder.create(loc, arguments[input.index()], call.getResult(0)); - } - builder.create(loc); - - builder.createBlock(&ifStatement.getElseBranch()); - builder.create(loc); - builder.setInsertionPointAfter(ifStatement); - } builder.restoreInsertionPoint(ip); return arguments; } @@ -304,7 +279,6 @@ static void emitSubactionCases( mlir::OpBuilder builder ) { auto ip = builder.saveInsertionPoint(); - auto pickArgument = findFunction(action->getParentOfType(), "fuzzer_pick_argument"); auto print = findFunction(action->getParentOfType(),"fuzzer_print"); auto skipFuzzInput = findFunction(action->getParentOfType(),"fuzzer_skip_input"); @@ -320,7 +294,7 @@ static void emitSubactionCases( // let arg1 = pickArgument(arg_1_size) // ... builder.createBlock(&ifActionIsChosen.getTrueBranch()); - auto args = emitSubactionArgumentDeclarations(subaction.value(), actionEntity, pickArgument, print, action->getLoc(), builder, moduleBuilder); + auto args = emitSubactionArgumentDeclarations(subaction.value(), actionEntity, print, action->getLoc(), builder, moduleBuilder); args.insert(args.begin(), actionEntity); // the first argument should be the entity itself. // if( !can_subaction_function(arg1, arg2, ...)) @@ -383,7 +357,7 @@ static void emitSubactionCases( ... ... */ -static void emitFuzzActionFunction(mlir::rlc::ActionFunction action) { +static void emitFuzzActionFunction(mlir::rlc::ActionFunction action, bool avoidUnavailableSubactions) { auto loc = action.getLoc(); mlir::OpBuilder builder(action); mlir::rlc::ModuleBuilder moduleBuilder(action->getParentOfType()); @@ -413,7 +387,7 @@ static void emitFuzzActionFunction(mlir::rlc::ActionFunction action) { {mlir::rlc::IntegerType::getInt64(builder.getContext())} ); auto initAvailableSubactions = findFunction(action->getParentOfType(), "fuzzer_init_available_subactions"); - auto availableSubactions = builder.create( + auto availableSubactions = builder.create( // TODO this might end up being unused, shouldn't be emitted in that case. action->getLoc(), initAvailableSubactions, false, @@ -423,7 +397,7 @@ static void emitFuzzActionFunction(mlir::rlc::ActionFunction action) { auto whileStmt = builder.create(loc); emitLoopCondition(action, &whileStmt.getCondition(), entityDeclaration, stopFlag.getResult(), builder); builder.createBlock(&whileStmt.getBody()); - auto chosenAction = emitChosenActionDeclaration(action, entityDeclaration, availableSubactions, moduleBuilder, builder); + auto chosenAction = emitChosenActionDeclaration(action, entityDeclaration, availableSubactions, avoidUnavailableSubactions, moduleBuilder, builder); emitSubactionCases(action, &whileStmt.getBody(), entityDeclaration, chosenAction, stopFlag.getResult(), moduleBuilder, builder); builder.create(loc); @@ -452,7 +426,7 @@ namespace mlir::rlc // invoke emitFuzzActionFunction on the ActionFunction with the correct unmangledName for(auto op :module.getOps()) { if(op.getUnmangledName().str() == actionToFuzz) - emitFuzzActionFunction(op); + emitFuzzActionFunction(op, avoidUnavailableSubactions); } } }; diff --git a/lib/dialect/src/Operations.cpp b/lib/dialect/src/Operations.cpp index a22d9309..b4a7b734 100644 --- a/lib/dialect/src/Operations.cpp +++ b/lib/dialect/src/Operations.cpp @@ -20,8 +20,10 @@ limitations under the License. #include "llvm/ADT/StringExtras.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/OpDefinition.h" +#include "mlir/Support/LogicalResult.h" #include "rlc/dialect/ActionLiveness.hpp" #include "rlc/dialect/Dialect.h" +#include "rlc/dialect/SymbolTable.h" #include "rlc/utils/IRange.hpp" #define GET_OP_CLASSES #include "./Operations.inc" @@ -931,6 +933,19 @@ mlir::LogicalResult mlir::rlc::CanOp::typeCheck( return mlir::success(); } +mlir::LogicalResult mlir::rlc::PickedArgOp::typeCheck( + mlir::rlc::ModuleBuilder &builder) +{ + auto &rewriter = builder.getRewriter(); + rewriter.replaceOpWithNewOp(*this, + getFunction().getType().cast().getInputs()[getArgumentIndex()], + getFunction(), + getArgumentIndex(), + getKnownArgs() + ); + return mlir::success(); +} + mlir::LogicalResult mlir::rlc::ForFieldStatement::typeCheck( mlir::rlc::ModuleBuilder &builder) { diff --git a/lib/dialect/src/Operations.td b/lib/dialect/src/Operations.td index 9e92d836..ab738798 100644 --- a/lib/dialect/src/Operations.td +++ b/lib/dialect/src/Operations.td @@ -1823,3 +1823,9 @@ def RLC_CanOp : RLC_Dialect <"can", [DeclareOpInterfaceMethods]> build($_builder, $_state, resultType, value); }]>]; } + +def RLC_PickedArgOp : RLC_Dialect <"pickedArg", [DeclareOpInterfaceMethods]> { + let arguments = (ins AnyFunctionType:$function, I8Attr:$argument_index, Variadic:$knownArgs); + + let results = (outs AnyType:$argument); +} diff --git a/lib/dialect/src/Passes.td b/lib/dialect/src/Passes.td index 17fca6fe..1ffba430 100644 --- a/lib/dialect/src/Passes.td +++ b/lib/dialect/src/Passes.td @@ -234,5 +234,14 @@ def EmitFuzzTargetPass : Pass<"rlc-emit-fuzz-target-pass", "mlir::ModuleOp"> { let options = [ Option<"actionToFuzz", "Action to fuzz", "std::string", /*default=*/"\"play\"", "Name of the action to generate a fuzzer for">, + Option<"avoidUnavailableSubactions", "Avoid unavailable subactions", "bool" , /*default=*/"true", "Whether the fuzzer filters out unavailable subaction calls."> + ]; +} + +def DynamicArgumentAnalysisPass : Pass<"rlc-dynamic-argument-analysis-pass", "mlir::ModuleOp"> { + let summary = "Lower ArgConstraintsOp's to expressions that compute the minimum and maximum value dynamically."; + let dependentDialects = ["rlc::RLCDialect"]; + let options = [ + Option<"analysePreconditions", "Analyse preconditions", "bool" , /*default=*/"true", "Whether the fuzzer analyses subaction preconditions to pick better arguments"> ]; } diff --git a/lib/driver/include/rlc/driver/Driver.hpp b/lib/driver/include/rlc/driver/Driver.hpp index 8c3f5a75..d85851d9 100644 --- a/lib/driver/include/rlc/driver/Driver.hpp +++ b/lib/driver/include/rlc/driver/Driver.hpp @@ -51,6 +51,14 @@ namespace mlir::rlc { emitFuzzer = newEmitFuzzer; } + void setFuzzerAvoidsUnavailableSubactions(bool newValue) + { + fuzzerAvoidsUnavailableSubactions = newValue; + } + void setFuzzerAnalysesPreconditions(bool newValue) + { + fuzzerAnalysesPreconditions = newValue; + } void setIncludeDirs(llvm::SmallVector newIncludeDirs) { @@ -85,6 +93,8 @@ namespace mlir::rlc bool emitPreconditionChecks = true; bool emitBoundChecks = true; bool emitFuzzer = false; + bool fuzzerAvoidsUnavailableSubactions = true; + bool fuzzerAnalysesPreconditions = true; bool skipParsing = false; bool debug = false; diff --git a/lib/driver/src/Driver.cpp b/lib/driver/src/Driver.cpp index 71295e97..dd90ea4d 100644 --- a/lib/driver/src/Driver.cpp +++ b/lib/driver/src/Driver.cpp @@ -35,7 +35,9 @@ namespace mlir::rlc manager.addPass(mlir::rlc::createValidateStorageQualifiersPass()); if (emitFuzzer) { - manager.addPass(mlir::rlc::createEmitFuzzTargetPass()); + manager.addPass(mlir::rlc::createEmitFuzzTargetPass({ + .avoidUnavailableSubactions = fuzzerAvoidsUnavailableSubactions + })); } if (request == Request::dumpCheckedAST) @@ -107,6 +109,8 @@ namespace mlir::rlc return; } + manager.addPass(mlir::rlc::createDynamicArgumentAnalysisPass({fuzzerAnalysesPreconditions})); + manager.addPass(mlir::rlc::createExtractPreconditionPass()); if (emitPreconditionChecks) diff --git a/lib/fuzzer/src/fuzz_target.cpp b/lib/fuzzer/src/fuzz_target.cpp index d77de541..7b79be93 100644 --- a/lib/fuzzer/src/fuzz_target.cpp +++ b/lib/fuzzer/src/fuzz_target.cpp @@ -53,15 +53,27 @@ int64_t consume_bits(const char *Data, int num_bits, int &byte_offset, int &bit_ // TODO this is not completely uniform since the number of possible inputs is not a power of two, think about whether or not that's a problem. extern "C" void rl_fuzzer_get_input__int64_t_r_int64_t(__int64_t *result, const __int64_t *max) { - // printf("Generating input in range [0, %ld)\n", *max); + printf("Generating input in range [0, %ld)\n", *max); int num_bits = ceil(log2(*max)); *result = consume_bits(fuzz_input, num_bits, byte_offset, bit_offset) % *max; } extern "C" void rl_fuzzer_pick_argument__int64_t_int64_t_r_int64_t(__int64_t *result, const __int64_t *min, __int64_t *max) { - // printf("Picking an integer argument in range [%ld, %ld]\n", *min, *max); + printf("Picking an integer argument in range [%ld, %ld]\n", *min, *max); + if(*min > *max) { + // The current implementation of constraint analysis allows this to happen. When it does, it's not a fault + // of the program under analysis but a fault of the analysis itself. It shouldn't cause an error. Returning + // any arbitrary value is safe here since any value is either valid by pure chance, or will be discarded by the fuzzer. + // It might be worth tweaking the fuzz target not to call this function in such a case, but I see no justification for that + // effort at the moment. + printf("Invalid range. Picked 0.\n"); + *result = 0; + return; + } + int num_bits = ceil(log2(*max - *min + 1)); *result = std::abs(consume_bits(fuzz_input, num_bits, byte_offset, bit_offset)) % (*max - *min + 1) + *min; + printf("Picked %ld\n", *result); } extern "C" void rl_fuzzer_is_input_long_enough__r_bool(__int8_t *result) { diff --git a/tool/rlc/src/Main.cpp b/tool/rlc/src/Main.cpp index be5a8617..4b1587e8 100644 --- a/tool/rlc/src/Main.cpp +++ b/tool/rlc/src/Main.cpp @@ -234,6 +234,20 @@ static cl::opt emitFuzzer( cl::cat(astDumperCategory) ); +static cl::opt fuzzerAvoidsUnavailableSubactions( + "fuzzer-avoid-unavailable-subactions", + cl::desc("the emitted fuzzer avoids generating unavailable subaction calls."), + cl::init(true), + cl::cat(astDumperCategory) +); + +static cl::opt fuzzerAnalysesPreconditions( + "fuzzer-analyse-preconditions", + cl::desc("the emitted fuzzer analyses subaction function preconditions."), + cl::init(true), + cl::cat(astDumperCategory) +); + cl::list RPath("rpath", cl::desc("")); @@ -306,6 +320,8 @@ static mlir::rlc::Driver configureDriver( driver.setExtraObjectFile(ExtraObjectFiles); driver.setRPath(RPath); driver.setEmitFuzzer(emitFuzzer); + driver.setFuzzerAvoidsUnavailableSubactions(fuzzerAvoidsUnavailableSubactions); + driver.setFuzzerAnalysesPreconditions(fuzzerAnalysesPreconditions); driver.setTargetInfo(&info); driver.setEmitBoundChecks(emitBoundChecks); diff --git a/tool/rlc/test/dynamic_analysis.rl b/tool/rlc/test/dynamic_analysis.rl new file mode 100644 index 00000000..b1abd864 --- /dev/null +++ b/tool/rlc/test/dynamic_analysis.rl @@ -0,0 +1,30 @@ +# RUN: rlc %s -o %t -i %stdlib --fuzzer +# RUN: %t +import fuzzer.cpp_functions +import fuzzer.utils + +fun crash_on_five(Int input) -> Int {input != 5}: + return 0 + +fun plus_three(Int input) -> Int: + return input + 3 + +fun factorial(Int input) -> Int: + if input <= 0: + return 1 + else: + return input * factorial(input - 1) + +act play() -> Play: + frm flag = true + frm current = 0 + + while current != 7: + act subact(Int x) {x > (plus_three(current) + 8) / 2, x < 10 + factorial(current)} + current = x + act that(Int a) {a >= 0, ( flag and a <= 20 ) or (flag and a <= 15), ( flag and a > 3 ) or (flag and a > 1)} + crash_on_five(a) + + + + diff --git a/tool/rlc/test/fuzzer_pick_struct.rl b/tool/rlc/test/fuzzer_pick_struct.rl new file mode 100644 index 00000000..b7908555 --- /dev/null +++ b/tool/rlc/test/fuzzer_pick_struct.rl @@ -0,0 +1,37 @@ +import fuzzer.cpp_functions +import fuzzer.utils + +fun crash() {false}: + return + +ent InnerTest: + Int c + +ent Test: + Int a + Int b + InnerTest inner + +act subact(ctx Test context_struct) -> Subact: + frm subact_frame_var : Int + subact_frame_var = 250 + act uses_context_struct(Test t1) { + t1.a == context_struct.b, + t1.b > context_struct.a, + t1.b < subact_frame_var, + t1.inner.c == context_struct.b + t1.b + } + +act play() -> Play: + frm struct_in_frame : Test + act uses_struct(Test t) { + t.a >= 0, + t.a <= 5, + t.b >= -10, + t.a <= 2 or t.b >= 5, + t.b <= 16 + } + struct_in_frame = t + act pick_substruct(InnerTest it) + subaction*(struct_in_frame) s = subact(struct_in_frame) + crash() diff --git a/tool/rlc/test/fuzzing_wrapped_action_statements.rl b/tool/rlc/test/fuzzing_wrapped_action_statements.rl new file mode 100644 index 00000000..7a00a052 --- /dev/null +++ b/tool/rlc/test/fuzzing_wrapped_action_statements.rl @@ -0,0 +1,114 @@ +import fuzzer.cpp_functions +import fuzzer.utils +import collections.vector + +fun crash() {1 == 0}: + return + +ent Deck: + Vector cards + + fun init(): + self.cards.init() + let i = 1 + while i <= 13 : + # once for each suit. + self.cards.append(i) + self.cards.append(i) + self.cards.append(i) + self.cards.append(i) + i = i + 1 + + fun switch_cards(Int a, Int b) {a >= 0, a < 52, b >= 0, b < 52}: + let temp = self.cards.get(a) + self.cards.set(a, self.cards.get(b)) + self.cards.set(b, temp) + +fun draw(Deck deck, Vector hand): + let card = deck.cards.pop() + hand.append(card) + +fun deal(Deck deck, Vector hand): + let i = 0 + while i < 2: + draw(deck, hand) + i = i + 1 + +fun calculate_points(Vector hand) -> Int: + let total = 0 + let num_ones = 0 + + let i = 0 + while i < hand.size(): + let card = hand.get(i) + if card <= 10: + total = total + card + else: + total = total + 10 + + if card == 1: + num_ones = num_ones + 1 + i = i + 1 + + while num_ones > 0 and total + 10 < 21: + num_ones = num_ones - 1 + total = total + 10 + + if num_ones == 2: + crash() + + return total + +act shuffle(ctx Deck deck) -> Shuffle: + frm done = false + #TODO delet dis. It's only here to showcase being able to use values from the underlying op's frame. + frm last_picked_a = 0 + while !done: + actions: + act stop_shuffle() + done = true + + act switch(Int a, Int b) {a >= last_picked_a, a < deck.cards.size(), b >= 0, b < 52} + deck.switch_cards(a, b) + last_picked_a = last_picked_a + 1 + +act play() -> Blackjack: + frm deck : Deck + frm player_hand : Vector + frm dealer_hand : Vector + + subaction*(deck) s = shuffle(deck) + + deal(deck, player_hand) + deal(deck, dealer_hand) + + frm player_passed = false + frm player_bust = false + + while !player_bust and !player_passed: + actions: + act hit() + draw(deck, player_hand) + if calculate_points(player_hand) > 21: + player_bust = true + + act stand() + player_passed = true + + while calculate_points(dealer_hand) <= 16: + draw(deck, dealer_hand) + +#fun main() -> Int: +# let game = play() +# game.switch(0, 51) +# game.switch(1, 50) +# game.stop_shuffle() +# if(game.player_hand.size() != 2): +# return 1 +# if(game.dealer_hand.size() != 2): +# return 1 +# game.hit() +# +# if(!game.is_done()): +# return 1 +# return 0