diff --git a/include/NeuraDialect/NeuraOps.td b/include/NeuraDialect/NeuraOps.td index 628190c4..223eee9f 100644 --- a/include/NeuraDialect/NeuraOps.td +++ b/include/NeuraDialect/NeuraOps.td @@ -133,8 +133,8 @@ def Neura_SelOp : Op { } def Neura_NotOp : Op { - let arguments = (ins I1:$input); - let results = (outs I1:$output); + let arguments = (ins AnyType:$input); + let results = (outs AnyType:$output); // let assemblyFormat = "$input attr-dict `:` type($output)"; } @@ -253,3 +253,35 @@ def Neura_ReserveOp : Op { let results = (outs AnyType:$result); let assemblyFormat = "attr-dict `:` type($result)"; } + +def Neura_GrantPredicateOp : Op { + let summary = "Grants a new predicate to a predicated value."; + let description = [{ + Takes a predicated value and a predicate (i1), producing a new predicated + value whose predicate bit is set to the given condition. + + Example: + %g = neura.grant_predicate %val, %pred : !neura.data, !neura.data -> !neura.data + }]; + + let arguments = (ins AnyType:$value, AnyType:$predicate); + let results = (outs AnyType:$result); + + let assemblyFormat = "$value `,` $predicate attr-dict `:` type($value) `,` type($predicate) `->` type($result)"; +} + +def Neura_GrantOnceOp : Op { + let summary = "Marks a value as valid once."; + let description = [{ + Grants a value a one-time predicate: the resulting value is considered valid + only during its first activation. Used to initialize recurrence cycles. + + Example: + %v = neura.grant_once %init : !neura.data -> !neura.data + }]; + + let arguments = (ins AnyType:$value); + let results = (outs AnyType:$result); + + // let assemblyFormat = "$value attr-dict `:` type($value) `->` type($result)"; +} diff --git a/include/NeuraDialect/NeuraPasses.h b/include/NeuraDialect/NeuraPasses.h index 9cdeef7f..61a14a0e 100644 --- a/include/NeuraDialect/NeuraPasses.h +++ b/include/NeuraDialect/NeuraPasses.h @@ -22,6 +22,7 @@ std::unique_ptr createFusePatternsPass(); std::unique_ptr createAssignAcceleratorPass(); std::unique_ptr createTransformCtrlToDataFlowPass(); std::unique_ptr createLeveragePredicatedValuePass(); +std::unique_ptr createMapToAcceleratorPass(); #define GEN_PASS_REGISTRATION #include "NeuraDialect/NeuraPasses.h.inc" diff --git a/include/NeuraDialect/NeuraPasses.td b/include/NeuraDialect/NeuraPasses.td index f4ea76a7..8808a452 100644 --- a/include/NeuraDialect/NeuraPasses.td +++ b/include/NeuraDialect/NeuraPasses.td @@ -50,4 +50,12 @@ def LeveragePredicatedValue : Pass<"leverage-predicated-value", "ModuleOp"> { let constructor = "neura::createLeveragePredicatedValuePass()"; } +def MapToAccelerator : Pass<"map-to-accelerator", "ModuleOp"> { + let summary = "Map Neura operations onto a given accelerator"; + let description = [{ + This pass performs mapping from Neura operations to accelerator. + }]; + let constructor = "neura::createMapToAcceleratorPass()"; +} + #endif // NEURA_PASSES_TD \ No newline at end of file diff --git a/lib/NeuraDialect/Transforms/CMakeLists.txt b/lib/NeuraDialect/Transforms/CMakeLists.txt index b5dbb4f9..c1d16bdc 100644 --- a/lib/NeuraDialect/Transforms/CMakeLists.txt +++ b/lib/NeuraDialect/Transforms/CMakeLists.txt @@ -8,6 +8,7 @@ add_mlir_library( AssignAcceleratorPass.cpp TransformCtrlToDataFlowPass.cpp LeveragePredicatedValuePass.cpp + MapToAcceleratorPass.cpp DEPENDS MLIRNeuraTransformsIncGen diff --git a/lib/NeuraDialect/Transforms/MapToAcceleratorPass.cpp b/lib/NeuraDialect/Transforms/MapToAcceleratorPass.cpp new file mode 100644 index 00000000..76e25901 --- /dev/null +++ b/lib/NeuraDialect/Transforms/MapToAcceleratorPass.cpp @@ -0,0 +1,146 @@ +#include + +#include "NeuraDialect/NeuraDialect.h" +#include "NeuraDialect/NeuraOps.h" +#include "NeuraDialect/NeuraTypes.h" +#include "NeuraDialect/NeuraPasses.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +using namespace mlir; + +#define GEN_PASS_DEF_MapToAccelerator +#include "NeuraDialect/NeuraPasses.h.inc" + +namespace { + +/// Represents a recurrence cycle rooted at a reserve operation and ending at a ctrl_mov. +/// The cycle consists of a sequence of operations and its corresponding length. +struct RecurrenceCycle { + SmallVector operations; // Ordered list of operations in the cycle. + int length = 0; // Number of operations excluding ctrl_mov and reserve_op. +}; + +// Traverses (backward) the operation graph starting from the given operation +// towards reserve_value. +void traverseAlongPath(Operation *op, Value reserve_value, + std::deque ¤t_path, + DenseSet &visited_in_path, + SmallVector &collected_paths) { + if (!op || visited_in_path.contains(op)) + return; + + visited_in_path.insert(op); + current_path.push_front(op); + + for (Value operand : op->getOperands()) { + if (operand == reserve_value) { + Operation *res_op = reserve_value.getDefiningOp(); + if (res_op) current_path.push_front(res_op); + + constexpr int kNumExcludedOps = 2; + collected_paths.push_back(RecurrenceCycle{ + operations: SmallVector(current_path.begin(), current_path.end()), + length: static_cast(current_path.size()) - kNumExcludedOps + }); + + if (res_op) current_path.pop_front(); // Remove reserve before backtracking + continue; + } + + if (Operation *def_op = operand.getDefiningOp()) { + traverseAlongPath(def_op, reserve_value, current_path, visited_in_path, collected_paths); + } + } + + current_path.pop_front(); // Backtrack + visited_in_path.erase(op); // Unmark from path +} + +/// Collects all recurrence cycles rooted at reserve operations and closed by ctrl_mov. +/// Each cycle contains the operation sequence and its corresponding length. +SmallVector collectRecurrenceCycles(Operation *root_op) { + SmallVector recurrence_cycles; + + root_op->walk([&](neura::CtrlMovOp ctrl_mov_op) { + Value target = ctrl_mov_op.getTarget(); + auto reserve_op = target.getDefiningOp(); + if (!reserve_op) + return; + + Value reserve_value = reserve_op.getResult(); + Value ctrl_mov_from = ctrl_mov_op.getValue(); + + Operation *parent_op = ctrl_mov_from.getDefiningOp(); + if (!parent_op) + return; + + std::deque current_path; + SmallVector collected_paths; + DenseSet visited_in_path; + traverseAlongPath(parent_op, reserve_value, current_path, visited_in_path, collected_paths); + + for (auto &cycle : collected_paths) { + cycle.operations.push_back(ctrl_mov_op); + ++cycle.length; + recurrence_cycles.push_back(std::move(cycle)); + } + }); + + return recurrence_cycles; +} + +struct MapToAcceleratorPass + : public PassWrapper> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MapToAcceleratorPass) + + StringRef getArgument() const override { return "map-to-accelerator"; } + StringRef getDescription() const override { + return "Maps IR to the target accelerator."; + } + + void getDependentDialects(DialectRegistry ®istry) const override { + registry.insert(); + } + + void runOnOperation() override { + ModuleOp module = getOperation(); + + module.walk([&](func::FuncOp func) { + // Skips functions not targeting the neura accelerator. + auto accel_attr = func->getAttrOfType("accelerator"); + if (!accel_attr || accel_attr.getValue() != "neura") + return; + + // Collects and reports recurrence cycles found in the function. + auto recurrence_cycles = collectRecurrenceCycles(func); + RecurrenceCycle *longest = nullptr; + for (auto &cycle : recurrence_cycles) { + if (!longest || cycle.length > longest->length) + longest = &cycle; + } + + if (longest) { + llvm::errs() << "[MapToAcceleratorPass] Longest recurrence cycle (length " + << longest->length << "):\n"; + for (Operation *op : longest->operations) + op->print(llvm::errs()), llvm::errs() << "\n"; + IntegerAttr mii_attr = IntegerAttr::get( + IntegerType::get(func.getContext(), 32), longest->length); + func->setAttr("RecMII", mii_attr); + } + }); + } +}; + +} // namespace + +namespace mlir::neura { + +std::unique_ptr createMapToAcceleratorPass() { + return std::make_unique(); +} + +} // namespace mlir::neura diff --git a/lib/NeuraDialect/Transforms/TransformCtrlToDataFlowPass.cpp b/lib/NeuraDialect/Transforms/TransformCtrlToDataFlowPass.cpp index 3af34324..54642b13 100644 --- a/lib/NeuraDialect/Transforms/TransformCtrlToDataFlowPass.cpp +++ b/lib/NeuraDialect/Transforms/TransformCtrlToDataFlowPass.cpp @@ -12,6 +12,48 @@ using namespace mlir; #define GEN_PASS_DEF_TransformCtrlToDataFlow #include "NeuraDialect/NeuraPasses.h.inc" +// Inserts `grant_once` for every predicated value defined in the entry block +// that is used outside of the block (i.e., a live-out). +void insertGrantOnceInEntryBlock(Block *entry_block, OpBuilder &builder, + DenseMap &granted_once_map) { + SmallVector live_out_values; + + // Step 1: Collects all live-out values first. + for (Operation &op : *entry_block) { + for (Value result : op.getResults()) { + if (!isa(result.getType())) + continue; + + bool is_live_out = llvm::any_of(result.getUses(), [&](OpOperand &use) { + Operation *user = use.getOwner(); + return user->getBlock() != entry_block || isa(user); + }); + + if (is_live_out && !granted_once_map.contains(result)) + live_out_values.push_back(result); + } + } + + // Step 2: Inserts grant_once for each candidate. + for (Value val : live_out_values) { + Operation *def_op = val.getDefiningOp(); + if (!def_op) + continue; + + builder.setInsertionPointAfter(def_op); + auto granted = builder.create(def_op->getLoc(), val.getType(), val); + granted_once_map[val] = granted.getResult(); + + // Replaces external uses with granted result. + for (OpOperand &use : llvm::make_early_inc_range(val.getUses())) { + Operation *user = use.getOwner(); + if (user->getBlock() != entry_block || isa(user)) { + use.set(granted.getResult()); + } + } + } +} + // Returns blocks in post-order traversal order. void getBlocksInPostOrder(Block *startBlock, SmallVectorImpl &postOrder, DenseSet &visited) { @@ -27,9 +69,9 @@ void getBlocksInPostOrder(Block *startBlock, SmallVectorImpl &postOrder } // Creates phi nodes for all live-in values in the given block. -void createPhiNodesForBlock(Block *block, OpBuilder &builder, - DenseMap &value_map, - SmallVectorImpl> &deferred_ctrl_movs) { +void createPhiNodesForBlock( + Block *block, OpBuilder &builder, DenseMap &value_map, + SmallVectorImpl> &deferred_ctrl_movs) { if (block->hasNoPredecessors()) { // Skips phi insertion for entry block. return; @@ -63,8 +105,8 @@ void createPhiNodesForBlock(Block *block, OpBuilder &builder, // Uses the location from the first operation in the block or block's parent operation. Location loc = block->empty() ? - block->getParent()->getLoc() : - block->front().getLoc(); + block->getParent()->getLoc() : + block->front().getLoc(); SmallVector phi_operands; BlockArgument arg = dyn_cast(live_in); @@ -76,6 +118,7 @@ void createPhiNodesForBlock(Block *block, OpBuilder &builder, unsigned arg_index = arg.getArgNumber(); for (Block *pred : block->getPredecessors()) { Value incoming; + Value branch_pred; Operation *term = pred->getTerminator(); // If it's a branch or cond_br, get the value passed into this block argument @@ -84,18 +127,33 @@ void createPhiNodesForBlock(Block *block, OpBuilder &builder, assert(arg_index < args.size()); incoming = args[arg_index]; } else if (auto condBr = dyn_cast(term)) { + Value cond = condBr.getCondition(); + branch_pred = cond; // by default + + OpBuilder pred_builder(condBr); + Location pred_loc = condBr.getLoc(); + if (condBr.getTrueDest() == block) { auto trueArgs = condBr.getTrueArgs(); assert(arg_index < trueArgs.size()); incoming = trueArgs[arg_index]; + // Keep branch_pred = cond } else if (condBr.getFalseDest() == block) { auto falseArgs = condBr.getFalseArgs(); assert(arg_index < falseArgs.size()); incoming = falseArgs[arg_index]; + // Negates cond for false edge. + branch_pred = pred_builder.create(pred_loc, cond.getType(), cond); + } else { llvm::errs() << "cond_br does not target block:\n" << *block << "\n"; - continue; + assert(false); } + + // Applies grant_predicate. + incoming = pred_builder.create( + pred_loc, incoming.getType(), incoming, branch_pred); + } else { llvm::errs() << "Unknown branch terminator in block: " << *pred << "\n"; continue; @@ -108,8 +166,10 @@ void createPhiNodesForBlock(Block *block, OpBuilder &builder, auto placeholder = builder.create(loc, incoming.getType()); phi_operands.push_back(placeholder.getResult()); // Defers the backward ctrl move operation to be inserted after all phi operands - // are defined. Inserted: (real_defined_value, just_created_reserve, current_block). - deferred_ctrl_movs.emplace_back(incoming, placeholder.getResult(), block); + // are defined. Inserted: + // (real_defined_value, just_created_reserve, branch_pred, current_block). + deferred_ctrl_movs.emplace_back( + incoming, placeholder.getResult(), branch_pred, block); } else { phi_operands.push_back(incoming); } @@ -191,9 +251,15 @@ struct TransformCtrlToDataFlowPass // The tuple contains: // - real value (the one that is defined in the same block, after the placeholder) // - placeholder value (the one that will be used in the phi node) + // - branch predicate (if any, for cond_br) // - block where the backward ctrl move should be inserted - SmallVector, 4> deferred_ctrl_movs; + SmallVector, 4> deferred_ctrl_movs; module.walk([&](func::FuncOp func) { + + OpBuilder builder(func.getContext()); + DenseMap granted_once_map; + insertGrantOnceInEntryBlock(&func.getBody().front(), builder, granted_once_map); + // Get blocks in post-order SmallVector postOrder; DenseSet visited; @@ -201,7 +267,6 @@ struct TransformCtrlToDataFlowPass // Value mapping for phi node creation. DenseMap value_map; - OpBuilder builder(func.getContext()); // Process blocks bottom-up for (Block *block : postOrder) { @@ -249,12 +314,25 @@ struct TransformCtrlToDataFlowPass // Inserts the deferred backward ctrl move operations after phi operands // are defined. - for (auto &[realVal, placeholder, block] : deferred_ctrl_movs) { - Operation *defOp = realVal.getDefiningOp(); - assert(defOp && "Backward ctrl move's source must be an op result"); - OpBuilder movBuilder(defOp->getBlock(), ++Block::iterator(defOp)); - movBuilder.create(defOp->getLoc(), realVal, placeholder); + for (auto &[real_dependent, placeholder, branch_pred, block] : deferred_ctrl_movs) { + Operation *def_op = real_dependent.getDefiningOp(); + assert(def_op && "Backward ctrl move's source must be an op result"); + + // Find the correct insertion point: after both real_dependent and branch_pred + Operation *insert_after = def_op; + if (Operation *pred_def = branch_pred.getDefiningOp()) { + if (insert_after->isBeforeInBlock(pred_def)) + insert_after = pred_def; + } + + OpBuilder mov_builder(insert_after->getBlock(), ++Block::iterator(insert_after)); + Location insert_loc = insert_after->getLoc(); + + Value guarded_val = real_dependent; + + mov_builder.create(insert_loc, guarded_val, placeholder); } + } }; } // namespace diff --git a/test/neura/ctrl/branch.mlir b/test/neura/ctrl/branch.mlir index 606ff758..572738b6 100644 --- a/test/neura/ctrl/branch.mlir +++ b/test/neura/ctrl/branch.mlir @@ -54,12 +54,23 @@ func.func @test(%in: i64) -> f32 { // CTRL2DATA: func.func @test(%arg0: i64) -> f32 attributes {accelerator = "neura"} { // CTRL2DATA-NEXT: %0 = "neura.constant"() <{predicate = true, value = 0 : i64}> : () -> !neura.data // CTRL2DATA-NEXT: %1 = "neura.constant"() <{predicate = true, value = 1.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %2 = "neura.constant"() <{predicate = true, value = 2.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %3 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %4 = "neura.constant"() <{predicate = true, value = 4.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %5 = "neura.icmp"(%arg0, %0) <{cmpType = "eq"}> : (i64, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %6 = "neura.fadd"(%1, %2) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %7 = "neura.fmul"(%3, %4) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %8 = "neura.phi"(%6, %7) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: "neura.return"(%8) : (!neura.data) -> () +// CTRL2DATA-NEXT: %2 = "neura.grant_once"(%1) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %3 = "neura.constant"() <{predicate = true, value = 2.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %4 = "neura.grant_once"(%3) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %5 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %6 = "neura.grant_once"(%5) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %7 = "neura.constant"() <{predicate = true, value = 4.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %8 = "neura.grant_once"(%7) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %9 = "neura.icmp"(%arg0, %0) <{cmpType = "eq"}> : (i64, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %10 = "neura.grant_once"(%9) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %11 = neura.grant_predicate %6, %10 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %12 = neura.grant_predicate %8, %10 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %13 = "neura.not"(%10) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %14 = neura.grant_predicate %2, %13 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %15 = "neura.not"(%10) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %16 = neura.grant_predicate %4, %15 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %17 = "neura.fadd"(%14, %16) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %18 = "neura.fmul"(%11, %12) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %19 = "neura.phi"(%17, %18) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: "neura.return"(%19) : (!neura.data) -> () // CTRL2DATA-NEXT: } \ No newline at end of file diff --git a/test/neura/ctrl/branch_for.mlir b/test/neura/ctrl/branch_for.mlir index d6207104..eac44582 100644 --- a/test/neura/ctrl/branch_for.mlir +++ b/test/neura/ctrl/branch_for.mlir @@ -11,6 +11,14 @@ // RUN: --transform-ctrl-to-data-flow \ // RUN: | FileCheck %s -check-prefix=CTRL2DATA +// RUN: mlir-neura-opt %s \ +// RUN: --assign-accelerator \ +// RUN: --lower-llvm-to-neura \ +// RUN: --leverage-predicated-value \ +// RUN: --transform-ctrl-to-data-flow \ +// RUN: --map-to-accelerator \ +// RUN: | FileCheck %s -check-prefix=RECMII + func.func @loop_test() -> f32 { %n = llvm.mlir.constant(10 : i64) : i64 %c0 = llvm.mlir.constant(0 : i64) : i64 @@ -48,18 +56,29 @@ func.func @loop_test() -> f32 { // CTRL2DATA: func.func @loop_test() -> f32 attributes {accelerator = "neura"} { // CTRL2DATA-NEXT: %0 = "neura.constant"() <{predicate = true, value = 10 : i64}> : () -> !neura.data -// CTRL2DATA-NEXT: %1 = "neura.constant"() <{predicate = true, value = 0 : i64}> : () -> !neura.data -// CTRL2DATA-NEXT: %2 = "neura.constant"() <{predicate = true, value = 1 : i64}> : () -> !neura.data -// CTRL2DATA-NEXT: %3 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %4 = "neura.constant"() <{predicate = true, value = 0.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %5 = neura.reserve : !neura.data -// CTRL2DATA-NEXT: %6 = "neura.phi"(%1, %5) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %7 = neura.reserve : !neura.data -// CTRL2DATA-NEXT: %8 = "neura.phi"(%4, %7) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %9 = "neura.fadd"(%8, %3) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: neura.ctrl_mov %9 -> %7 : !neura.data !neura.data -// CTRL2DATA-NEXT: %10 = "neura.add"(%6, %2) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: neura.ctrl_mov %10 -> %5 : !neura.data !neura.data -// CTRL2DATA-NEXT: %11 = "neura.icmp"(%10, %0) <{cmpType = "slt"}> : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: "neura.return"(%9) : (!neura.data) -> () +// CTRL2DATA-NEXT: %1 = "neura.grant_once"(%0) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %2 = "neura.constant"() <{predicate = true, value = 0 : i64}> : () -> !neura.data +// CTRL2DATA-NEXT: %3 = "neura.grant_once"(%2) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %4 = "neura.constant"() <{predicate = true, value = 1 : i64}> : () -> !neura.data +// CTRL2DATA-NEXT: %5 = "neura.grant_once"(%4) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %6 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %7 = "neura.grant_once"(%6) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %8 = "neura.constant"() <{predicate = true, value = 0.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %9 = "neura.grant_once"(%8) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %10 = neura.reserve : !neura.data +// CTRL2DATA-NEXT: %11 = "neura.phi"(%3, %10) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %12 = neura.reserve : !neura.data +// CTRL2DATA-NEXT: %13 = "neura.phi"(%9, %12) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %14 = "neura.fadd"(%13, %7) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %15 = "neura.add"(%11, %5) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %16 = "neura.icmp"(%15, %1) <{cmpType = "slt"}> : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %17 = "neura.not"(%16) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %18 = neura.grant_predicate %14, %17 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %19 = neura.grant_predicate %14, %16 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: neura.ctrl_mov %19 -> %12 : !neura.data !neura.data +// CTRL2DATA-NEXT: %20 = neura.grant_predicate %15, %16 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: neura.ctrl_mov %20 -> %10 : !neura.data !neura.data +// CTRL2DATA-NEXT: "neura.return"(%18) : (!neura.data) -> () // CTRL2DATA-NEXT: } + +// RECMII: func.func @loop_test() -> f32 attributes {RecMII = 4 : i32, accelerator = "neura"} \ No newline at end of file diff --git a/test/neura/ctrl/branch_no_arg.mlir b/test/neura/ctrl/branch_no_arg.mlir index 4755fd1c..5d759c6d 100644 --- a/test/neura/ctrl/branch_no_arg.mlir +++ b/test/neura/ctrl/branch_no_arg.mlir @@ -50,15 +50,23 @@ func.func @test(%in: i64) -> f32 { // CHECK-NEXT: "neura.return"(%10) : (!neura.data) -> () // CHECK-NEXT: } +// FIXME: Seems the bb1 is not depending on condition's NOT. // CTRL2DATA: func.func @test(%arg0: i64) -> f32 attributes {accelerator = "neura"} { // CTRL2DATA-NEXT: %0 = "neura.constant"() <{predicate = true, value = 0 : i64}> : () -> !neura.data // CTRL2DATA-NEXT: %1 = "neura.constant"() <{predicate = true, value = 1.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %2 = "neura.constant"() <{predicate = true, value = 2.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %3 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %4 = "neura.constant"() <{predicate = true, value = 4.000000e+00 : f32}> : () -> !neura.data -// CTRL2DATA-NEXT: %5 = "neura.icmp"(%arg0, %0) <{cmpType = "eq"}> : (i64, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %6 = "neura.fadd"(%1, %2) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %7 = "neura.fmul"(%3, %4) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: %8 = "neura.phi"(%6, %7) : (!neura.data, !neura.data) -> !neura.data -// CTRL2DATA-NEXT: "neura.return"(%8) : (!neura.data) -> () +// CTRL2DATA-NEXT: %2 = "neura.grant_once"(%1) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %3 = "neura.constant"() <{predicate = true, value = 2.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %4 = "neura.grant_once"(%3) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %5 = "neura.constant"() <{predicate = true, value = 3.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %6 = "neura.grant_once"(%5) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %7 = "neura.constant"() <{predicate = true, value = 4.000000e+00 : f32}> : () -> !neura.data +// CTRL2DATA-NEXT: %8 = "neura.grant_once"(%7) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %9 = "neura.icmp"(%arg0, %0) <{cmpType = "eq"}> : (i64, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %10 = "neura.grant_once"(%9) : (!neura.data) -> !neura.data +// CTRL2DATA-NEXT: %11 = neura.grant_predicate %6, %10 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %12 = neura.grant_predicate %8, %10 : !neura.data, !neura.data -> !neura.data +// CTRL2DATA-NEXT: %13 = "neura.fadd"(%2, %4) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %14 = "neura.fmul"(%11, %12) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: %15 = "neura.phi"(%13, %14) : (!neura.data, !neura.data) -> !neura.data +// CTRL2DATA-NEXT: "neura.return"(%15) : (!neura.data) -> () // CTRL2DATA-NEXT: } diff --git a/test/neura/ctrl/nested_branch.mlir b/test/neura/ctrl/nested_branch.mlir new file mode 100644 index 00000000..4309d8a7 --- /dev/null +++ b/test/neura/ctrl/nested_branch.mlir @@ -0,0 +1,44 @@ +// RUN: mlir-neura-opt \ +// RUN: --assign-accelerator \ +// RUN: --lower-llvm-to-neura +// RN: --transform-ctrl-to-data-flow \ +// RN: %s | FileCheck %s + +func.func @complex_test(%in: i64) -> f32 { + %c0 = llvm.mlir.constant(0 : i64) : i64 + %c1 = llvm.mlir.constant(1.0 : f32) : f32 + %c2 = llvm.mlir.constant(2.0 : f32) : f32 + %c3 = llvm.mlir.constant(3.0 : f32) : f32 + %c4 = llvm.mlir.constant(4.0 : f32) : f32 + %cond = llvm.icmp "eq" %in, %c0 : i64 + llvm.cond_br %cond, ^bb2, ^bb1(%c1 : f32) + +^bb1(%true_val: f32): + %loop_cond = llvm.fcmp "olt" %true_val, %c2 : f32 + llvm.cond_br %loop_cond, ^bb1_loop(%true_val : f32), ^bb3(%true_val : f32) + +^bb1_loop(%loop_val: f32): + %updated_val = llvm.fadd %loop_val, %c1 : f32 + llvm.br ^bb1(%updated_val : f32) + +^bb2: + %false_val = llvm.fmul %c3, %c4 : f32 + llvm.br ^bb3(%false_val : f32) + +^bb3(%v: f32): + return %v : f32 +} + +// CHECK: func.func @complex_test(%arg0: i64) -> f32 attributes {accelerator = "neura"} { +// CHECK-NEXT: %0 = "neura.constant"() <{value = 0 : i64}> : () -> i64 +// CHECK-NEXT: %1 = "neura.constant"() <{value = 1.000000e+00 : f32}> : () -> f32 +// CHECK-NEXT: %2 = "neura.constant"() <{value = 2.000000e+00 : f32}> : () -> f32 +// CHECK-NEXT: %3 = "neura.constant"() <{value = 3.000000e+00 : f32}> : () -> f32 +// CHECK-NEXT: %4 = "neura.constant"() <{value = 4.000000e+00 : f32}> : () -> f32 +// CHECK-NEXT: %5 = "neura.icmp"(%arg0, %0) <{cmpType = "eq"}> : (i64, i64) -> i1 +// CHECK-NEXT: %6 = "neura.not"(%5) : (i1) -> i1 +// CHECK-NEXT: %7 = "neura.fmul"(%3, %4, %5) : (f32, f32, i1) -> f32 +// CHECK-NEXT: %8 = "neura.fadd"(%1, %2, %6) : (f32, f32, i1) -> f32 +// CHECK-NEXT: %9 = "neura.sel"(%7, %8, %5) : (f32, f32, i1) -> f32 +// CHECK-NEXT: return %9 : f32 +// CHECK-NEXT: } \ No newline at end of file