From c48566c55d0de7b566fb96e2c4f1382943d7eb66 Mon Sep 17 00:00:00 2001 From: item Date: Wed, 6 Aug 2025 21:48:50 +0800 Subject: [PATCH 1/6] add interpreter dataflow mode --- .../interpreter/basic_operation/add.mlir | 2 +- .../neura/interpreter/basic_operation/br.mlir | 4 +- .../interpreter/basic_operation/cast.mlir | 2 +- .../interpreter/basic_operation/cond_br.mlir | 2 +- .../interpreter/basic_operation/ctrl_mov.mlir | 2 +- .../interpreter/basic_operation/fadd.mlir | 2 +- .../basic_operation/fadd_fadd.mlir | 2 +- .../interpreter/basic_operation/fcmp.mlir | 2 +- .../interpreter/basic_operation/fdiv.mlir | 2 +- .../interpreter/basic_operation/fmul.mlir | 2 +- .../interpreter/basic_operation/fmul_add.mlir | 2 +- .../interpreter/basic_operation/fsub.mlir | 2 +- .../interpreter/basic_operation/grant.mlir | 2 +- .../interpreter/basic_operation/icmp.mlir | 2 +- .../basic_operation/load_store.mlir | 2 +- .../basic_operation/load_store_index.mlir | 2 +- .../interpreter/basic_operation/not.mlir | 23 +- .../neura/interpreter/basic_operation/or.mlir | 2 +- .../interpreter/basic_operation/phi.mlir | 2 +- .../interpreter/basic_operation/reserve.mlir | 2 +- .../interpreter/basic_operation/sel.mlir | 2 +- .../interpreter/basic_operation/sub.mlir | 2 +- .../interpreter/basic_operation/vfmul.mlir | 2 +- test/neura/interpreter/loop_dataflow.mlir | 38 + .../interpreter/lower_and_interpret.mlir | 4 +- tools/neura-interpreter/neura-interpreter.cpp | 1102 ++++++++++++++--- 26 files changed, 978 insertions(+), 235 deletions(-) create mode 100644 test/neura/interpreter/loop_dataflow.mlir diff --git a/test/neura/interpreter/basic_operation/add.mlir b/test/neura/interpreter/basic_operation/add.mlir index bd731a96..5496aa1c 100644 --- a/test/neura/interpreter/basic_operation/add.mlir +++ b/test/neura/interpreter/basic_operation/add.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ===----------------------------------------------------------------------===// // Test 1: Add two float constants diff --git a/test/neura/interpreter/basic_operation/br.mlir b/test/neura/interpreter/basic_operation/br.mlir index fe49b75e..a8959c8c 100644 --- a/test/neura/interpreter/basic_operation/br.mlir +++ b/test/neura/interpreter/basic_operation/br.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_br_with_args() -> i32 { %0 = "neura.constant"() {value = 42 : i32} : () -> i32 @@ -16,6 +16,6 @@ func.func @test_br_with_multi_args() { ^bb1(%a: i32, %b: f32): "neura.add"(%a, %a) : (i32, i32) -> i32 - // CHECK-NEXT: [neura-interpreter] → Output: (void) + // CHECK: [neura-interpreter] → Output: (void) return } \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/cast.mlir b/test/neura/interpreter/basic_operation/cast.mlir index 074d755c..dbb6b381 100644 --- a/test/neura/interpreter/basic_operation/cast.mlir +++ b/test/neura/interpreter/basic_operation/cast.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // int -> float func.func @test_cast_i2f() -> f32 { diff --git a/test/neura/interpreter/basic_operation/cond_br.mlir b/test/neura/interpreter/basic_operation/cond_br.mlir index d23cab63..c0d73fb3 100644 --- a/test/neura/interpreter/basic_operation/cond_br.mlir +++ b/test/neura/interpreter/basic_operation/cond_br.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_cond_br_true() { %cond = arith.constant 1 : i1 diff --git a/test/neura/interpreter/basic_operation/ctrl_mov.mlir b/test/neura/interpreter/basic_operation/ctrl_mov.mlir index a6574ac7..f018f9c4 100644 --- a/test/neura/interpreter/basic_operation/ctrl_mov.mlir +++ b/test/neura/interpreter/basic_operation/ctrl_mov.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_ctrl_mov_basic() { %a = "neura.reserve"() : () -> (i32) diff --git a/test/neura/interpreter/basic_operation/fadd.mlir b/test/neura/interpreter/basic_operation/fadd.mlir index f13717bc..d1b1d9ac 100644 --- a/test/neura/interpreter/basic_operation/fadd.mlir +++ b/test/neura/interpreter/basic_operation/fadd.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ===----------------------------------------------------------------------===// // Test 1: Valid neura.fadd with positive constants diff --git a/test/neura/interpreter/basic_operation/fadd_fadd.mlir b/test/neura/interpreter/basic_operation/fadd_fadd.mlir index 02664da2..204ee439 100644 --- a/test/neura/interpreter/basic_operation/fadd_fadd.mlir +++ b/test/neura/interpreter/basic_operation/fadd_fadd.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // Test basic fused fadd operation: (2.5 + 1.5) + 3.0 = 7.0 func.func @test_fadd_fadd_basic() -> f32 { diff --git a/test/neura/interpreter/basic_operation/fcmp.mlir b/test/neura/interpreter/basic_operation/fcmp.mlir index 7888d7e5..441b5d00 100644 --- a/test/neura/interpreter/basic_operation/fcmp.mlir +++ b/test/neura/interpreter/basic_operation/fcmp.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ====== Equal comparison (eq) ====== func.func @test_fcmp_eq_true() -> i1 { diff --git a/test/neura/interpreter/basic_operation/fdiv.mlir b/test/neura/interpreter/basic_operation/fdiv.mlir index 492d6c00..5b3bf0f8 100644 --- a/test/neura/interpreter/basic_operation/fdiv.mlir +++ b/test/neura/interpreter/basic_operation/fdiv.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_fdiv_positive() -> f32 { %a = arith.constant 10.0 : f32 diff --git a/test/neura/interpreter/basic_operation/fmul.mlir b/test/neura/interpreter/basic_operation/fmul.mlir index 93fdf302..40ba2daa 100644 --- a/test/neura/interpreter/basic_operation/fmul.mlir +++ b/test/neura/interpreter/basic_operation/fmul.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ===----------------------------------------------------------------------===// // Test 1: Valid neura.fmul with positive constants diff --git a/test/neura/interpreter/basic_operation/fmul_add.mlir b/test/neura/interpreter/basic_operation/fmul_add.mlir index 85b10e75..402c5f9a 100644 --- a/test/neura/interpreter/basic_operation/fmul_add.mlir +++ b/test/neura/interpreter/basic_operation/fmul_add.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // (2.0 * 3.0) + 4.0 = 10.0 func.func @test_fmul_fadd_basic() -> f32 { diff --git a/test/neura/interpreter/basic_operation/fsub.mlir b/test/neura/interpreter/basic_operation/fsub.mlir index cd12aebf..a800145e 100644 --- a/test/neura/interpreter/basic_operation/fsub.mlir +++ b/test/neura/interpreter/basic_operation/fsub.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ===----------------------------------------------------------------------===// // Test 1: Valid neura.fsub with positive constants diff --git a/test/neura/interpreter/basic_operation/grant.mlir b/test/neura/interpreter/basic_operation/grant.mlir index e0e2a0f3..8d86ead3 100644 --- a/test/neura/interpreter/basic_operation/grant.mlir +++ b/test/neura/interpreter/basic_operation/grant.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_grant_predicate() -> vector<4xf32> { %val = "neura.constant"() { diff --git a/test/neura/interpreter/basic_operation/icmp.mlir b/test/neura/interpreter/basic_operation/icmp.mlir index dbc804ee..aeef3d52 100644 --- a/test/neura/interpreter/basic_operation/icmp.mlir +++ b/test/neura/interpreter/basic_operation/icmp.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ====== Equal comparison (eq) ====== // Positive case: Equal numbers diff --git a/test/neura/interpreter/basic_operation/load_store.mlir b/test/neura/interpreter/basic_operation/load_store.mlir index e60c8292..4b78c07b 100644 --- a/test/neura/interpreter/basic_operation/load_store.mlir +++ b/test/neura/interpreter/basic_operation/load_store.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_store_load_i32() -> i32 { %addr = arith.constant 0 : i32 diff --git a/test/neura/interpreter/basic_operation/load_store_index.mlir b/test/neura/interpreter/basic_operation/load_store_index.mlir index b47c801b..8804e0a9 100644 --- a/test/neura/interpreter/basic_operation/load_store_index.mlir +++ b/test/neura/interpreter/basic_operation/load_store_index.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s module { // Test case: float (f32) load/store with single index diff --git a/test/neura/interpreter/basic_operation/not.mlir b/test/neura/interpreter/basic_operation/not.mlir index a350326c..1debb472 100644 --- a/test/neura/interpreter/basic_operation/not.mlir +++ b/test/neura/interpreter/basic_operation/not.mlir @@ -1,25 +1,30 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s -// Test 1: Bitwise NOT of 42 (result should be -43) -func.func @test_not_basic() -> i32 { +func.func @test_not_nonzero() -> i32 { %a = arith.constant 42 : i32 %res = "neura.not"(%a) : (i32) -> i32 - // CHECK: [neura-interpreter] → Output: -43.000000 + // CHECK: [neura-interpreter] → Output: 0.000000 return %res : i32 } -// Test 2: Bitwise NOT of 0 (result should be -1) func.func @test_not_zero() -> i32 { %a = arith.constant 0 : i32 %res = "neura.not"(%a) : (i32) -> i32 - // CHECK: [neura-interpreter] → Output: -1.000000 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %res : i32 +} + +func.func @test_not_negative() -> i32 { + %a = arith.constant -1 : i32 + %res = "neura.not"(%a) : (i32) -> i32 + // CHECK: [neura-interpreter] → Output: 0.000000 return %res : i32 } -// Test 3: Bitwise NOT of 1 (result should be -2) func.func @test_not_one() -> i32 { %a = arith.constant 1 : i32 %res = "neura.not"(%a) : (i32) -> i32 - // CHECK: [neura-interpreter] → Output: -2.000000 + // CHECK: [neura-interpreter] → Output: 0.000000 return %res : i32 -} \ No newline at end of file +} + \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/or.mlir b/test/neura/interpreter/basic_operation/or.mlir index 13e9dede..aaf82fdd 100644 --- a/test/neura/interpreter/basic_operation/or.mlir +++ b/test/neura/interpreter/basic_operation/or.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // ====== Bitwise OR Operation Tests ====== diff --git a/test/neura/interpreter/basic_operation/phi.mlir b/test/neura/interpreter/basic_operation/phi.mlir index b1ca96be..1e3f00d9 100644 --- a/test/neura/interpreter/basic_operation/phi.mlir +++ b/test/neura/interpreter/basic_operation/phi.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s //===----------------------------------------------------------------------===// // Test 1: Basic Phi node with control flow diff --git a/test/neura/interpreter/basic_operation/reserve.mlir b/test/neura/interpreter/basic_operation/reserve.mlir index c02ff505..d3320a5f 100644 --- a/test/neura/interpreter/basic_operation/reserve.mlir +++ b/test/neura/interpreter/basic_operation/reserve.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_reserve_basic() { %a = "neura.reserve"() : () -> (i32) diff --git a/test/neura/interpreter/basic_operation/sel.mlir b/test/neura/interpreter/basic_operation/sel.mlir index e917288b..8166555e 100644 --- a/test/neura/interpreter/basic_operation/sel.mlir +++ b/test/neura/interpreter/basic_operation/sel.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_sel_with_comparison() -> f32 { %a = arith.constant 5.0 : f32 diff --git a/test/neura/interpreter/basic_operation/sub.mlir b/test/neura/interpreter/basic_operation/sub.mlir index 7a6a89c1..8a6ce158 100644 --- a/test/neura/interpreter/basic_operation/sub.mlir +++ b/test/neura/interpreter/basic_operation/sub.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s // Test basic subtraction with positive result func.func @test_sub_positive() -> i32 { diff --git a/test/neura/interpreter/basic_operation/vfmul.mlir b/test/neura/interpreter/basic_operation/vfmul.mlir index d3e63605..bf807366 100644 --- a/test/neura/interpreter/basic_operation/vfmul.mlir +++ b/test/neura/interpreter/basic_operation/vfmul.mlir @@ -1,4 +1,4 @@ -// RUN: neura-interpreter %s | FileCheck %s +// RUN: neura-interpreter %s --verbose | FileCheck %s func.func @test_vfmul_basic() -> vector<2xf32> { %a = "neura.constant"() {value = dense<[2.0, 3.0]> : vector<2xf32>} : () -> vector<2xf32> diff --git a/test/neura/interpreter/loop_dataflow.mlir b/test/neura/interpreter/loop_dataflow.mlir new file mode 100644 index 00000000..2bed1a60 --- /dev/null +++ b/test/neura/interpreter/loop_dataflow.mlir @@ -0,0 +1,38 @@ +// RUN: neura-interpreter %s --verbose --dataflow | FileCheck %s + +func.func @loop_test() -> f32 attributes {accelerator = "neura"} { + %0 = "neura.grant_once"() <{constant_value = 10 : i64}> : () -> !neura.data + %1 = "neura.grant_once"() <{constant_value = 0 : i64}> : () -> !neura.data + %2 = "neura.grant_once"() <{constant_value = 1 : i64}> : () -> !neura.data + %3 = "neura.grant_once"() <{constant_value = 3.000000e+00 : f32}> : () -> !neura.data + %4 = "neura.grant_once"() <{constant_value = 0.000000e+00 : f32}> : () -> !neura.data + %5 = "neura.reserve"() : () -> (!neura.data) + %6 = "neura.phi"(%5, %0) : (!neura.data, !neura.data) -> !neura.data + %7 = "neura.reserve"() : () -> (!neura.data) + %8 = "neura.phi"(%7, %2) : (!neura.data, !neura.data) -> !neura.data + %9 = "neura.reserve"() : () -> (!neura.data) + %10 = "neura.phi"(%9, %3) : (!neura.data, !neura.data) -> !neura.data + %11 = "neura.reserve"() : () -> (!neura.data) + %12 = "neura.phi"(%11, %4) : (!neura.data, !neura.data) -> !neura.data + %13 = "neura.reserve"() : () -> (!neura.data) + %14 = "neura.phi"(%13, %1) : (!neura.data, !neura.data) -> !neura.data + %15 = "neura.fadd"(%12, %10) : (!neura.data, !neura.data) -> !neura.data + %16 = "neura.add"(%14, %8) : (!neura.data, !neura.data) -> !neura.data + %17 = "neura.icmp"(%16, %6) <{cmpType = "slt"}> : (!neura.data, !neura.data) -> !neura.data + %18 = "neura.grant_predicate"(%16, %17) : (!neura.data, !neura.data) -> !neura.data + "neura.ctrl_mov"(%18, %13) : (!neura.data, !neura.data) -> () + %19 = "neura.grant_predicate"(%15, %17) : (!neura.data, !neura.data) -> !neura.data + "neura.ctrl_mov"(%19, %11) : (!neura.data, !neura.data) -> () + %20 = "neura.grant_predicate"(%3, %17) : (!neura.data, !neura.data) -> !neura.data + "neura.ctrl_mov"(%20, %9) : (!neura.data, !neura.data) -> () + %21 = "neura.grant_predicate"(%2, %17) : (!neura.data, !neura.data) -> !neura.data + "neura.ctrl_mov"(%21, %7) : (!neura.data, !neura.data) -> () + %22 = "neura.grant_predicate"(%0, %17) : (!neura.data, !neura.data) -> !neura.data + "neura.ctrl_mov"(%22, %5) : (!neura.data, !neura.data) -> () + %23 = "neura.not"(%17) : (!neura.data) -> !neura.data + %24 = "neura.grant_predicate"(%15, %23) : (!neura.data, !neura.data) -> !neura.data + + // CHECK: [neura-interpreter] │ └─30.000000, [pred = 1] + + "neura.return"(%24) : (!neura.data) -> () +} \ No newline at end of file diff --git a/test/neura/interpreter/lower_and_interpret.mlir b/test/neura/interpreter/lower_and_interpret.mlir index 9d50c317..56bf20b8 100644 --- a/test/neura/interpreter/lower_and_interpret.mlir +++ b/test/neura/interpreter/lower_and_interpret.mlir @@ -31,8 +31,8 @@ module { %arg0 = arith.constant 9.0 : f32 %cst = arith.constant 2.0 : f32 %0 = arith.addf %arg0, %cst : f32 - // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] - // CHECK: [neura-interpreter] → Output: [[OUTPUT]] + // // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] + // // CHECK: [neura-interpreter] → Output: [[OUTPUT]] return %0 : f32 } } diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index b4699ca1..c6760565 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -24,7 +24,20 @@ using namespace mlir; +static llvm::DenseMap> valueUsers; +static llvm::SmallVector worklist; +static llvm::DenseMap inWorklist; + static bool verbose = false; +static bool dataflow = false; + +inline void setDataflowMode(bool v) { + dataflow = v; +} + +inline bool isDataflowMode() { + return dataflow; +} inline void setVerboseMode(bool v) { verbose = v; @@ -34,6 +47,56 @@ inline bool isVerboseMode() { return verbose; } +void buildDependencyGraph(ModuleOp module) { + valueUsers.clear(); + + module.walk([&](Operation* op) { + if (isa(op) || isa(op)) { + return; + } + + if (isa(op) || + isa(op) || + isa(op) || + isa(op) || + isa(op)) { + return; + } + + for (Value operand : op->getOperands()) { + valueUsers[operand].insert(op); + } + }); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Dependency Graph:\n"; + for (auto& entry : valueUsers) { + llvm::outs() << "[neura-interpreter] Value: "; + entry.first.print(llvm::outs()); + llvm::outs() << " -> Users: "; + for (auto* userOp : entry.second) { + llvm::outs() << userOp->getName() << ", "; + } + llvm::outs() << "\n"; + } + } +} + + +void addToWorklist(Operation* op) { + if (op == nullptr) + return; + if (inWorklist.lookup(op)) + return; + + worklist.push_back(op); + inWorklist[op] = true; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Worklist Added: " << op->getName() << "\n"; + } +} + class Memory { public: Memory(size_t size) : mem(size, 0) { @@ -173,6 +236,7 @@ struct PredicatedData { bool isVector; std::vector vectorData; bool isReserve; + bool isUpdated; }; bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& valueMap) { @@ -986,7 +1050,6 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue << ", [pred = " << finalPredicate << "]\n"; } - valueMap[op.getResult()] = result; return true; @@ -1059,10 +1122,6 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &valueMap) } bool handleNotOp(neura::NotOp op, llvm::DenseMap &valueMap) { - // if (op.getNumOperands() != 1) { - // llvm::errs() << "[neura-interpreter] neura.not expects exactly one operand\n"; - // return false; - // } auto input = valueMap[op.getOperand()]; @@ -1074,11 +1133,11 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &valueMa } int64_t inputInt = static_cast(std::round(input.value)); - int64_t resultInt = ~inputInt; + int64_t resultInt = !inputInt; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; - llvm::outs() << "[neura-interpreter]] │ └─ Bitwise NOT : ~" << inputInt; + llvm::outs() << "[neura-interpreter] │ └─ Logical NOT : !" << inputInt; if (inputInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; llvm::outs() << " = " << resultInt; if (resultInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; @@ -1282,12 +1341,6 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value auto addrVal = valueMap[op.getOperand(0)]; bool finalPredicate = addrVal.predicate; - // if(isVerboseMode()) { - // llvm::outs() << "[neura-interpreter] ├─ Operand\n"; - // llvm::outs() << "[neura-interpreter] │ └─ Address : value = " << addrVal.value - // << ", [pred = " << addrVal.predicate << "]\n"; - // } - if (op.getNumOperands() > 1) { auto predVal = valueMap[op.getOperand(1)]; finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); @@ -1670,13 +1723,6 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &valueMap, llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; } - // auto parentFunc = destBlock->getParentOp(); - // unsigned blockIndex = 0; - // for (auto &block : parentFunc->getRegion(0)) { - // if (&block == destBlock) break; - // blockIndex++; - // } - const auto &args = op.getArgs(); const auto &destParams = destBlock->getArguments(); @@ -1766,8 +1812,6 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } } - // llvm::outs() << "[neura-interpreter] ├─ Current block: Block@" << currentBlock << "\n"; - auto currentSuccsRange = currentBlock->getSuccessors(); std::vector succBlocks(currentSuccsRange.begin(), currentSuccsRange.end()); @@ -1792,13 +1836,6 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val Block *targetBlock = isTrueBranch ? op.getTrueDest() : op.getFalseDest(); const auto &branchArgs = isTrueBranch ? op.getTrueArgs() : op.getFalseArgs(); const auto &targetParams = targetBlock->getArguments(); - - // auto parentFunc = targetBlock->getParentOp(); - // unsigned blockIndex = 0; - // for (auto &block : parentFunc->getRegion(0)) { - // if (&block == targetBlock) break; - // blockIndex++; - // } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; @@ -1845,7 +1882,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val return true; } -bool handlePhiOp(neura::PhiOp op, llvm::DenseMap &valueMap, +bool handlePhiOpControlFlowMode(neura::PhiOp op, llvm::DenseMap &valueMap, Block *currentBlock, Block *lastVisitedBlock) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; @@ -1929,6 +1966,112 @@ bool handlePhiOp(neura::PhiOp op, llvm::DenseMap &valueMa return true; } +bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.phi(dataflow):\n"; + } + + auto inputs = op.getInputs(); + size_t inputCount = inputs.size(); + + if (inputCount == 0) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; + } + return false; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Input values (" << inputCount << ")\n"; + for (size_t i = 0; i < inputCount; ++i) { + Value input = inputs[i]; + if (valueMap.count(input)) { + auto inputData = valueMap[input]; + const std::string prefix = (i < inputCount - 1) ? "│ ├─" : "│ └─"; + llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]:" + << "value = " << inputData.value << "," + << "pred = " << inputData.predicate << "\n"; + } else { + const std::string prefix = (i < inputCount - 1) ? "│ ├─" : "│ └─"; + llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]: \n"; + } + } + } + + PredicatedData result; + result.value = 0.0f; + result.predicate = false; + result.isVector = false; + result.vectorData = {}; + result.isReserve = false; + result.isUpdated = false; + bool foundValidInput = false; + + for (size_t i = 0; i < inputCount; ++i) { + Value input = inputs[i]; + + if (!valueMap.count(input)) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Input [" << i << "] not found, skipping\n"; + } + continue; + } + + auto inputData = valueMap[input]; + + if (inputData.predicate && !foundValidInput) { + result.value = inputData.value; + result.predicate = inputData.predicate; + result.isVector = inputData.isVector; + result.vectorData = inputData.vectorData; + foundValidInput = true; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Selected input [" << i << "] (latest valid)\n"; + } + break; + } + } + + if (!foundValidInput && inputCount > 0) { + Value firstInput = inputs[0]; + if (valueMap.count(firstInput)) { + auto firstData = valueMap[firstInput]; + result.value = firstData.value; + result.isVector = firstData.isVector; + result.vectorData = firstData.vectorData; + result.predicate = false; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ No valid input, using first input with pred=false\n"; + } + } + } + + bool fieldsChanged = false; + if (valueMap.count(op.getResult())) { + auto oldResult = valueMap[op.getResult()]; + fieldsChanged = (result.value != oldResult.value) || + (result.predicate != oldResult.predicate) || + (result.isVector != oldResult.isVector) || + (result.vectorData != oldResult.vectorData); + } else { + fieldsChanged = true; + } + + result.isUpdated = fieldsChanged; + valueMap[op.getResult()] = result; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Execution " << (foundValidInput ? "succeeded" : "partially succeeded") + << " | Result: value = " << result.value + << ", pred = " << result.predicate + << ", isUpdated = " << result.isUpdated << "\n"; + } + + return true; +} + + bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &valueMap) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.reserve:\n"; @@ -2011,65 +2154,209 @@ bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap return true; } -bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &valueMap) { +bool handleCtrlMovOpDataFlowMode_(neura::CtrlMovOp op, + llvm::DenseMap &valueMap) { if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.return:\n"; + llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov(dataflow):\n"; } - std::vector returnValues; - for (Value val : op.getValues()) { - if (!valueMap.count(val)) { - llvm::errs() << "[neura-interpreter] └─ neura.return: Return value not found in value map\n"; - return false; + Value source = op.getValue(); + Value target = op.getTarget(); + + if (!valueMap.count(source)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map (execution failed)\n"; } - returnValues.push_back(valueMap[val]); + return false; + } + + if (!valueMap.count(target) || !valueMap[target].isReserve) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder (execution failed)\n"; + } + return false; + } + + const auto &sourceData = valueMap[source]; + auto &targetData = valueMap[target]; + + float oldValue = targetData.value; + bool oldPredicate = targetData.predicate; + bool oldIsVector = targetData.isVector; + + targetData.value = sourceData.value; + targetData.predicate = sourceData.predicate; + targetData.isVector = sourceData.isVector; + + bool isTerminated = false; + if (!isTerminated) { + targetData.value = sourceData.value; + targetData.predicate = sourceData.predicate; + targetData.isVector = sourceData.isVector; + targetData.vectorData = sourceData.isVector ? sourceData.vectorData : std::vector(); + } + + if (sourceData.isVector) { + targetData.vectorData = sourceData.vectorData; + } else { + targetData.vectorData.clear(); + } + + std::vector oldVectorData = targetData.vectorData; + + bool isScalarUpdated = false; + bool isVectorUpdated = false; + bool isTypeUpdated = false; + + if (!isTerminated) { + if (!targetData.isVector) { + isScalarUpdated = (targetData.value != oldValue) || + (targetData.predicate != oldPredicate) || + !oldVectorData.empty(); + } + if (targetData.isVector) { + isVectorUpdated = (targetData.predicate != oldPredicate) || + (targetData.vectorData != oldVectorData); + } + isTypeUpdated = (targetData.isVector != oldIsVector); + targetData.isUpdated = isScalarUpdated || isVectorUpdated || isTypeUpdated; + } else { + targetData.isUpdated = false; } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Return values:"; + llvm::outs() << "[neura-interpreter] ├─ Source: " << sourceData.value << " | " << sourceData.predicate << "\n"; + llvm::outs() << "[neura-interpreter] ├─ Target (after update): " << targetData.value << " | " << targetData.predicate << " | isUpdated=" << targetData.isUpdated << "\n"; + llvm::outs() << "[neura-interpreter] └─ Execution succeeded (data copied)\n"; } - if (returnValues.empty()) { + return true; +} + +bool handleCtrlMovOpDataFlowMode(neura::CtrlMovOp op, + llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov(dataflow):\n"; + } + + Value source = op.getValue(); + Value target = op.getTarget(); + + if (!valueMap.count(source)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map (execution failed)\n"; + } + return false; + } + + if (!valueMap.count(target) || !valueMap[target].isReserve) { if (isVerboseMode()) { - llvm::outs() << " void\n"; + llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder (execution failed)\n"; } + return false; + } + + const auto &sourceData = valueMap[source]; + auto &targetData = valueMap[target]; + + float oldValue = targetData.value; + bool oldPredicate = targetData.predicate; + bool oldIsVector = targetData.isVector; + std::vector oldVectorData = targetData.vectorData; + + bool isTerminated = false; + bool isScalarUpdated = false; + bool isVectorUpdated = false; + bool isTypeUpdated = false; + targetData.isUpdated = false; + + bool shouldUpdate = !isTerminated && (sourceData.predicate == 1); + if (shouldUpdate) { + targetData.value = sourceData.value; + targetData.predicate = sourceData.predicate; + targetData.isVector = sourceData.isVector; + + if (sourceData.isVector) { + targetData.vectorData = sourceData.vectorData; + } else { + targetData.vectorData.clear(); + } + + if (!targetData.isVector) { + isScalarUpdated = (targetData.value != oldValue) || + (targetData.predicate != oldPredicate) || + !oldVectorData.empty(); + } + if (targetData.isVector) { + isVectorUpdated = (targetData.predicate != oldPredicate) || + (targetData.vectorData != oldVectorData); + } + isTypeUpdated = (targetData.isVector != oldIsVector); + targetData.isUpdated = isScalarUpdated || isVectorUpdated || isTypeUpdated; } else { - llvm::outs() << "\n"; - for (size_t i = 0; i < returnValues.size(); ++i) { - const auto &data = returnValues[i]; - if (isVerboseMode()) { - llvm::outs() << " [" << i << "]: "; + if (isVerboseMode()) { + if (isTerminated) { + llvm::outs() << "[neura-interpreter] ├─ Skip update: Loop terminated\n"; + } else { + llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate is invalid (pred=" << sourceData.predicate << ")\n"; } + } + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Source: " << sourceData.value << " | " << sourceData.predicate << "\n"; + llvm::outs() << "[neura-interpreter] ├─ Target (after update): " << targetData.value << " | " << targetData.predicate << " | isUpdated=" << targetData.isUpdated << "\n"; + llvm::outs() << "[neura-interpreter] └─ Execution succeeded (update controlled by predicate and loop status)\n"; + } + + return true; +} + + +bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.return:\n"; + } + + auto returnValues = op.getValues(); + if (returnValues.empty()) { + llvm::outs() << "[neura-interpreter] → Output: (void)\n"; + return true; + } + + std::vector results; + for (Value val : returnValues) { + if (!valueMap.count(val)) { + llvm::errs() << "[neura-interpreter] └─ Return value not found in value map\n"; + return false; + } + results.push_back(valueMap[val]); + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Return values:\n"; + for (size_t i = 0; i < results.size(); ++i) { + const auto &data = results[i]; + // llvm::outs() << "[neura-interpreter] [" << i << "]: "; if (data.isVector) { - if (isVerboseMode()) { - llvm::outs() << "vector=["; - } - - for (size_t j = 0; j < data.vectorData.size() && isVerboseMode(); ++j) { + llvm::outs() << "[neura-interpreter] │ └─ vector = ["; + for (size_t j = 0; j < data.vectorData.size(); ++j) { float val = data.predicate ? data.vectorData[j] : 0.0f; llvm::outs() << llvm::format("%.6f", val); - if (j != data.vectorData.size() - 1) llvm::outs() << ", "; - } - if (isVerboseMode()) { - llvm::outs() << "]"; + if (j != data.vectorData.size() - 1) + llvm::outs() << ", "; } + llvm::outs() << "]"; } else { float val = data.predicate ? data.value : 0.0f; - if (isVerboseMode()) { - llvm::outs() << llvm::format("%.6f", val); - } - } - if (isVerboseMode()) { - llvm::outs() << ", [pred = " << data.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─" << llvm::format("%.6f", val); } + llvm::outs() << ", [pred = " << data.predicate << "]\n"; } - } - - if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Execution terminated successfully\n"; } - + return true; } @@ -2085,6 +2372,13 @@ bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMapgetNumOperands() != 1) { + bool hasValue = op.getValue() != nullptr; + bool hasConstant = op.getConstantValue().has_value(); + + if (hasValue == hasConstant) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.grant_once expects exactly 1 operand (value)\n"; + llvm::errs() << "[neura-interpreter] └─ grant_once requires exactly one of (value operand, constant_value attribute)\n"; } return false; } - auto source = valueMap[op.getValue()]; - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Operand\n"; - llvm::outs() << "[neura-interpreter] │ └─ Source value: " << source.value << ", [pred = " << source.predicate << "]\n"; + PredicatedData source; + if (hasValue) { + Value inputValue = op.getValue(); + if (!valueMap.count(inputValue)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Source value not found in valueMap\n"; + } + return false; + } + source = valueMap[inputValue]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Source value: " << source.value << ", [pred = " << source.predicate << "]\n"; + } + } else { + Attribute constantAttr = op.getConstantValue().value(); + // 使用全局的mlir::dyn_cast替代成员函数dyn_cast + if (auto intAttr = mlir::dyn_cast(constantAttr)) { + source.value = intAttr.getInt(); + source.predicate = false; + source.isVector = false; + // 使用全局的mlir::dyn_cast替代成员函数dyn_cast + } else if (auto floatAttr = mlir::dyn_cast(constantAttr)) { + source.value = floatAttr.getValueAsDouble(); + source.predicate = false; + source.isVector = false; + } else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported constant_value type\n"; + } + return false; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Constant value: " << source.value << "\n"; + } } - - - static llvm::DenseMap granted; - bool hasGranted = granted[op.getValue()]; + static llvm::DenseMap granted; + bool hasGranted = granted[op.getOperation()]; bool resultPredicate = !hasGranted; + if (!hasGranted) { - granted[op.getValue()] = true; + granted[op.getOperation()] = true; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ First access - granting predicate\n"; } @@ -2144,11 +2475,11 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMapprint(llvm::errs()); + llvm::errs() << "\n"; + executionSuccess = false; + } + + if (!executionSuccess) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Operation failed, no propagation\n"; + } + return; + } + + bool hasUpdate = false; + for (Value result : op->getResults()) { + if (!valueMap.count(result)) continue; + PredicatedData newData = valueMap[result]; + + if (!oldValues.count(result)) { + newData.isUpdated = true; + valueMap[result] = newData; + hasUpdate = true; + continue; + } + + PredicatedData oldData = oldValues[result]; + if (isDataUpdated(oldData, newData)) { + newData.isUpdated = true; + valueMap[result] = newData; + hasUpdate = true; + } else { + newData.isUpdated = false; + valueMap[result] = newData; + } + } + + if (op->getNumResults() == 0) { + if (auto ctrlMovOp = dyn_cast(op)) { + Value target = ctrlMovOp.getTarget(); + if (valueMap.count(target) && valueMap[target].isUpdated) { + hasUpdate = true; + } else { + llvm::outs() << "[neura-interpreter] No update for ctrl_mov target: " << target << "\n"; + } + } + } + + if (hasUpdate) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; + } + + llvm::SmallVector affectedValues; + for (Value result : op->getResults()) { + if (valueMap.count(result) && valueMap[result].isUpdated) { + affectedValues.push_back(result); + } + } + + if (op->getNumResults() == 0) { + if (auto ctrlMovOp = dyn_cast(op)) { + Value target = ctrlMovOp.getTarget(); + if (valueMap.count(target) && valueMap[target].isUpdated) { + affectedValues.push_back(target); + } + } + } + + for (Value val : affectedValues) { + if (!valueUsers.count(val)) continue; + for (Operation* userOp : valueUsers[val]) { + if (!inWorklist[userOp]) { + nextWorklist.push_back(userOp); + inWorklist[userOp] = true; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Added user to next worklist: "; + userOp->print(llvm::outs()); + llvm::outs() << "\n"; + } + } + } + } + } +} + +void initializeStaticOps(func::FuncOp func, + llvm::DenseMap& valueMap, + Memory& mem, + llvm::SmallVector& nextWorklist) { + for (auto& block : func.getBody()) { + for (auto& op : block.getOperations()) { + if (isa(op) || + isa(op) || + // isa(op) || + isa(op)) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Init phase: executing "; + op.print(llvm::outs()); + llvm::outs() << "\n"; + } + + executeOperation(&op, valueMap, mem, nextWorklist); + } + } + } +} + +int runDataFlowMode_(func::FuncOp func, + llvm::DenseMap& valueMap, + Memory& mem) { + + llvm::SmallVector nextWorklist; + initializeStaticOps(func, valueMap, mem, nextWorklist); + worklist.clear(); + inWorklist.clear(); + llvm::SmallVector delayQueue; + + for (auto& block : func.getBody()) { + for (auto& op : block.getOperations()) { + if (isa(op) || isa(op)) { + delayQueue.push_back(&op); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Return op added to delay queue: "; + op.print(llvm::outs()); + llvm::outs() << "\n"; + } + } else if (!(isa(op) || isa(op) || + isa(op) || isa(op))) { + worklist.push_back(&op); + inWorklist[&op] = true; + } + } + } + + int iterCount = 0; + while (!worklist.empty() || !delayQueue.empty()) { + iterCount++; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Iteration " << iterCount + << " | worklist: " << worklist.size() + << " | delayQueue: " << delayQueue.size() << "\n"; + } + + for (Operation* op : worklist) { + inWorklist[op] = false; + executeOperation(op, valueMap, mem, nextWorklist); + } + + bool canExecuteReturn = true; + if (!delayQueue.empty()) { + Operation* returnOp = delayQueue[0]; + for (Value input : returnOp->getOperands()) { + if (!valueMap.count(input) || !valueMap[input].predicate) { + canExecuteReturn = false; + break; + } + } + } + + if (canExecuteReturn && !delayQueue.empty()) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] All return inputs are valid, executing return\n"; + } + for (Operation* returnOp : delayQueue) { + nextWorklist.push_back(returnOp); + inWorklist[returnOp] = true; + } + delayQueue.clear(); + } else { + if (isVerboseMode() && !delayQueue.empty()) { + llvm::outs() << "[neura-interpreter] Some return inputs are invalid, keeping return in delay queue\n"; + } + } + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter]" << + " | next work: " << nextWorklist.size() << "\n"; + } + worklist = std::move(nextWorklist); + + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Total iterations: " << iterCount << "\n"; + } + return 0; +} + +int runDataFlowMode(func::FuncOp func, + llvm::DenseMap& valueMap, + Memory& mem) { + llvm::SmallVector delayQueue; + + for (auto& block : func.getBody()) { + for (auto& op : block.getOperations()) { + if (isa(op) || isa(op)) { + delayQueue.push_back(&op); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Return op added to delay queue: "; + op.print(llvm::outs()); + llvm::outs() << "\n"; + } + } else { + worklist.push_back(&op); + inWorklist[&op] = true; + } + } + } + + int iterCount = 0; + while (!worklist.empty() || !delayQueue.empty()) { + iterCount++; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Iteration " << iterCount + << " | worklist: " << worklist.size() + << " | delayQueue: " << delayQueue.size() << "\n"; + } + + llvm::SmallVector nextWorklist; + + for (Operation* op : worklist) { + inWorklist[op] = false; + executeOperation(op, valueMap, mem, nextWorklist); + } + + if (!delayQueue.empty()) { + Operation* returnOp = delayQueue[0]; + bool canExecuteReturn = true; + for (Value input : returnOp->getOperands()) { + if (!valueMap.count(input) || !valueMap[input].predicate) { + canExecuteReturn = false; + break; + } + } + + if (canExecuteReturn) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] All return inputs are valid, executing return\n"; + } + for (Operation* op : delayQueue) { + nextWorklist.push_back(op); + inWorklist[op] = true; + } + delayQueue.clear(); + } else { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Some return inputs are invalid, keeping return in delay queue\n"; + } + } + } + + worklist = std::move(nextWorklist); + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Total iterations: " << iterCount << "\n"; + } + + return 0; +} + + +int runControlFlowMode(func::FuncOp func, + llvm::DenseMap& valueMap, + Memory& mem) { + Block* currentBlock = &func.getBody().front(); + Block* lastVisitedBlock = nullptr; + size_t opIndex = 0; + bool isTerminated = false; + + while (!isTerminated && currentBlock) { + auto& operations = currentBlock->getOperations(); + + if (opIndex >= operations.size()) { + break; + } + + Operation& op = *std::next(operations.begin(), opIndex); + + if (auto constOp = dyn_cast(op)) { + if (!handleArithConstantOp(constOp, valueMap)) return 1; + ++opIndex; + } else if (auto constOp = dyn_cast(op)) { + if (!handleNeuraConstantOp(constOp, valueMap)) return 1; + ++opIndex; + } else if (auto movOp = dyn_cast(op)) { + valueMap[movOp.getResult()] = valueMap[movOp.getOperand()]; + ++opIndex; + } else if (auto addOp = dyn_cast(op)) { + if (!handleAddOp(addOp, valueMap)) return 1; + ++opIndex; + } else if (auto subOp = dyn_cast(op)) { + if (!handleSubOp(subOp, valueMap)) return 1; + ++opIndex; + } else if (auto faddOp = dyn_cast(op)) { + if (!handleFAddOp(faddOp, valueMap)) return 1; + ++opIndex; + } else if (auto fsubOp = dyn_cast(op)) { + if (!handleFSubOp(fsubOp, valueMap)) return 1; + ++opIndex; + } else if (auto fmulOp = dyn_cast(op)) { + if (!handleFMulOp(fmulOp, valueMap)) return 1; + ++opIndex; + } else if (auto fdivOp = dyn_cast(op)) { + if (!handleFDivOp(fdivOp, valueMap)) return 1; + ++opIndex; + } else if (auto vfmulOp = dyn_cast(op)) { + if (!handleVFMulOp(vfmulOp, valueMap)) return 1; + opIndex++; + } else if (auto faddFaddOp = dyn_cast(op)) { + if (!handleFAddFAddOp(faddFaddOp, valueMap)) return 1; + ++opIndex; + } else if (auto fmulFaddOp = dyn_cast(op)) { + if (!handleFMulFAddOp(fmulFaddOp, valueMap)) return 1; + ++opIndex; + } else if (auto retOp = dyn_cast(op)) { + if (!handleFuncReturnOp(retOp, valueMap)) return 1; + isTerminated = true; + ++opIndex; + } else if (auto fcmpOp = dyn_cast(op)) { + if (!handleFCmpOp(fcmpOp, valueMap)) return 1; + ++opIndex; + } else if (auto icmpOp = dyn_cast(op)) { + if (!handleICmpOp(icmpOp, valueMap)) return 1; + ++opIndex; + } else if (auto orOp = dyn_cast(op)) { + if (!handleOrOp(orOp, valueMap)) return 1; + ++opIndex; + } else if (auto notOp = dyn_cast(op)) { + if (!handleNotOp(notOp, valueMap)) return 1; + ++opIndex; + } else if (auto selOp = dyn_cast(op)) { + if (!handleSelOp(selOp, valueMap)) return 1; + ++opIndex; + } else if (auto castOp = dyn_cast(op)) { + if (!handleCastOp(castOp, valueMap)) return 1; + ++opIndex; + } else if (auto loadOp = dyn_cast(op)) { + if (!handleLoadOp(loadOp, valueMap, mem)) return 1; + ++opIndex; + } else if (auto storeOp = dyn_cast(op)) { + if (!handleStoreOp(storeOp, valueMap, mem)) return 1; + ++opIndex; + } else if (auto gepOp = dyn_cast(op)) { + if (!handleGEPOp(gepOp, valueMap)) return 1; + ++opIndex; + } else if (auto loadIndexOp = dyn_cast(op)) { + if (!handleLoadIndexedOp(loadIndexOp, valueMap, mem)) return 1; + ++opIndex; + } else if (auto storeIndexOp = dyn_cast(op)) { + if (!handleStoreIndexedOp(storeIndexOp, valueMap, mem)) return 1; + ++opIndex; + } else if (auto brOp = dyn_cast(op)) { + if (!handleBrOp(brOp, valueMap, currentBlock, lastVisitedBlock)) return 1; + opIndex = 0; + } else if (auto condBrOp = dyn_cast(op)) { + if (!handleCondBrOp(condBrOp, valueMap, currentBlock, lastVisitedBlock)) return 1; + opIndex = 0; + } else if (auto phiOp = dyn_cast(op)) { + if (!handlePhiOpControlFlowMode(phiOp, valueMap, currentBlock, lastVisitedBlock)) return 1; + ++opIndex; + } else if (auto reserveOp = dyn_cast(op)) { + if (!handleReserveOp(reserveOp, valueMap)) return 1; + ++opIndex; + } else if (auto ctrlMovOp = dyn_cast(op)) { + if (!handleCtrlMovOp(ctrlMovOp, valueMap)) return 1; + ++opIndex; + } else if (auto returnOp = dyn_cast(op)) { + if (!handleNeuraReturnOp(returnOp, valueMap)) return 1; + isTerminated = true; + ++opIndex; + } else if (auto grantPredOp = dyn_cast(op)) { + if (!handleGrantPredicateOp(grantPredOp, valueMap)) return 1; + ++opIndex; + } else if (auto grantOnceOp = dyn_cast(op)) { + if (!handleGrantOnceOp(grantOnceOp, valueMap)) return 1; + ++opIndex; + } else if (auto grantAlwaysOp = dyn_cast(op)) { + if (!handleGrantAlwaysOp(grantAlwaysOp, valueMap)) return 1; + ++opIndex; + } else { + llvm::errs() << "[neura-interpreter] Unhandled op: "; + op.print(llvm::errs()); + llvm::errs() << "\n"; + return 1; + } + } + + return 0; +} + int main(int argc, char **argv) { for (int i = 0; i < argc; ++i) { if (std::string(argv[i]) == "--verbose") { setVerboseMode(true); + } else if (std::string(argv[i]) == "--dataflow") { + setDataflowMode(true); } } @@ -2229,128 +3039,18 @@ int main(int argc, char **argv) { Memory mem(1024); // 1MB - Block *currentBlock = nullptr; - Block *lastVisitedBlock = nullptr; - size_t opIndex = 0; - bool isTerminated = false; - - for (auto func : module->getOps()) { - currentBlock = &func.getBody().front(); - opIndex = 0; - isTerminated = false; - - while(!isTerminated && currentBlock) { - auto& operations = currentBlock->getOperations(); - if(opIndex >= operations.size()) { - break; + if (isDataflowMode()) { + buildDependencyGraph(*module); + for (auto func : module->getOps()) { + if (runDataFlowMode(func, valueMap, mem)) { + llvm::errs() << "[neura-interpreter] Data Flow execution failed\n"; + return 1; } - - Operation &op = *std::next(operations.begin(), opIndex); - if (auto constOp = dyn_cast(op)) { - if(!handleArithConstantOp(constOp, valueMap)) return 1; - ++opIndex; - } else if (auto constOp = dyn_cast(op)) { - if(!handleNeuraConstantOp(constOp, valueMap)) return 1; - ++opIndex; - } else if (auto movOp = dyn_cast(op)) { - valueMap[movOp.getResult()] = valueMap[movOp.getOperand()]; - ++opIndex; - } else if (auto addOp = dyn_cast(op)) { - if(!handleAddOp(addOp, valueMap)) return 1; - ++opIndex; - } else if (auto subOp = dyn_cast(op)) { - if(!handleSubOp(subOp, valueMap)) return 1; - ++opIndex; - } else if (auto faddOp = dyn_cast(op)) { - if(!handleFAddOp(faddOp, valueMap)) return 1; - ++opIndex; - } else if (auto fsubOp = dyn_cast(op)) { - if(!handleFSubOp(fsubOp, valueMap)) return 1; - ++opIndex; - } else if (auto fmulOp = dyn_cast(op)) { - if(!handleFMulOp(fmulOp, valueMap)) return 1; - ++opIndex; - } else if (auto fdivOp = dyn_cast(op)) { - if(!handleFDivOp(fdivOp, valueMap)) return 1; - ++opIndex; - } else if (auto vfmulOp = dyn_cast(op)) { - if(!handleVFMulOp(vfmulOp, valueMap)) return 1; - opIndex++; - } else if (auto faddFaddOp = dyn_cast(op)) { - if(!handleFAddFAddOp(faddFaddOp, valueMap)) return 1; - ++opIndex; - } else if (auto fmulFaddOp = dyn_cast(op)) { - if(!handleFMulFAddOp(fmulFaddOp, valueMap)) return 1; - ++opIndex; - } else if (auto retOp = dyn_cast(op)) { - if(!handleFuncReturnOp(retOp, valueMap)) return 1; - isTerminated = true; - ++opIndex; - } else if (auto fcmpOp = dyn_cast(op)) { - if(!handleFCmpOp(fcmpOp, valueMap)) return 1; - ++opIndex; - } else if (auto icmpOp = dyn_cast(op)) { - if(!handleICmpOp(icmpOp, valueMap)) return 1; - ++opIndex; - } else if (auto orOp = dyn_cast(op)) { - if(!handleOrOp(orOp, valueMap)) return 1; - ++opIndex; - } else if (auto notOp = dyn_cast(op)) { - if(!handleNotOp(notOp, valueMap)) return 1; - ++opIndex; - } else if (auto selOp = dyn_cast(op)) { - if(!handleSelOp(selOp, valueMap)) return 1; - ++opIndex; - } else if (auto castOp = dyn_cast(op)) { - if(!handleCastOp(castOp, valueMap)) return 1; - ++opIndex; - } else if (auto loadOp = dyn_cast(op)) { - if(!handleLoadOp(loadOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto storeOp = dyn_cast(op)) { - if(!handleStoreOp(storeOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto gepOp = dyn_cast(op)) { - if(!handleGEPOp(gepOp, valueMap)) return 1; - ++opIndex; - } else if (auto loadIndexOp = dyn_cast(op)) { - if(!handleLoadIndexedOp(loadIndexOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto storeIndexOp = dyn_cast(op)) { - if(!handleStoreIndexedOp(storeIndexOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto brOp = dyn_cast(op)) { - if(!handleBrOp(brOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - opIndex = 0; - } else if (auto condBrOp = dyn_cast(op)) { - if(!handleCondBrOp(condBrOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - opIndex = 0; - } else if (auto phiOp = dyn_cast(op)) { - if(!handlePhiOp(phiOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - ++opIndex; - } else if (auto reserveOp = dyn_cast(op)) { - if(!handleReserveOp(reserveOp, valueMap)) return 1; - ++opIndex; - } else if (auto ctrlMovOp = dyn_cast(op)) { - if(!handleCtrlMovOp(ctrlMovOp, valueMap)) return 1; - ++opIndex; - } else if (auto returnOp = dyn_cast(op)) { - if(!handleNeuraReturnOp(returnOp, valueMap)) return 1; - isTerminated = true; - ++opIndex; - } else if (auto grantPredOp = dyn_cast(op)) { - if(!handleGrantPredicateOp(grantPredOp, valueMap)) return 1; - ++opIndex; - } else if (auto grantOnceOp = dyn_cast(op)) { - if(!handleGrantOnceOp(grantOnceOp, valueMap)) return 1; - ++opIndex; - } else if (auto grantAlwaysOp = dyn_cast(op)) { - if(!handleGrantAlwaysOp(grantAlwaysOp, valueMap)) return 1; - ++opIndex; - } else { - llvm::errs() << "[neura-interpreter] Unhandled op: "; - op.print(llvm::errs()); - llvm::errs() << "\n"; + } + } else { + for (auto func : module->getOps()) { + if (runControlFlowMode(func, valueMap, mem)) { + llvm::errs() << "[neura-interpreter] Control Flow execution failed\n"; return 1; } } From 30c0c82e8d3f0f68bbfae07eaf614c8997e15045 Mon Sep 17 00:00:00 2001 From: item Date: Thu, 7 Aug 2025 22:04:51 +0800 Subject: [PATCH 2/6] add interpreter dataflow mode --- .../neura/interpreter/basic_operation/or.mlir | 43 +- tools/neura-interpreter/neura-interpreter.cpp | 3150 ++++++++++------- 2 files changed, 1830 insertions(+), 1363 deletions(-) diff --git a/test/neura/interpreter/basic_operation/or.mlir b/test/neura/interpreter/basic_operation/or.mlir index aaf82fdd..0e776af6 100644 --- a/test/neura/interpreter/basic_operation/or.mlir +++ b/test/neura/interpreter/basic_operation/or.mlir @@ -1,38 +1,39 @@ // RUN: neura-interpreter %s --verbose | FileCheck %s -// ====== Bitwise OR Operation Tests ====== +// ====== Logical OR Operation Tests ====== -// Case 1: 42 | 5 = 47 -func.func @test_or_basic() -> i32 { - %a = arith.constant 42 : i32 - %b = arith.constant 5 : i32 +// Case 1: true | true = true (1 || 1 = 1) +func.func @test_or_basic_true_true() -> i32 { +%a = arith.constant 1 : i32 // true (non-zero) + %b = arith.constant 1 : i32 // true (non-zero) %res = "neura.or"(%a, %b) : (i32, i32) -> i32 - // CHECK: [neura-interpreter] → Output: 47.000000 + // CHECK: [neura-interpreter] → Output: 1.000000 return %res : i32 } -// Case 2: OR with zero, should return original number -func.func @test_or_with_zero() -> i32 { - %a = arith.constant 123 : i32 - %b = arith.constant 0 : i32 +// Case 2: true | false = true (1 || 0 = 1) +func.func @test_or_true_false() -> i32 { + %a = arith.constant 1 : i32 // true + %b = arith.constant 0 : i32 // false %res = "neura.or"(%a, %b) : (i32, i32) -> i32 - // CHECK: [neura-interpreter] → Output: 123.000000 + // CHECK: [neura-interpreter] → Output: 1.000000 return %res : i32 } -// Case 3: Self OR, result should equal input -func.func @test_or_self() -> i32 { - %a = arith.constant 77 : i32 - %res = "neura.or"(%a, %a) : (i32, i32) -> i32 - // CHECK: [neura-interpreter] → Output: 77.000000 +// Case 3: false | true = true (0 || 5 = 1) +func.func @test_or_false_true() -> i32 { + %a = arith.constant 0 : i32 // false + %b = arith.constant 5 : i32 // true (non-zero) + %res = "neura.or"(%a, %b) : (i32, i32) -> i32 + // CHECK: [neura-interpreter] → Output: 1.000000 return %res : i32 } -// Case 4: OR with -1 (all bits set), should return -1 -func.func @test_or_with_minus_one() -> i32 { - %a = arith.constant 123 : i32 - %b = arith.constant -1 : i32 +// Case 4: false | false = false (0 || 0 = 0) +func.func @test_or_false_false() -> i32 { + %a = arith.constant 0 : i32 // false + %b = arith.constant 0 : i32 // false %res = "neura.or"(%a, %b) : (i32, i32) -> i32 - // CHECK: [neura-interpreter] → Output: -1.000000 + // CHECK: [neura-interpreter] → Output: 0.000000 return %res : i32 } \ No newline at end of file diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index c6760565..c462ac0d 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -24,12 +24,182 @@ using namespace mlir; -static llvm::DenseMap> valueUsers; -static llvm::SmallVector worklist; -static llvm::DenseMap inWorklist; +/** + * @brief Implements a memory management system with allocation and deallocation capabilities. + * + * This class provides a simulated memory space with malloc/free operations, load/store + * operations for data access, and memory visualization. It maintains internal bookkeeping + * of allocated and free memory blocks using allocation tables and free lists. + */ +class Memory { +public: + /** + * @brief Constructs a Memory object with specified size. + * @param size The total size of memory to allocate (in bytes) + */ + Memory(size_t size); + + /** + * @brief Loads a value of type T from the specified memory address. + * @tparam T The type of data to load (must be trivially copyable) + * @param addr The memory address to load from + * @return The value loaded from memory + * @throws std::runtime_error if address is out of bounds + */ + template + T load(size_t addr) const; + + /** + * @brief Stores a value of type T at the specified memory address. + * @tparam T The type of data to store (must be trivially copyable) + * @param addr The memory address to store at + * @param value The value to store + * @throws std::runtime_error if address is out of bounds + */ + template + void store(size_t addr, const T& value); + + /** + * @brief Allocates a contiguous block of memory. + * @param sizeBytes The size of memory to allocate (in bytes) + * @return The starting address of the allocated block + * @throws std::runtime_error if insufficient memory is available + */ + size_t malloc(size_t sizeBytes); + + /** + * @brief Deallocates a previously allocated memory block. + * @param addr The starting address of the block to free + * @note Silently ignores invalid free operations (prints warning) + */ + void free(size_t addr); + + /** + * @brief Dumps memory contents in hexadecimal format. + * @param start The starting address for the dump (default: 0) + * @param length The number of bytes to display (default: 64) + */ + void dump(size_t start = 0, size_t length = 64) const; + + /** + * @brief Gets the total size of the memory space. + * @return The total memory size in bytes + */ + size_t getSize() const; + +private: + std::vector mem; /* The actual memory storage */ + std::unordered_map alloc_table; /* Tracks allocated blocks (address -> size) */ + std::map free_list; /* Tracks free blocks (address -> size) */ + + /** + * @brief Validates if a memory access is within bounds. + * @param addr The starting address to check + * @param size The size of the memory region to check + * @return true if access is valid, false otherwise + */ + bool validAddr(size_t addr, size_t size) const; + + /** + * @brief Merges adjacent free blocks in the free list. + * + * This internal method coalesces contiguous free blocks to prevent fragmentation + * and maintain optimal allocation performance. + */ + void mergeFreeBlocks(); +}; + +Memory::Memory(size_t size) : mem(size, 0) { + free_list[0] = size; +} + +template +T Memory::load(size_t addr) const { + assert(validAddr(addr, sizeof(T)) && "Memory load out of bounds"); + T result; + std::memcpy(&result, &mem[addr], sizeof(T)); + return result; +} + +template +void Memory::store(size_t addr, const T& value) { + assert(validAddr(addr, sizeof(T)) && "Memory store out of bounds"); + std::memcpy(&mem[addr], &value, sizeof(T)); +} -static bool verbose = false; -static bool dataflow = false; +size_t Memory::malloc(size_t sizeBytes) { + for (auto it = free_list.begin(); it != free_list.end(); ++it) { + if (it->second >= sizeBytes) { + size_t addr = it->first; + size_t remain = it->second - sizeBytes; + free_list.erase(it); + if (remain > 0) { + free_list[addr + sizeBytes] = remain; + } + alloc_table[addr] = sizeBytes; + return addr; + } + } + throw std::runtime_error("Out of memory"); +} + +void Memory::free(size_t addr) { + auto it = alloc_table.find(addr); + if (it == alloc_table.end()) { + std::cerr << "Invalid free at addr " << addr << "\n"; + return; + } + size_t size = it->second; + alloc_table.erase(it); + free_list[addr] = size; + + mergeFreeBlocks(); +} + +void Memory::dump(size_t start, size_t length) const { + for (size_t i = start; i < start + length && i < mem.size(); ++i) { + printf("%02X ", mem[i]); + if ((i - start + 1) % 16 == 0) printf("\n"); + } + printf("\n"); +} + +size_t Memory::getSize() const { + return mem.size(); +} + +bool Memory::validAddr(size_t addr, size_t size) const { + return addr + size <= mem.size(); +} + +void Memory::mergeFreeBlocks() { + auto it = free_list.begin(); + while (it != free_list.end()) { + auto curr = it++; + if (it != free_list.end() && curr->first + curr->second == it->first) { + curr->second += it->second; + free_list.erase(it); + it = curr; + } + } +} + +// Predicated data structure, used to store scalar/vector values and related metadata +struct PredicatedData { + float value; /* Scalar floating-point value (valid when is_vector is false) */ + bool predicate; /* Validity flag: true means the value is valid, false means it should be ignored */ + bool is_vector; /* Indicates if it's a vector: true for vector, false for scalar */ + std::vector vector_data; /* Vector data (valid when is_vector is true) */ + bool is_reserve; /* Reserve flag (may be used for memory reservation or temporary storage marking) */ + bool is_updated; /* Update flag (indicates whether the data has been modified) */ +}; + +static llvm::DenseMap> value_users; /* Dependency graph tracking: Maps each value to the set of operations that depend on/use it */ +static llvm::SmallVector work_list; /* List of operations to process */ +static llvm::DenseMap in_work_list; /* Marks whether an operation is already in work_list */ + +static bool verbose = false; /* Verbose logging mode switch: outputs debug information when true */ +static bool dataflow = false; /* Dataflow analysis mode switch: enables dataflow-related analysis logic when true */ inline void setDataflowMode(bool v) { dataflow = v; @@ -47,9 +217,21 @@ inline bool isVerboseMode() { return verbose; } +/** + * @brief Builds a dependency graph tracking value-to-user relationships within a module. + * + * This function constructs a graph where each entry maps a Value to the set of Operations + * that use it as an operand. It skips certain operation types (returns, constants, memory grants) + * that don't contribute to data flow dependencies. + * + * @param module The MLIR ModuleOp to analyze for value dependencies + * @return void + */ void buildDependencyGraph(ModuleOp module) { - valueUsers.clear(); + value_users.clear(); + + // Traverse all operations in the module module.walk([&](Operation* op) { if (isa(op) || isa(op)) { return; @@ -63,255 +245,154 @@ void buildDependencyGraph(ModuleOp module) { return; } + // Record each operand's relationship with the current operation for (Value operand : op->getOperands()) { - valueUsers[operand].insert(op); + value_users[operand].insert(op); } }); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Dependency Graph:\n"; - for (auto& entry : valueUsers) { + for (auto& entry : value_users) { llvm::outs() << "[neura-interpreter] Value: "; entry.first.print(llvm::outs()); llvm::outs() << " -> Users: "; - for (auto* userOp : entry.second) { - llvm::outs() << userOp->getName() << ", "; + for (auto* user_op : entry.second) { + llvm::outs() << user_op->getName() << ", "; } llvm::outs() << "\n"; } } } - -void addToWorklist(Operation* op) { +/** + * @brief Adds an operation to the work list if it's not already present. + * + * This function checks if the given operation is valid and not already in the work list + * before adding it. It also maintains a flag to track presence in the work list for efficiency. + * Verbose mode will log the addition of operations. + * + * @param op The Operation to be added to the work list. + * @return void + */ +void addToWorkList(Operation* op) { if (op == nullptr) return; - if (inWorklist.lookup(op)) + if (in_work_list.lookup(op)) return; - worklist.push_back(op); - inWorklist[op] = true; + work_list.push_back(op); + in_work_list[op] = true; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Worklist Added: " << op->getName() << "\n"; + llvm::outs() << "[neura-interpreter] work_list Added: " << op->getName() << "\n"; } } -class Memory { -public: - Memory(size_t size) : mem(size, 0) { - freeList[0] = size; - } - - template - T load(size_t addr) const { - assert(validAddr(addr, sizeof(T)) && "Memory load out of bounds"); - T result; - std::memcpy(&result, &mem[addr], sizeof(T)); - return result; - } - - template - void store(size_t addr, const T& value) { - assert(validAddr(addr, sizeof(T)) && "Memory store out of bounds"); - std::memcpy(&mem[addr], &value, sizeof(T)); - } - - size_t malloc(size_t sizeBytes) { - for (auto it = freeList.begin(); it != freeList.end(); ++it) { - if (it->second >= sizeBytes) { - size_t addr = it->first; - size_t remain = it->second - sizeBytes; - freeList.erase(it); - if (remain > 0) { - freeList[addr + sizeBytes] = remain; - } - allocTable[addr] = sizeBytes; - return addr; - } - } - throw std::runtime_error("Out of memory"); - } - - void free(size_t addr) { - auto it = allocTable.find(addr); - if (it == allocTable.end()) { - std::cerr << "Invalid free at addr " << addr << "\n"; - return; - } - size_t size = it->second; - allocTable.erase(it); - freeList[addr] = size; - - mergeFreeBlocks(); - } - - void dump(size_t start = 0, size_t length = 64) const { - for (size_t i = start; i < start + length && i < mem.size(); ++i) { - printf("%02X ", mem[i]); - if ((i - start + 1) % 16 == 0) printf("\n"); - } - printf("\n"); - } - - size_t getSize() const { return mem.size(); } - -private: - std::vector mem; - std::unordered_map allocTable; - std::map freeList; - - bool validAddr(size_t addr, size_t size) const { - return addr + size <= mem.size(); - } - - void mergeFreeBlocks() { - auto it = freeList.begin(); - while (it != freeList.end()) { - auto curr = it++; - if (it != freeList.end() && curr->first + curr->second == it->first) { - curr->second += it->second; - freeList.erase(it); - it = curr; - } - } - } -}; - -template -class NdArray { -public: - NdArray(Memory& mem, const std::vector& dims) - : memory(mem), shape(dims) { - strides.resize(dims.size()); - size_t stride = sizeof(T); - for (int i = dims.size() - 1; i >= 0; --i) { - strides[i] = stride; - stride *= dims[i]; - } - totalSize = stride; - baseAddr = memory.malloc(totalSize); - } - - void set(const std::vector& indices, T value) { - size_t addr = calcAddr(indices); - memory.store(addr, value); - } - - T get(const std::vector& indices) const { - size_t addr = calcAddr(indices); - return memory.load(addr); - } - - void dumpRaw() const { - memory.dump(baseAddr, totalSize); - } - - void free() { - memory.free(baseAddr); - } - -private: - Memory& memory; - std::vector shape; - std::vector strides; - size_t totalSize; - size_t baseAddr; - - size_t calcAddr(const std::vector& indices) const { - assert(indices.size() == shape.size()); - size_t offset = baseAddr; - for (size_t i = 0; i < indices.size(); ++i) { - assert(indices[i] < shape[i]); - offset += indices[i] * strides[i]; - } - return offset; - } -}; - -// Data structure to hold both value and predicate. -struct PredicatedData { - float value; - bool predicate; - bool isVector; - std::vector vectorData; - bool isReserve; - bool isUpdated; -}; - -bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& valueMap) { +/** + * @brief Handles the execution of an arithmetic constant operation (arith.constant) by parsing its value and storing it in the value map. + * + * This function processes MLIR's arith.constant operations, which represent constant values. It extracts the constant value from the operation's + * attribute, converts it to a floating-point representation (supporting floats, integers, and booleans), and stores it in the value map with a + * predicate set to true (since constants are always valid). Unsupported constant types result in an error. + * + * @param op The arith.constant operation to handle + * @param value_map Reference to the map where the parsed constant value will be stored, keyed by the operation's result value + * @return bool True if the constant is successfully parsed and stored; false if the constant type is unsupported + */ +bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_map) { auto attr = op.getValue(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing arith.constant:\n"; } - PredicatedData val{0.0f, true}; // arith constants always have true predicate - - if (auto floatAttr = llvm::dyn_cast(attr)) { - val.value = floatAttr.getValueAsDouble(); + PredicatedData val{0.0f, true}; + + // Handle floating-point constants (convert to double-precision float) + if (auto float_attr = llvm::dyn_cast(attr)) { + val.value = float_attr.getValueAsDouble(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Parsed float constant : " << llvm::format("%.6f", val.value) << "\n"; } - } else if (auto intAttr = llvm::dyn_cast(attr)) { - if(intAttr.getType().isInteger(1)) { - val.value = intAttr.getInt() != 0 ? 1.0f : 0.0f; + } + // Handle integer constants (including booleans, which are 1-bit integers) + else if (auto int_attr = llvm::dyn_cast(attr)) { + if(int_attr.getType().isInteger(1)) { + val.value = int_attr.getInt() != 0 ? 1.0f : 0.0f; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Parsed boolean constant : " << (val.value ? "true" : "false") << "\n"; } } else { - val.value = static_cast(intAttr.getInt()); + val.value = static_cast(int_attr.getInt()); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Parsed integer constant : " << llvm::format("%.6f", val.value) << "\n"; } } - } else { + } + // Handle unsupported constant types + else { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported constant type in arith.constant\n"; } return false; } - assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); - valueMap[op.getResult()] = val; + assert(value_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_map[op.getResult()] = val; return true; } -bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap& valueMap) { +/** + * @brief Handles the execution of a Neura constant operation (neura.constant) by parsing its value (scalar or vector) and storing it in the value map. + * + * This function processes Neura's custom constant operations, which can represent floating-point scalars, integer scalars, or floating-point vectors. + * It extracts the constant value from the operation's attribute, converts it to the appropriate format, and stores it in the value map. The predicate + * for the constant can be explicitly set via an attribute (defaulting to true if not specified). Unsupported types or vector element types result in an error. + * + * @param op The neura.constant operation to handle + * @param value_map Reference to the map where the parsed constant value will be stored, keyed by the operation's result value + * @return bool True if the constant is successfully parsed and stored; false if the constant type or vector element type is unsupported + */ +bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap& value_map) { auto attr = op.getValue(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.constant:\n"; } - - if (auto floatAttr = llvm::dyn_cast(attr)) { + // Handle floating-point scalar constants + if (auto float_attr = llvm::dyn_cast(attr)) { PredicatedData val; - val.value = floatAttr.getValueAsDouble(); + val.value = float_attr.getValueAsDouble(); val.predicate = true; - val.isVector = false; + val.is_vector = false; - if (auto predAttr = op->getAttrOfType("predicate")) { - val.predicate = predAttr.getValue(); + if (auto pred_attr = op->getAttrOfType("predicate")) { + val.predicate = pred_attr.getValue(); } - assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); - valueMap[op.getResult()] = val; - } else if (auto intAttr = llvm::dyn_cast(attr)) { + assert(value_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_map[op.getResult()] = val; + } + // Handle integer scalar constants + else if (auto int_attr = llvm::dyn_cast(attr)) { PredicatedData val; - val.value = static_cast(intAttr.getInt()); + val.value = static_cast(int_attr.getInt()); val.predicate = true; - val.isVector = false; + val.is_vector = false; - if (auto predAttr = op->getAttrOfType("predicate")) { - val.predicate = predAttr.getValue(); + if (auto pred_attr = op->getAttrOfType("predicate")) { + val.predicate = pred_attr.getValue(); } - assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); - valueMap[op.getResult()] = val; - } else if (auto denseAttr = llvm::dyn_cast(attr)) { - if (!denseAttr.getElementType().isF32()) { + assert(value_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_map[op.getResult()] = val; + } + // Handle vector constants (dense element attributes) + else if (auto dense_attr = llvm::dyn_cast(attr)) { + if (!dense_attr.getElementType().isF32()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported vector element type in neura.constant\n"; } @@ -319,26 +400,28 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap(); - std::copy(floatValues.begin(), floatValues.end(), val.vectorData.begin()); + auto float_values = dense_attr.getValues(); + std::copy(float_values.begin(), float_values.end(), val.vector_data.begin()); - if (auto predAttr = op->getAttrOfType("predicate")) { - val.predicate = predAttr.getValue(); + if (auto pred_attr = op->getAttrOfType("predicate")) { + val.predicate = pred_attr.getValue(); } - assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); - valueMap[op.getResult()] = val; + assert(value_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_map[op.getResult()] = val; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Parsed vector constant of size: " << vectorSize << "\n"; + llvm::outs() << "[neura-interpreter] └─ Parsed vector constant of size: " << vector_size << "\n"; } - } else { + } + // Handle unsupported constant types + else { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported constant type in neura.constant\n"; } @@ -347,7 +430,19 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura addition operation (neura.add) by computing the sum of integer operands. + * + * This function processes Neura's addition operations, which take 2-3 operands: two integer inputs (LHS and RHS) + * and an optional predicate operand. It computes the sum of the integer values, combines the predicates of all + * operands (including the optional predicate if present), and stores the result in the value map. The operation + * requires at least two operands; fewer will result in an error. + * + * @param op The neura.add operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the addition is successfully computed; false if there are fewer than 2 operands + */ +bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.add:\n"; } @@ -359,8 +454,8 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &valueMa return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -368,27 +463,27 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &valueMa llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << " [pred = " << rhs.predicate << "]\n"; } - int64_t lhsInt = static_cast(std::round(lhs.value)); - int64_t rhsInt = static_cast(std::round(rhs.value)); - bool finalPredicate = lhs.predicate && rhs.predicate; + int64_t lhs_int = static_cast(std::round(lhs.value)); + int64_t rhs_int = static_cast(std::round(rhs.value)); + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; } } - int64_t sum = lhsInt + rhsInt; + int64_t sum = lhs_int + rhs_int; PredicatedData result; result.value = static_cast(sum); - result.predicate = finalPredicate; - result.isVector = false; + result.predicate = final_predicate; + result.is_vector = false; - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; @@ -397,7 +492,19 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &valueMa return true; } -bool handleSubOp(neura::SubOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura subtraction operation (neura.sub) by computing the difference of integer operands. + * + * This function processes Neura's subtraction operations, which take 2-3 operands: two integer inputs (LHS and RHS) + * and an optional predicate operand. It computes the difference of the integer values (LHS - RHS), combines the + * predicates of all operands (including the optional predicate if present), and stores the result in the value map. + * The operation requires at least two operands; fewer will result in an error. + * + * @param op The neura.sub operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the subtraction is successfully computed; false if there are fewer than 2 operands + */ +bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sub:\n"; } @@ -409,8 +516,8 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &valueMa return false; } - auto lhs = valueMap[op.getOperand(0)]; - auto rhs = valueMap[op.getOperand(1)]; + auto lhs = value_map[op.getOperand(0)]; + auto rhs = value_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -418,35 +525,47 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &valueMa llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << " [pred = " << rhs.predicate << "]\n"; } - bool finalPredicate = lhs.predicate && rhs.predicate; + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; } } - int64_t lhsInt = static_cast(std::round(lhs.value)); - int64_t rhsInt = static_cast(std::round(rhs.value)); - int64_t resultInt = lhsInt - rhsInt; + int64_t lhs_int = static_cast(std::round(lhs.value)); + int64_t rhs_int = static_cast(std::round(rhs.value)); + int64_t result_int = lhs_int - rhs_int; PredicatedData result; - result.value = static_cast(resultInt); - result.predicate = finalPredicate; - result.isVector = false; + result.value = static_cast(result_int); + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura floating-point addition operation (neura.fadd) by computing the sum of floating-point operands. + * + * This function processes Neura's floating-point addition operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) + * and an optional predicate operand. It computes the sum of the floating-point values, combines the predicates of all operands + * (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; + * fewer will result in an error. + * + * @param op The neura.fadd operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point addition is successfully computed; false if there are fewer than 2 operands + */ +bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd:\n"; } @@ -458,9 +577,9 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; - bool finalPredicate = lhs.predicate && rhs.predicate; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; + bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -469,8 +588,8 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value } if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; @@ -479,18 +598,30 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value PredicatedData result; result.value = lhs.value + rhs.value; - result.predicate = finalPredicate; - result.isVector = false; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura floating-point subtraction operation (neura.fsub) by computing the difference of floating-point operands. + * + * This function processes Neura's floating-point subtraction operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) + * and an optional predicate operand. It calculates the difference of the floating-point values (LHS - RHS), combines the predicates of all + * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; + * fewer will result in an error. + * + * @param op The neura.fsub operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point subtraction is successfully computed; false if there are fewer than 2 operands + */ +bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fsub:\n"; } @@ -502,9 +633,9 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; - bool finalPredicate = lhs.predicate && rhs.predicate; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; + bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -513,8 +644,8 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value } if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; @@ -523,18 +654,30 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value PredicatedData result; result.value = lhs.value - rhs.value; - result.predicate = finalPredicate; - result.isVector = false; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura floating-point multiplication operation (neura.fmul) by computing the product of floating-point operands. + * + * This function processes Neura's floating-point multiplication operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) + * and an optional predicate operand. It calculates the product of the floating-point values (LHS * RHS), combines the predicates of all + * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; + * fewer will result in an error. + * + * @param op The neura.fmul operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point multiplication is successfully computed; false if there are fewer than 2 operands + */ +bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul:\n"; } @@ -546,8 +689,8 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getOperand(0)]; - auto rhs = valueMap[op.getOperand(1)]; + auto lhs = value_map[op.getOperand(0)]; + auto rhs = value_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -555,35 +698,47 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << " [pred = " << rhs.predicate << "]\n"; } - bool finalPredicate = lhs.predicate && rhs.predicate; + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; } } - float lhsFloat = static_cast(lhs.value); - float rhsFloat = static_cast(rhs.value); - float resultFloat = lhsFloat * rhsFloat; + float lhs_float = static_cast(lhs.value); + float rhs_float = static_cast(rhs.value); + float result_float = lhs_float * rhs_float; PredicatedData result; - result.value = resultFloat; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_float; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura floating-point division operation (neura.fdiv) by computing the quotient of floating-point operands. + * + * This function processes Neura's floating-point division operations, which take 2-3 operands: two floating-point inputs (dividend/LHS and divisor/RHS) + * and an optional predicate operand. It calculates the quotient of the floating-point values (LHS / RHS), handles division by zero by returning NaN, + * combines the predicates of all operands (including the optional predicate if present), and stores the result in the value map. The operation requires + * at least two operands; fewer will result in an error. + * + * @param op The neura.fdiv operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point division is successfully computed (including division by zero cases); false if there are fewer than 2 operands + */ +bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fdiv:\n"; } @@ -595,8 +750,8 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getOperand(0)]; - auto rhs = valueMap[op.getOperand(1)]; + auto lhs = value_map[op.getOperand(0)]; + auto rhs = value_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -604,44 +759,57 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << " [pred = " << rhs.predicate << "]\n"; } - bool finalPredicate = lhs.predicate && rhs.predicate; + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; } } - float resultFloat = 0.0f; - float rhsFloat = static_cast(rhs.value); + float result_float = 0.0f; + float rhs_float = static_cast(rhs.value); - if (rhsFloat == 0.0f) { - resultFloat = std::numeric_limits::quiet_NaN(); + if (rhs_float == 0.0f) { + // Return quiet NaN for division by zero to avoid runtime errors + result_float = std::numeric_limits::quiet_NaN(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Warning: Division by zero, result is NaN\n"; } } else { - float lhsFloat = static_cast(lhs.value); - resultFloat = lhsFloat / rhsFloat; + float lhs_float = static_cast(lhs.value); + result_float = lhs_float / rhs_float; } PredicatedData result; - result.value = resultFloat; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_float; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura vector floating-point multiplication operation (neura.vfmul) by computing element-wise products of vector operands. + * + * This function processes Neura's vector floating-point multiplication operations, which take 2-3 operands: two vector inputs (LHS and RHS) + * and an optional scalar predicate operand. It validates that both primary operands are vectors of equal size, computes element-wise products, + * combines the predicates of all operands (including the optional scalar predicate if present), and stores the resulting vector in the value map. + * Errors are returned for invalid operand types (non-vectors), size mismatches, or vector predicates. + * + * @param op The neura.vfmul operation to handle + * @param value_map Reference to the map where the resulting vector will be stored, keyed by the operation's result value + * @return bool True if the vector multiplication is successfully computed; false if there are invalid operands, size mismatches, or other errors + */ +bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.vfmul:\n"; } @@ -653,17 +821,17 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; - if (!lhs.isVector || !rhs.isVector) { + if (!lhs.is_vector || !rhs.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.vfmul requires both operands to be vectors\n"; } return false; } - auto printVector = [](ArrayRef vec) { + auto print_vector = [](ArrayRef vec) { llvm::outs() << "["; for (size_t i = 0; i < vec.size(); ++i) { llvm::outs() << vec[i]; @@ -675,32 +843,32 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; - llvm::outs() << "[neura-interpreter] │ ├─ LHS : vector size = " << lhs.vectorData.size() << ", "; - printVector(lhs.vectorData); + llvm::outs() << "[neura-interpreter] │ ├─ LHS : vector size = " << lhs.vector_data.size() << ", "; + print_vector(lhs.vector_data); llvm::outs() << ", [pred = " << lhs.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] │ └─ RHS : vector size = " << rhs.vectorData.size() << ", "; - printVector(rhs.vectorData); + llvm::outs() << "[neura-interpreter] │ └─ RHS : vector size = " << rhs.vector_data.size() << ", "; + print_vector(rhs.vector_data); llvm::outs() << ", [pred = " << rhs.predicate << "]\n"; } - if (lhs.vectorData.size() != rhs.vectorData.size()) { + if (lhs.vector_data.size() != rhs.vector_data.size()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector size mismatch in neura.vfmul\n"; } return false; } - bool finalPredicate = lhs.predicate && rhs.predicate; + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - if (pred.isVector) { + auto pred = value_map[op.getOperand(2)]; + if (pred.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Predicate operand must be a scalar in neura.vfmul\n"; } return false; } - finalPredicate = finalPredicate && (pred.value != 0.0f); + final_predicate = final_predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; @@ -708,25 +876,37 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val } PredicatedData result; - result.isVector = true; - result.predicate = finalPredicate; - result.vectorData.resize(lhs.vectorData.size()); - - for (size_t i = 0; i < lhs.vectorData.size(); ++i) { - result.vectorData[i] = lhs.vectorData[i] * rhs.vectorData[i]; + result.is_vector = true; + result.predicate = final_predicate; + result.vector_data.resize(lhs.vector_data.size()); + // Compute element-wise multiplication + for (size_t i = 0; i < lhs.vector_data.size(); ++i) { + result.vector_data[i] = lhs.vector_data[i] * rhs.vector_data[i]; } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Result : " << "vector size = " << result.vectorData.size() << ", "; - printVector(result.vectorData); + llvm::outs() << "[neura-interpreter] └─ Result : " << "vector size = " << result.vector_data.size() << ", "; + print_vector(result.vector_data); llvm::outs() << ", [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura chained floating-point addition operation (neura.fadd_fadd) by computing a three-operand sum. + * + * This function processes Neura's chained floating-point addition operations, which take 3-4 operands: three floating-point inputs (A, B, C) + * and an optional predicate operand. It calculates the sum using the order ((A + B) + C), combines the predicates of all operands + * (including the optional predicate if present), and stores the result in the value map. The operation requires at least three operands; + * fewer will result in an error. + * + * @param op The neura.fadd_fadd operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the chained floating-point addition is successfully computed; false if there are fewer than 3 operands + */ +bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd_fadd:\n"; } @@ -738,9 +918,9 @@ bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap 3) { - auto predOperand = valueMap[op.getOperand(3)]; - finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + auto pred_operand = value_map[op.getOperand(3)]; + final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value - << " [pred = " << predOperand.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_operand.value + << " [pred = " << pred_operand.predicate << "]\n"; } } - float resultValue = (a.value + b.value) + c.value; + // Compute the chained sum: ((A + B) + C) + float result_value = (a.value + b.value) + c.value; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Calculation : (" << a.value << " + " << b.value << ") + " << c.value - << " = " << resultValue << "\n"; + << " = " << result_value << "\n"; } PredicatedData result; - result.value = resultValue; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue - << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result_value + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura fused multiply-add operation (neura.fmul_fadd) by computing (A * B) + C. + * + * This function processes Neura's fused multiply-add operations, which take 3-4 operands: three floating-point inputs (A, B, C) + * and an optional predicate operand. It calculates the result using the formula (A * B) + C, combines the predicates of all + * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at + * least three operands; fewer will result in an error. + * + * @param op The neura.fmul_fadd operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the fused multiply-add is successfully computed; false if there are fewer than 3 operands + */ +bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul_fadd:\n"; } @@ -793,9 +986,9 @@ bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap 3) { - auto predOperand = valueMap[op.getOperand(3)]; - finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + auto pred_operand = value_map[op.getOperand(3)]; + final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value - << ", [pred = " << predOperand.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_operand.value + << ", [pred = " << pred_operand.predicate << "]\n"; } } - - float resultValue = 0.0f; - float mulResult = a.value * b.value; - resultValue = mulResult + c.value; + // Compute the fused multiply-add: (A * B) + C + float result_value = 0.0f; + float mul_result = a.value * b.value; + result_value = mul_result + c.value; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Calculation : (" << a.value << " * " << b.value << ") + " << c.value - << " = " << mulResult << " + " << c.value << " = " << resultValue << "\n"; + << " = " << mul_result << " + " << c.value << " = " << result_value << "\n"; } PredicatedData result; - result.value = resultValue; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue - << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result_value + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a function return operation (func.return) by outputting the return value (if any). + * + * This function processes MLIR's standard function return operations, which may optionally return a single value. + * It retrieves the return value from the value map (if present) and prints it in a human-readable format—either as a + * scalar or a vector. For vector values, it formats elements as a comma-separated list. If no return value is present + * (void return), it indicates a void output. + * + * @param op The func.return operation to handle + * @param value_map Reference to the map storing predicated data for values (used to retrieve the return value) + * @return bool True if the return operation is processed successfully; false only if the operation is invalid (nullptr) + */ +bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing func.return:\n"; } @@ -853,14 +1058,14 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap return true; } - auto result = valueMap[op.getOperand(0)]; - if (result.isVector) { - + auto result = value_map[op.getOperand(0)]; + // Print vector return value if the result is a vector + if (result.is_vector) { llvm::outs() << "[neura-interpreter] → Output: ["; - for (size_t i = 0; i < result.vectorData.size(); ++i) { - float val = result.predicate ? result.vectorData[i] : 0.0f; + for (size_t i = 0; i < result.vector_data.size(); ++i) { + float val = result.predicate ? result.vector_data[i] : 0.0f; llvm::outs() << llvm::format("%.6f", val); - if (i != result.vectorData.size() - 1) + if (i != result.vector_data.size() - 1) llvm::outs() << ", "; } llvm::outs() << "]\n"; @@ -871,7 +1076,19 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap return true; } -bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura floating-point comparison operation (neura.fcmp) by evaluating a specified comparison between two operands. + * + * This function processes Neura's floating-point comparison operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) + * and an optional execution predicate. It evaluates the comparison based on the specified type (e.g., "eq" for equality, "lt" for less than), + * combines the predicates of all operands (including the optional predicate if present), and stores the result as a boolean scalar (1.0f for true, 0.0f for false) + * in the value map. Errors are returned for insufficient operands or unsupported comparison types. + * + * @param op The neura.fcmp operation to handle + * @param value_map Reference to the map where the comparison result will be stored, keyed by the operation's result value + * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + */ +bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fcmp:\n"; } @@ -882,8 +1099,8 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -895,59 +1112,72 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value bool pred = true; if (op.getNumOperands() > 2) { - auto predData = valueMap[op.getPredicate()]; - pred = predData.predicate && (predData.value != 0.0f); + auto pred_data = value_map[op.getPredicate()]; + pred = pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predData.value - << ", [pred = " << predData.predicate << "]\n"; - } - } - - bool fcmpResult = false; - StringRef cmpType = op.getCmpType(); - - if (cmpType == "eq") { - fcmpResult = (lhs.value == rhs.value); - } else if (cmpType == "ne") { - fcmpResult = (lhs.value != rhs.value); - } else if (cmpType == "le") { - fcmpResult = (lhs.value <= rhs.value); - } else if (cmpType == "lt") { - fcmpResult = (lhs.value < rhs.value); - } else if (cmpType == "ge") { - fcmpResult = (lhs.value >= rhs.value); - } else if (cmpType == "gt") { - fcmpResult = (lhs.value > rhs.value); + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_data.value + << ", [pred = " << pred_data.predicate << "]\n"; + } + } + + bool fcmp_result = false; + StringRef cmp_type = op.getCmpType(); + // Evaluate the comparison based on the specified type + if (cmp_type == "eq") { + fcmp_result = (lhs.value == rhs.value); + } else if (cmp_type == "ne") { + fcmp_result = (lhs.value != rhs.value); + } else if (cmp_type == "le") { + fcmp_result = (lhs.value <= rhs.value); + } else if (cmp_type == "lt") { + fcmp_result = (lhs.value < rhs.value); + } else if (cmp_type == "ge") { + fcmp_result = (lhs.value >= rhs.value); + } else if (cmp_type == "gt") { + fcmp_result = (lhs.value > rhs.value); } else { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported comparison type: " << cmpType << "\n"; + llvm::errs() << "[neura-interpreter] └─ Unsupported comparison type: " << cmp_type << "\n"; } return false; } - bool finalPredicate = lhs.predicate && rhs.predicate && pred; - float resultValue = fcmpResult ? 1.0f : 0.0f; + bool final_predicate = lhs.predicate && rhs.predicate && pred; + float result_value = fcmp_result ? 1.0f : 0.0f; PredicatedData result; - result.value = resultValue; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; llvm::outs() << "[neura-interpreter] │ ├─ Comparison type : " << op.getCmpType() << "\n"; llvm::outs() << "[neura-interpreter] │ └─ Comparison result : " - << (fcmpResult ? "true" : "false") << "\n"; + << (fcmp_result ? "true" : "false") << "\n"; llvm::outs() << "[neura-interpreter] └─ Result : value = " - << resultValue << ", [pred = " << finalPredicate << "]\n"; + << result_value << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura integer comparison operation (neura.icmp) by evaluating signed/unsigned comparisons between integer operands. + * + * This function processes Neura's integer comparison operations, which take 2-3 operands: two integer inputs (LHS and RHS, stored as floats) + * and an optional execution predicate. It converts the floating-point stored values to integers, evaluates the comparison based on the specified + * type (e.g., "eq" for equality, "slt" for signed less than, "ult" for unsigned less than), combines the predicates of all operands, and stores + * the result as a boolean scalar (1.0f for true, 0.0f for false) in the value map. Errors are returned for insufficient operands or unsupported + * comparison types. + * + * @param op The neura.icmp operation to handle + * @param value_map Reference to the map where the comparison result will be stored, keyed by the operation's result value + * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + */ +bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.icmp:\n"; } @@ -958,8 +1188,8 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; + auto lhs = value_map[op.getLhs()]; + auto rhs = value_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -971,15 +1201,15 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value bool pred = true; if (op.getNumOperands() > 2) { - auto predData = valueMap[op.getPredicate()]; - pred = predData.predicate && (predData.value != 0.0f); + auto pred_data = value_map[op.getPredicate()]; + pred = pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predData.value - << ", [pred = " << predData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_data.value + << ", [pred = " << pred_data.predicate << "]\n"; } } - + // Convert stored floating-point values to signed integers (rounded to nearest integer) int64_t s_lhs = static_cast(std::round(lhs.value)); int64_t s_rhs = static_cast(std::round(rhs.value)); @@ -988,7 +1218,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value static_cast(val) : static_cast(UINT64_MAX + val + 1); }; - + // Convert signed integers to unsigned for unsigned comparisons uint64_t u_lhs = signed_to_unsigned(s_lhs); uint64_t u_rhs = signed_to_unsigned(s_rhs); @@ -1003,7 +1233,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value bool icmp_result = false; StringRef cmp_type = op.getCmpType(); - + // Evaluate the comparison based on the specified type (signed, unsigned, or equality) if (cmp_type == "eq") { icmp_result = (s_lhs == s_rhs); } else if (cmp_type == "ne") { @@ -1019,7 +1249,9 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value } return false; } - } else if (cmp_type.starts_with("u")) { + } + // Handle unsigned comparisons + else if (cmp_type.starts_with("u")) { if (cmp_type == "ult") icmp_result = (u_lhs < u_rhs); else if (cmp_type == "ule") icmp_result = (u_lhs <= u_rhs); else if (cmp_type == "ugt") icmp_result = (u_lhs > u_rhs); @@ -1037,93 +1269,185 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value return false; } - bool finalPredicate = lhs.predicate && rhs.predicate && pred; - float resultValue = icmp_result ? 1.0f : 0.0f; + bool final_predicate = lhs.predicate && rhs.predicate && pred; + float result_value = icmp_result ? 1.0f : 0.0f; PredicatedData result; - result.value = resultValue; - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ Comparison result : " << (icmp_result ? "true" : "false") << "\n"; - llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue - << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result_value + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleOrOp(neura::OrOp op, llvm::DenseMap &valueMap) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.or:\n"; +// bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map) { +// if (isVerboseMode()) { +// llvm::outs() << "[neura-interpreter] Executing neura.or:\n"; +// } + +// if (op.getNumOperands() < 2) { +// if (isVerboseMode()) { +// llvm::errs() << "[neura-interpreter] └─ neura.or expects at least two operands\n"; +// } +// return false; +// } + +// auto lhs = value_map[op.getOperand(0)]; +// auto rhs = value_map[op.getOperand(1)]; + +// if (lhs.is_vector || rhs.is_vector) { +// if (isVerboseMode()) { +// llvm::errs() << "[neura-interpreter] └─ neura.or requires scalar operands\n"; +// } +// return false; +// } + +// if (isVerboseMode()) { +// llvm::outs() << "[neura-interpreter] ├─ Operands \n"; +// llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value << ", [pred = " << lhs.predicate << "]\n"; +// llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << ", [pred = " << rhs.predicate << "]\n"; +// } + +// int64_t lhs_int = static_cast(std::round(lhs.value)); +// int64_t rhs_int = static_cast(std::round(rhs.value)); +// int64_t result_int = lhs_int | rhs_int; + +// bool final_predicate = lhs.predicate && rhs.predicate; +// if (op.getNumOperands() > 2) { +// auto pred = value_map[op.getOperand(2)]; +// final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); +// llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; +// llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value +// << ", [pred = " << pred.predicate << "]\n"; +// } + +// if (isVerboseMode()) { +// llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; +// llvm::outs() << "[neura-interpreter] │ └─ Bitwise OR : " << lhs_int; +// if (lhs_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; +// llvm::outs() << " | " << rhs_int; +// if (rhs_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; +// llvm::outs() << " = " << result_int; +// if (result_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; +// llvm::outs() << "\n"; +// } + +// PredicatedData result; +// result.value = static_cast(result_int); +// result.predicate = final_predicate; +// result.is_vector = false; + +// if (isVerboseMode()) { +// llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value +// << ", [pred = " << final_predicate << "]\n"; +// } + +// value_map[op.getResult()] = result; +// return true; +// } + +/** + * @brief Handles the execution of a Neura logical OR operation (neura.or) for scalar boolean values. + * + * This function processes Neura's logical OR operations, which compute the logical OR of two scalar boolean operands. + * Logical OR returns true if at least one of the operands is true (non-zero). It supports an optional third operand + * as a predicate to further validate the result. The operation requires scalar operands (no vectors) and returns + * a scalar result with a combined validity predicate. + * + * @param op The neura.or operation to handle (modified for logical OR) + * @param value_map Reference to the map storing operands and where the result will be stored + * @return bool True if the logical OR is successfully executed; false for invalid operands (e.g., vectors, insufficient count) + */ +bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.or (logical OR):\n"; } if (op.getNumOperands() < 2) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.or expects at least two operands\n"; + llvm::errs() << "[neura-interpreter] └─ neura.or (logical) expects at least two operands\n"; } return false; } - auto lhs = valueMap[op.getOperand(0)]; - auto rhs = valueMap[op.getOperand(1)]; + // Retrieve left and right operands + auto lhs = value_map[op.getOperand(0)]; + auto rhs = value_map[op.getOperand(1)]; - if (lhs.isVector || rhs.isVector) { + if (lhs.is_vector || rhs.is_vector) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.or requires scalar operands\n"; + llvm::errs() << "[neura-interpreter] └─ neura.or (logical) requires scalar operands\n"; } return false; } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; - llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value << ", [pred = " << lhs.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << ", [pred = " << rhs.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value + << " (boolean: " << (lhs.value != 0.0f ? "true" : "false") << "), [pred = " << lhs.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value + << " (boolean: " << (rhs.value != 0.0f ? "true" : "false") << "), [pred = " << rhs.predicate << "]\n"; } - int64_t lhsInt = static_cast(std::round(lhs.value)); - int64_t rhsInt = static_cast(std::round(rhs.value)); - int64_t resultInt = lhsInt | rhsInt; + // Convert operands to boolean (non-zero = true) + bool lhs_bool = (lhs.value != 0.0f); + bool rhs_bool = (rhs.value != 0.0f); + // Logical OR result: true if either operand is true + bool result_bool = lhs_bool || rhs_bool; - bool finalPredicate = lhs.predicate && rhs.predicate; + // Compute final validity predicate (combines operand predicates and optional predicate) + bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && pred.predicate && (pred.value != 0.0f); - llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value - << ", [pred = " << pred.predicate << "]\n"; + auto pred = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value + << ", [pred = " << pred.predicate << "]\n"; + } } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; - llvm::outs() << "[neura-interpreter] │ └─ Bitwise OR : " << lhsInt; - if (lhsInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; - llvm::outs() << " | " << rhsInt; - if (rhsInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; - llvm::outs() << " = " << resultInt; - if (resultInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; - llvm::outs() << "\n"; + llvm::outs() << "[neura-interpreter] │ └─ Logical OR : " + << lhs_bool << " || " << rhs_bool << " = " << result_bool << "\n"; } PredicatedData result; - result.value = static_cast(resultInt); - result.predicate = finalPredicate; - result.isVector = false; + result.value = result_bool ? 1.0f : 0.0f; + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value - << ", [pred = " << finalPredicate << "]\n"; + << " (boolean: " << (result_bool ? "true" : "false") << "), [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleNotOp(neura::NotOp op, llvm::DenseMap &valueMap) { - - auto input = valueMap[op.getOperand()]; +/** + * @brief Handles the execution of a Neura logical NOT operation (neura.not) by computing the inverse of a boolean input. + * + * This function processes Neura's logical NOT operations, which take a single boolean operand (represented as a floating-point value). + * It converts the input value to an integer (rounded to the nearest whole number), applies the logical NOT operation (inverting true/false), + * and stores the result as a floating-point value (1.0f for true, 0.0f for false) in the value map. The result's predicate is inherited + * from the input operand's predicate. + * + * @param op The neura.not operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool Always returns true as the operation is guaranteed to execute successfully with valid input + */ +bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_map) { + auto input = value_map[op.getOperand()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.not:\n"; @@ -1132,88 +1456,112 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &valueMa << ", [pred = " << input.predicate << "]\n"; } + // Convert the input floating-point value to an integer (rounded to nearest whole number) int64_t inputInt = static_cast(std::round(input.value)); - int64_t resultInt = !inputInt; + // Apply logical NOT: 0 (false) becomes 1 (true), non-zero (true) becomes 0 (false) + int64_t result_int = !inputInt; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; llvm::outs() << "[neura-interpreter] │ └─ Logical NOT : !" << inputInt; - if (inputInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; - llvm::outs() << " = " << resultInt; - if (resultInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; llvm::outs() << "\n"; } - PredicatedData result; - result.value = static_cast(resultInt); + result.value = static_cast(result_int); result.predicate = input.predicate; - result.isVector = false; + result.is_vector = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << ", [pred = " << result.predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleSelOp(neura::SelOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura selection operation (neura.sel) by choosing between two values based on a condition. + * + * This function processes Neura's selection operations, which take exactly 3 operands: a condition, a value to use if the condition is true, + * and a value to use if the condition is false. It evaluates the condition (treating non-zero values with a true predicate as true), + * selects the corresponding value (either "if_true" or "if_false"), and combines the predicate of the condition with the predicate of the selected value. + * The result is marked as a vector only if both input values are vectors. Errors are returned if the operand count is not exactly 3. + * + * @param op The neura.sel operation to handle + * @param value_map Reference to the map where the selected result will be stored, keyed by the operation's result value + * @return bool True if the selection is successfully computed; false if the operand count is invalid + */ +bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sel:\n"; } if (op.getNumOperands() != 3) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.sel expects exactly 3 operands (cond, ifTrue, ifFalse)\n"; + llvm::errs() << "[neura-interpreter] └─ neura.sel expects exactly 3 operands (cond, if_true, if_false)\n"; } return false; } - auto cond = valueMap[op.getCond()]; - auto ifTrue = valueMap[op.getIfTrue()]; - auto ifFalse = valueMap[op.getIfFalse()]; - bool condValue = (cond.value != 0.0f) && cond.predicate; + auto cond = value_map[op.getCond()]; /* Condition to evaluate */ + auto if_true = value_map[op.getIfTrue()]; /* Value if condition is true */ + auto if_false = value_map[op.getIfFalse()]; /* Value if condition is false */ + // Evaluate the condition: true if the value is non-zero and its predicate is true + bool cond_value = (cond.value != 0.0f) && cond.predicate; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; llvm::outs() << "[neura-interpreter] │ ├─ Condition : value = " << cond.value << ", [pred = " << cond.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] │ ├─ If true : value = " << ifTrue.value - << ", [pred = " << ifTrue.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] │ └─ If false : value = " << ifFalse.value - << ", [pred = " << ifFalse.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ ├─ If true : value = " << if_true.value + << ", [pred = " << if_true.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ If false : value = " << if_false.value + << ", [pred = " << if_false.predicate << "]\n"; llvm::outs() << "[neura-interpreter] ├─ Evaluation \n"; } PredicatedData result; - if (condValue) { - result.value = ifTrue.value; - result.predicate = ifTrue.predicate && cond.predicate; + // Prepare the result by selecting the appropriate value based on the condition + if (cond_value) { + result.value = if_true.value; + result.predicate = if_true.predicate && cond.predicate; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] │ └─ Condition is true, selecting 'ifTrue' branch\n"; + llvm::outs() << "[neura-interpreter] │ └─ Condition is true, selecting 'if_true' branch\n"; } } else { - result.value = ifFalse.value; - result.predicate = ifFalse.predicate && cond.predicate; + result.value = if_false.value; + result.predicate = if_false.predicate && cond.predicate; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] │ └─ Condition is false, selecting 'ifFalse' branch\n"; + llvm::outs() << "[neura-interpreter] │ └─ Condition is false, selecting 'if_false' branch\n"; } } - result.isVector = ifTrue.isVector && ifFalse.isVector; + result.is_vector = if_true.is_vector && if_false.is_vector; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << ", predicate = " << result.predicate << "\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleCastOp(neura::CastOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura type conversion operation (neura.cast) by converting an input value between supported types. + * + * This function processes Neura's type conversion operations, which take 1-2 operands: an input value to convert, and an optional predicate operand. + * It supports multiple conversion types (e.g., float to integer, integer to boolean) and validates that the input type matches the conversion requirements. + * The result's predicate is combined with the optional predicate operand (if present), and the result inherits the input's vector flag. Errors are returned for + * invalid operand counts, unsupported conversion types, or mismatched input types for the specified conversion. + * + * @param op The neura.cast operation to handle + * @param value_map Reference to the map where the converted result will be stored, keyed by the operation's result value + * @return bool True if the conversion is successfully computed; false for invalid operands, unsupported types, or mismatched input types + */ +bool handleCastOp(neura::CastOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cast:\n"; } @@ -1224,8 +1572,8 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value return false; } - auto input = valueMap[op.getOperand(0)]; - std::string castType = op.getCastType().str(); + auto input = value_map[op.getOperand(0)]; + std::string cast_type = op.getCastType().str(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operand\n"; @@ -1233,100 +1581,113 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value << input.value << ", [pred = " << input.predicate << "]\n"; } - bool finalPredicate = input.predicate; + bool final_predicate = input.predicate; if (op.getOperation()->getNumOperands() > 1) { - auto predOperand = valueMap[op.getOperand(1)]; - finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + auto pred_operand = value_map[op.getOperand(1)]; + final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value - << ", [pred = " << predOperand.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_operand.value + << ", [pred = " << pred_operand.predicate << "]\n"; } } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Cast type : " << castType << "\n"; + llvm::outs() << "[neura-interpreter] ├─ Cast type : " << cast_type << "\n"; } - float resultValue = 0.0f; - auto inputType = op.getOperand(0).getType(); - - if (castType == "f2i") { - if (!inputType.isF32()) { + float result_value = 0.0f; + auto input_type = op.getOperand(0).getType(); + // Handle specific conversion types with input type validation + if (cast_type == "f2i") { + if (!input_type.isF32()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Cast type 'f2i' requires f32 input\n"; } return false; } - int64_t intValue = static_cast(std::round(input.value)); - resultValue = static_cast(intValue); + int64_t int_value = static_cast(std::round(input.value)); + result_value = static_cast(int_value); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ Converting float to integer " - << input.value << " -> " << intValue << "\n"; + << input.value << " -> " << int_value << "\n"; } - } else if (castType == "i2f") { - if (!inputType.isInteger()) { + } else if (cast_type == "i2f") { + if (!input_type.isInteger()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Cast type 'i2f' requires integer input\n"; } return false; } - int64_t intValue = static_cast(input.value); - resultValue = static_cast(intValue); + int64_t int_value = static_cast(input.value); + result_value = static_cast(int_value); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ Converting integer to float " - << intValue << " -> " << resultValue << "\n"; + << int_value << " -> " << result_value << "\n"; } - } else if (castType == "bool2i" || castType == "bool2f") { - if (!inputType.isInteger(1)) { + } else if (cast_type == "bool2i" || cast_type == "bool2f") { + if (!input_type.isInteger(1)) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Cast type '" << castType + llvm::errs() << "[neura-interpreter] └─ Cast type '" << cast_type << "' requires i1 (boolean) input\n"; } return false; } - bool boolValue = (input.value != 0.0f); - resultValue = boolValue ? 1.0f : 0.0f; + bool bool_value = (input.value != 0.0f); + result_value = bool_value ? 1.0f : 0.0f; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ Converting boolean to number " - << (boolValue ? "true" : "false") << " -> " << resultValue << "\n"; + << (bool_value ? "true" : "false") << " -> " << result_value << "\n"; } - } else if (castType == "i2bool" || castType == "f2bool") { - if (!inputType.isInteger() && !inputType.isF32()) { + } else if (cast_type == "i2bool" || cast_type == "f2bool") { + if (!input_type.isInteger() && !input_type.isF32()) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Cast type '" << castType + llvm::errs() << "[neura-interpreter] └─ Cast type '" << cast_type << "' requires integer or f32 input\n"; } return false; } - bool boolValue = (input.value != 0.0f); - resultValue = boolValue ? 1.0f : 0.0f; + bool bool_value = (input.value != 0.0f); + result_value = bool_value ? 1.0f : 0.0f; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ Converting number to boolean " - << input.value << " -> " << (boolValue ? "true" : "false") << " (stored as " << resultValue << ")\n"; + << input.value << " -> " << (bool_value ? "true" : "false") << " (stored as " << result_value << ")\n"; } } else { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported cast type: " << castType << "\n"; + llvm::errs() << "[neura-interpreter] └─ Unsupported cast type: " << cast_type << "\n"; } return false; } PredicatedData result; - result.value = resultValue; - result.predicate = finalPredicate; - result.isVector = input.isVector; + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = input.is_vector; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue - << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result_value + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } -bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &valueMap, Memory &mem) { +/** + * @brief Handles the execution of a Neura load operation (neura.load) by reading a value from memory at a specified address. + * + * This function processes Neura's memory load operations, which take 1-2 operands: a memory address (stored as a float) and an optional predicate operand. + * It reads the value from memory at the specified address, with support for 32-bit floats, 32-bit integers, and booleans (1-bit integers). The operation + * is skipped if the combined predicate (input address predicate + optional predicate operand) is false, returning a default value of 0.0f. Errors are + * returned for invalid operand counts or unsupported data types. + * + * @param op The neura.load operation to handle + * @param value_map Reference to the map where the loaded value will be stored, keyed by the operation's result value + * @param mem Reference to the memory object used to read the value + * @return bool True if the load is successfully executed (including skipped loads); false for invalid operands or unsupported types + */ +bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.load:\n"; } @@ -1337,30 +1698,30 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value } return false; } - - auto addrVal = valueMap[op.getOperand(0)]; - bool finalPredicate = addrVal.predicate; + // Convert address from float to size_t (memory address type) + auto addr_val = value_map[op.getOperand(0)]; + bool final_predicate = addr_val.predicate; if (op.getNumOperands() > 1) { - auto predVal = valueMap[op.getOperand(1)]; - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + auto pred_val = value_map[op.getOperand(1)]; + final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predVal.value - << ", [pred = " << predVal.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_val.value + << ", [pred = " << pred_val.predicate << "]\n"; } } float val = 0.0f; - size_t addr = static_cast(addrVal.value); - - if (finalPredicate) { - auto resultType = op.getResult().getType(); - if (resultType.isF32()) { + size_t addr = static_cast(addr_val.value); + // Perform load only if final predicate is true + if (final_predicate) { + auto result_type = op.getResult().getType(); + if (result_type.isF32()) { val = mem.load(addr); - } else if (resultType.isInteger(32)) { + } else if (result_type.isInteger(32)) { val = static_cast(mem.load(addr)); - } else if (resultType.isInteger(1)) { + } else if (result_type.isInteger(1)) { val = static_cast(mem.load(addr)); } else { if (isVerboseMode()) { @@ -1377,14 +1738,27 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Load [addr = " << addr << "] => val = " - << val << ", [pred = " << finalPredicate << "]\n"; + << val << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = { val, finalPredicate }; + value_map[op.getResult()] = { val, final_predicate }; return true; } -bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &valueMap, Memory &mem) { +/** + * @brief Handles the execution of a Neura store operation (neura.store) by writing a value to memory at a specified address. + * + * This function processes Neura's memory store operations, which take 2-3 operands: a value to store, a memory address (both stored as floats), + * and an optional predicate operand. It writes the value to memory at the specified address if the combined predicate (address predicate + + * optional predicate operand) is true. Supported types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). The operation + * is skipped if the predicate is false. Errors are returned for insufficient operands or unsupported data types. + * + * @param op The neura.store operation to handle + * @param value_map Reference to the map storing the value and address to be used for the store + * @param mem Reference to the memory object used to write the value + * @return bool True if the store is successfully executed (including skipped stores); false for invalid operands or unsupported types + */ +bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &value_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.store:\n"; } @@ -1396,30 +1770,30 @@ bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &val return false; } - auto valData = valueMap[op.getOperand(0)]; - auto addrVal = valueMap[op.getOperand(1)]; - bool finalPredicate = addrVal.predicate; + auto val_data = value_map[op.getOperand(0)]; /* Value to store */ + auto addr_val = value_map[op.getOperand(1)]; /* Target address */ + bool final_predicate = addr_val.predicate; /* Base predicate from address validity */ if (op.getNumOperands() > 2) { - auto predVal = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + auto pred_val = value_map[op.getOperand(2)]; + final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predVal.value - << ", [pred = " << predVal.predicate << "]\n"; - } - } - - size_t addr = static_cast(addrVal.value); - - if(finalPredicate) { - auto valType = op.getOperand(0).getType(); - if (valType.isF32()) { - mem.store(addr, valData.value); - } else if (valType.isInteger(32)) { - mem.store(addr, static_cast(valData.value)); - } else if (valType.isInteger(1)) { - mem.store(addr, (valData.value != 0.0f)); + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_val.value + << ", [pred = " << pred_val.predicate << "]\n"; + } + } + // Convert address from float to size_t (memory address type) + size_t addr = static_cast(addr_val.value); + // Perform store only if final predicate is true + if(final_predicate) { + auto val_type = op.getOperand(0).getType(); + if (val_type.isF32()) { + mem.store(addr, val_data.value); + } else if (val_type.isInteger(32)) { + mem.store(addr, static_cast(val_data.value)); + } else if (val_type.isInteger(1)) { + mem.store(addr, (val_data.value != 0.0f)); } else { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported store type\n"; @@ -1428,7 +1802,7 @@ bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &val } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Store [addr = " << addr - << "] => val = " << valData.value + << "] => val = " << val_data.value << ", [pred = 1" << "]\n"; } } else { @@ -1440,7 +1814,27 @@ bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &val return true; } -bool handleGEPOp(neura::GEP op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura Get Element Pointer operation (neura.gep) by computing a memory address from a base address and indices. + * + * This function processes Neura's GEP operations, which calculate a target memory address by adding an offset to a base address. The offset is computed + * using multi-dimensional indices and corresponding strides (step sizes between elements in each dimension). The operation accepts 1 or more operands: + * a base address, optional indices (one per dimension), and an optional boolean predicate operand (last operand, if present). It requires a "strides" + * attribute specifying the stride for each dimension, which determines how much to multiply each index by when calculating the offset. + * + * Key behavior: + * - Validates operand count (minimum 1: base address) + * - Identifies optional predicate operand (last operand, if it's a 1-bit integer) + * - Uses "strides" attribute to determine step sizes for each index dimension + * - Computes total offset as the sum of (index * stride) for each dimension + * - Combines predicates from base address and optional predicate operand + * - Returns the final address (base + offset) with the combined predicate + * + * @param op The neura.gep operation to handle + * @param value_map Reference to the map storing predicated data for values (base address, indices, predicate) + * @return bool True if the address is successfully computed; false for invalid operands, missing strides, or mismatched indices/strides + */ +bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.gep:\n"; } @@ -1452,65 +1846,67 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &valueMap) return false; } - auto baseVal = valueMap[op.getOperand(0)]; - size_t baseAddr = static_cast(baseVal.value); - bool finalPredicate = baseVal.predicate; + auto base_val = value_map[op.getOperand(0)]; + size_t base_addr = static_cast(base_val.value); + bool final_predicate = base_val.predicate; if(isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Base address: value = " << baseAddr << ", [pred = " << baseVal.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] ├─ Base address: value = " << base_addr << ", [pred = " << base_val.predicate << "]\n"; } - unsigned numOperands = op.getOperation()->getNumOperands(); - bool hasPredicate = false; - unsigned indexCount = numOperands - 1; + unsigned num_operands = op.getOperation()->getNumOperands(); + bool has_predicate = false; + unsigned index_count = num_operands - 1; - if (numOperands > 1) { - auto lastOperandType = op.getOperand(numOperands - 1).getType(); - if (lastOperandType.isInteger(1)) { - hasPredicate = true; - indexCount -= 1; + if (num_operands > 1) { + auto last_operand_type = op.getOperand(num_operands - 1).getType(); + if (last_operand_type.isInteger(1)) { + has_predicate = true; + index_count -= 1; } } - auto stridesAttr = op->getAttrOfType("strides"); - if (!stridesAttr) { + auto strides_attr = op->getAttrOfType("strides"); + if (!strides_attr) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.gep requires 'strides' attribute\n"; } return false; } + // Convert strides attribute to a vector of size_t (scaling factors for indices) std::vector strides; - for (auto s : stridesAttr) { - auto intAttr = mlir::dyn_cast(s); - if (!intAttr) { + for (auto s : strides_attr) { + auto int_attr = mlir::dyn_cast(s); + if (!int_attr) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Invalid type in 'strides' attribute (expected integer)\n"; } return false; } - strides.push_back(static_cast(intAttr.getInt())); + strides.push_back(static_cast(int_attr.getInt())); } - if (indexCount != strides.size()) { + if (index_count != strides.size()) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ GEP index count (" << indexCount + llvm::errs() << "[neura-interpreter] └─ GEP index count (" << index_count << ") mismatch with strides size (" << strides.size() << ")\n"; } return false; } + // Calculate total offset by scaling each index with its stride and summing size_t offset = 0; - for (unsigned i = 0; i < indexCount; ++i) { - auto idxVal = valueMap[op.getOperand(i + 1)]; - if (!idxVal.predicate) { + for (unsigned i = 0; i < index_count; ++i) { + auto idx_val = value_map[op.getOperand(i + 1)]; + if (!idx_val.predicate) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ GEP index " << i << " has false predicate\n"; } return false; } - size_t idx = static_cast(idxVal.value); + size_t idx = static_cast(idx_val.value); offset += idx * strides[i]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Index " << i << ": value = " << idx << ", stride = " << strides[i] @@ -1518,84 +1914,104 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &valueMap) } } - if (hasPredicate) { - auto predVal = valueMap[op.getOperand(numOperands - 1)]; - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + if (has_predicate) { + auto pred_val = value_map[op.getOperand(num_operands - 1)]; + final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Predicate operand: value = " << predVal.value - << ", [pred = " << predVal.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Predicate operand: value = " << pred_val.value + << ", [pred = " << pred_val.predicate << "]\n"; } } - size_t finalAddr = baseAddr + offset; + size_t final_addr = base_addr + offset; PredicatedData result; - result.value = static_cast(finalAddr); - result.predicate = finalPredicate; - result.isVector = false; + result.value = static_cast(final_addr); + result.predicate = final_predicate; + result.is_vector = false; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Final GEP result: base = " << baseAddr << ", total offset = " << offset - << ", final address = " << finalAddr - << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Final GEP result: base = " << base_addr << ", total offset = " << offset + << ", final address = " << final_addr + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = result; + value_map[op.getResult()] = result; return true; } +/** + * @brief Handles the execution of a Neura indexed load operation (neura.load_indexed) by loading a value from memory using a base address and summed indices. + * + * This function processes Neura's indexed load operations, which calculate a target memory address by adding a base address to the sum of multiple indices. + * It supports scalar values only (no vectors) for the base address, indices, and predicate. The operation loads data from the computed address if the combined + * predicate (validity of base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). + * + * @param op The neura.load_indexed operation to handle + * @param value_map Reference to the map storing values (base address, indices, predicate) and where the loaded result will be stored + * @param mem Reference to the memory object used to read the value + * @return bool True if the indexed load is successfully executed (including skipped loads); false for vector inputs, unsupported types, or errors + */ bool handleLoadIndexedOp(neura::LoadIndexedOp op, - llvm::DenseMap &valueMap, + llvm::DenseMap &value_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Executing neura.load_indexed:\n"; } - auto baseVal = valueMap[op.getBase()]; - if (baseVal.isVector) { + // Retrieve base address and validate it is not a vector + auto base_val = value_map[op.getBase()]; + if (base_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector base not supported in load_indexed\n"; } return false; } - float baseF = baseVal.value; - bool finalPredicate = baseVal.predicate; + float base_F = base_val.value; /* Base address (stored as float) */ + bool final_predicate = base_val.predicate; /* Initialize predicate with base's validity */ + + // Calculate total offset by summing all indices (validate indices are not vectors) + // Todo: multi-dimensional index will be supported in the future float offset = 0.0f; for (Value idx : op.getIndices()) { - auto idxVal = valueMap[idx]; - if (idxVal.isVector) { + auto idx_val = value_map[idx]; + if (idx_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector index not supported in load_indexed\n"; } return false; } - offset += idxVal.value; - finalPredicate = finalPredicate && idxVal.predicate; + // Accumulate index values into total offset + offset += idx_val.value; + final_predicate = final_predicate && idx_val.predicate; } + // Incorporate optional predicate operand (validate it is not a vector) if (op.getPredicate()) { - Value predOperand = op.getPredicate(); - auto predVal = valueMap[predOperand]; - if (predVal.isVector) { + Value pred_operand = op.getPredicate(); + auto pred_val = value_map[pred_operand]; + if (pred_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; } return false; } - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); } - size_t addr = static_cast(baseF + offset); + // Compute target address (base + total offset) and initialize loaded value + size_t addr = static_cast(base_F + offset); float val = 0.0f; - if (finalPredicate) { - auto resultType = op.getResult().getType(); - if (resultType.isF32()) { + // Perform load only if final predicate is true (valid address and conditions) + if (final_predicate) { + auto result_type = op.getResult().getType(); + if (result_type.isF32()) { val = mem.load(addr); - } else if (resultType.isInteger(32)) { + } else if (result_type.isInteger(32)) { val = static_cast(mem.load(addr)); - } else if (resultType.isInteger(1)) { + } else if (result_type.isInteger(1)) { val = static_cast(mem.load(addr)); } else { if (isVerboseMode()) { @@ -1607,74 +2023,90 @@ bool handleLoadIndexedOp(neura::LoadIndexedOp op, if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ LoadIndexed [addr = " << addr << "] => val = " - << val << ", [pred = " << finalPredicate << "]\n"; + << val << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = { val, finalPredicate, false, {}, false }; + value_map[op.getResult()] = { val, final_predicate, false, {}, false }; return true; } +/** + * @brief Handles the execution of a Neura indexed store operation (neura.store_indexed) by storing a value to memory using a base address and summed indices. + * + * This function processes Neura's indexed store operations, which calculate a target memory address by adding a base address to the sum of multiple indices, then stores a value at that address. + * It supports scalar values only (no vectors) for the value to store, base address, indices, and predicate. The operation performs the store only if the combined predicate (validity of the value, + * base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). + * + * @param op The neura.store_indexed operation to handle + * @param value_map Reference to the map storing the value to store, base address, indices, and predicate + * @param mem Reference to the memory object used to write the value + * @return bool True if the indexed store is successfully executed (including skipped stores); false for vector inputs, unsupported types, or errors + */ bool handleStoreIndexedOp(neura::StoreIndexedOp op, - llvm::DenseMap &valueMap, + llvm::DenseMap &value_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.store_indexed:\n"; } - auto valToStore = valueMap[op.getValue()]; - if (valToStore.isVector) { + auto val_to_store = value_map[op.getValue()]; + if (val_to_store.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector value not supported in store_indexed\n"; } return false; } - float value = valToStore.value; - bool finalPredicate = valToStore.predicate; + float value = val_to_store.value; + bool final_predicate = val_to_store.predicate; - auto baseVal = valueMap[op.getBase()]; - if (baseVal.isVector) { + auto base_val = value_map[op.getBase()]; + if (base_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector base not supported in store_indexed\n"; } return false; } - float baseF = baseVal.value; - finalPredicate = finalPredicate && baseVal.predicate; + // Retrieve base address and validate it is not a vector + float base_F = base_val.value; + final_predicate = final_predicate && base_val.predicate; + + // Calculate total offset by summing all indices (validate indices are not vectors) + // Todo: multi-dimensional index will be supported in the future float offset = 0.0f; for (Value idx : op.getIndices()) { - auto idxVal = valueMap[idx]; - if (idxVal.isVector) { + auto idx_val = value_map[idx]; + if (idx_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector index not supported in store_indexed\n"; } return false; } - offset += idxVal.value; - finalPredicate = finalPredicate && idxVal.predicate; + offset += idx_val.value; + final_predicate = final_predicate && idx_val.predicate; } if (op.getPredicate()) { - Value predOperand = op.getPredicate(); - auto predVal = valueMap[predOperand]; - if (predVal.isVector) { + Value pred_operand = op.getPredicate(); + auto pred_val = value_map[pred_operand]; + if (pred_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; } return false; } - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); } - size_t addr = static_cast(baseF + offset); + size_t addr = static_cast(base_F + offset); - if (finalPredicate) { - auto valType = op.getValue().getType(); - if (valType.isF32()) { + if (final_predicate) { + auto val_type = op.getValue().getType(); + if (val_type.isF32()) { mem.store(addr, value); - } else if (valType.isInteger(32)) { + } else if (val_type.isInteger(32)) { mem.store(addr, static_cast(value)); - } else if (valType.isInteger(1)) { + } else if (val_type.isInteger(1)) { mem.store(addr, value != 0.0f); } else { if (isVerboseMode()) { @@ -1686,60 +2118,76 @@ bool handleStoreIndexedOp(neura::StoreIndexedOp op, if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ StoreIndexed [addr = " << addr << "] <= val = " - << value << ", [pred = " << finalPredicate << "]\n"; + << value << ", [pred = " << final_predicate << "]\n"; } return true; } -bool handleBrOp(neura::Br op, llvm::DenseMap &valueMap, - Block *¤tBlock, Block *&lastVisitedBlock) { +/** + * @brief Handles the execution of a Neura unconditional branch operation (neura.br) by transferring control to a target block. + * + * This function processes Neura's unconditional branch operations, which unconditionally direct control flow to a specified target block. + * It validates the target block exists, ensures the number of branch arguments matches the target block's parameters, and copies argument + * values to the target's parameters. Finally, it updates the current and last visited blocks to reflect the control transfer. + * + * @param op The neura.br operation to handle + * @param value_map Reference to the map storing branch arguments and where target parameters will be updated + * @param current_block Reference to the current block; updated to the target block after branch + * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch + * @return bool True if the branch is successfully executed; false for invalid target block or argument/parameter mismatch + */ +bool handleBrOp(neura::Br op, llvm::DenseMap &value_map, + Block *¤t_block, Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.br:\n"; } - Block *destBlock = op.getDest(); - if (!destBlock) { + // Get the target block of the unconditional branch + Block *dest_block = op.getDest(); + if (!dest_block) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.br: Target block is null\n"; } return false; } - auto currentSuccsRange = currentBlock->getSuccessors(); - std::vector succBlocks(currentSuccsRange.begin(), currentSuccsRange.end()); + // Retrieve all successor blocks of the current block + auto current_succs_range = current_block->getSuccessors(); + std::vector succ_blocks(current_succs_range.begin(), current_succs_range.end()); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Block Information\n"; - llvm::outs() << "[neura-interpreter] │ ├─ Current block : Block@" << currentBlock << "\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Current block : Block@" << current_block << "\n"; llvm::outs() << "[neura-interpreter] │ ├─ Successor blocks : \n"; - for (unsigned int i = 0; i < succBlocks.size(); ++i) { - if(i < succBlocks.size() - 1) - llvm::outs() << "[neura-interpreter] │ │ ├─ [" << i << "] Block@" << succBlocks[i] << "\n"; + for (unsigned int i = 0; i < succ_blocks.size(); ++i) { + if(i < succ_blocks.size() - 1) + llvm::outs() << "[neura-interpreter] │ │ ├─ [" << i << "] Block@" << succ_blocks[i] << "\n"; else - llvm::outs() << "[neura-interpreter] │ │ └─ [" << i << "] Block@" << succBlocks[i] << "\n"; + llvm::outs() << "[neura-interpreter] │ │ └─ [" << i << "] Block@" << succ_blocks[i] << "\n"; } - llvm::outs() << "[neura-interpreter] │ └─ Target block : Block@" << destBlock << "\n"; + llvm::outs() << "[neura-interpreter] │ └─ Target block : Block@" << dest_block << "\n"; llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; } + // Get branch arguments and target block parameters const auto &args = op.getArgs(); - const auto &destParams = destBlock->getArguments(); + const auto &dest_params = dest_block->getArguments(); - if (args.size() != destParams.size()) { + if (args.size() != dest_params.size()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.br: Argument count mismatch (passed " - << args.size() << ", target expects " << destParams.size() << ")\n"; + << args.size() << ", target expects " << dest_params.size() << ")\n"; } return false; } - + // Copy argument values to target block parameters in the value map for (size_t i = 0; i < args.size(); ++i) { - Value destParam = destParams[i]; - Value srcArg = args[i]; + Value dest_param = dest_params[i]; + Value src_arg = args[i]; - if (!valueMap.count(srcArg)) { + if (!value_map.count(src_arg)) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.br: Argument " << i << " (source value) not found in value map\n"; @@ -1747,27 +2195,42 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &valueMap, return false; } - valueMap[destParam] = valueMap[srcArg]; - if (isVerboseMode() && i < destParams.size() - 1) { + // Transfer argument value to target parameter + value_map[dest_param] = value_map[src_arg]; + if (isVerboseMode() && i < dest_params.size() - 1) { llvm::outs() << "[neura-interpreter] │ ├─ Param[" << i << "]: value = " - << valueMap[srcArg].value << "\n"; - } else if (isVerboseMode() && i == destParams.size() - 1) { + << value_map[src_arg].value << "\n"; + } else if (isVerboseMode() && i == dest_params.size() - 1) { llvm::outs() << "[neura-interpreter] │ └─ Param[" << i << "]: value = " - << valueMap[srcArg].value << "\n"; + << value_map[src_arg].value << "\n"; } } - lastVisitedBlock = currentBlock; - currentBlock = destBlock; + // Update control flow state: last visited = previous current block; current = target block + last_visited_block = current_block; + current_block = dest_block; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Control Transfer\n"; - llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@ " << destBlock << "\n"; + llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@ " << dest_block << "\n"; } return true; } -bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &valueMap, - Block *¤tBlock, Block *&lastVisitedBlock) { +/** + * @brief Handles the execution of a Neura conditional branch operation (neura.cond_br) by transferring control to one of two target blocks based on a boolean condition. + * + * This function processes Neura's conditional branch operations, which direct control flow to a "true" target block or "false" target block based on the value of a boolean condition. + * It validates the operation's operands (1 mandatory condition + 1 optional predicate), checks that the condition is a boolean (i1 type), and computes a final predicate to determine if the branch is valid. + * If valid, it selects the target block based on the condition's value, passes arguments to the target block's parameters, and updates the current and last visited blocks to reflect the control transfer. + * + * @param op The neura.cond_br operation to handle + * @param value_map Reference to the map storing values (including the condition and optional predicate) + * @param current_block Reference to the current block; updated to the target block after branch + * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch + * @return bool True if the branch is successfully executed; false for invalid operands, missing values, type mismatches, or invalid predicates + */ +bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &value_map, + Block *¤t_block, Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cond_br:\n"; } @@ -1779,14 +2242,14 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val return false; } - auto condValue = op.getCondition(); - if (!valueMap.count(condValue)) { + auto cond_value = op.getCondition(); + if (!value_map.count(cond_value)) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ cond_br: condition value not found in valueMap! (SSA name missing)\n"; + llvm::errs() << "[neura-interpreter] └─ cond_br: condition value not found in value_map! (SSA name missing)\n"; } return false; } - auto condData = valueMap[op.getCondition()]; + auto cond_data = value_map[op.getCondition()]; if (!op.getCondition().getType().isInteger(1)) { if (isVerboseMode()) { @@ -1797,118 +2260,144 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operand\n"; - llvm::outs() << "[neura-interpreter] │ └─ Condition : value = " << condData.value - << ", [pred = " << condData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Condition : value = " << cond_data.value + << ", [pred = " << cond_data.predicate << "]\n"; } - bool finalPredicate = condData.predicate; + // Compute final predicate (combines condition's predicate and optional predicate operand) + bool final_predicate = cond_data.predicate; if (op.getNumOperands() > 1) { - auto predData = valueMap[op.getPredicate()]; - finalPredicate = finalPredicate && predData.predicate && (predData.value != 0.0f); + auto pred_data = value_map[op.getPredicate()]; + final_predicate = final_predicate && pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predData.value - << " [pred = " << predData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_data.value + << " [pred = " << pred_data.predicate << "]\n"; } } - auto currentSuccsRange = currentBlock->getSuccessors(); - std::vector succBlocks(currentSuccsRange.begin(), currentSuccsRange.end()); + // Retrieve successor blocks (targets of the conditional branch) + auto current_succs_range = current_block->getSuccessors(); + std::vector succ_blocks(current_succs_range.begin(), current_succs_range.end()); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Block Information\n"; - llvm::outs() << "[neura-interpreter] │ └─ Current block : Block@" << currentBlock << "\n"; + llvm::outs() << "[neura-interpreter] │ └─ Current block : Block@" << current_block << "\n"; llvm::outs() << "[neura-interpreter] ├─ Branch Targets\n"; - for (unsigned int i = 0; i < succBlocks.size(); ++i) { - if(i < succBlocks.size() - 1) - llvm::outs() << "[neura-interpreter] │ ├─ True block : Block@" << succBlocks[i] << "\n"; + for (unsigned int i = 0; i < succ_blocks.size(); ++i) { + if(i < succ_blocks.size() - 1) + llvm::outs() << "[neura-interpreter] │ ├─ True block : Block@" << succ_blocks[i] << "\n"; else - llvm::outs() << "[neura-interpreter] │ └─ False block : Block@" << succBlocks[i] << "\n"; + llvm::outs() << "[neura-interpreter] │ └─ False block : Block@" << succ_blocks[i] << "\n"; } } - if (!finalPredicate) { + if (!final_predicate) { llvm::errs() << "[neura-interpreter] └─ neura.cond_br: condition or predicate is invalid\n"; return false; } - bool isTrueBranch = (condData.value != 0.0f); - Block *targetBlock = isTrueBranch ? op.getTrueDest() : op.getFalseDest(); - const auto &branchArgs = isTrueBranch ? op.getTrueArgs() : op.getFalseArgs(); - const auto &targetParams = targetBlock->getArguments(); + // Determine target block based on condition value (non-zero = true branch) + bool is_true_branch = (cond_data.value != 0.0f); + Block *target_block = is_true_branch ? op.getTrueDest() : op.getFalseDest(); + const auto &branch_args = is_true_branch ? op.getTrueArgs() : op.getFalseArgs(); + const auto &target_params = target_block->getArguments(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; - llvm::outs() << "[neura-interpreter] │ └─ Condition is " << (condData.value != 0.0f ? "true" : "false") - << " → selecting '" << (isTrueBranch ? "true" : "false") << "' branch\n"; + llvm::outs() << "[neura-interpreter] │ └─ Condition is " << (cond_data.value != 0.0f ? "true" : "false") + << " → selecting '" << (is_true_branch ? "true" : "false") << "' branch\n"; } - if (branchArgs.size() != targetParams.size()) { + if (branch_args.size() != target_params.size()) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.cond_br: argument count mismatch for " - << (isTrueBranch ? "true" : "false") << " branch (expected " - << targetParams.size() << ", got " << branchArgs.size() << ")\n"; + << (is_true_branch ? "true" : "false") << " branch (expected " + << target_params.size() << ", got " << branch_args.size() << ")\n"; } return false; } - if (isVerboseMode()) { - if (!branchArgs.empty()) { + // Pass branch arguments to target block parameters (update value_map) + if (!branch_args.empty()) { + if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; } - for (size_t i = 0; i < branchArgs.size(); ++i) { - valueMap[targetParams[i]] = valueMap[branchArgs[i]]; - if (i < branchArgs.size() - 1) + } + + for (size_t i = 0; i < branch_args.size(); ++i) { + value_map[target_params[i]] = value_map[branch_args[i]]; + if (i < branch_args.size() - 1) { + if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ ├─ param[" << i << "]: value = " - << valueMap[branchArgs[i]].value << "\n"; - else { + << value_map[branch_args[i]].value << "\n"; + } + } else { + if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ param[" << i << "]: value = " - << valueMap[branchArgs[i]].value << "\n"; + << value_map[branch_args[i]].value << "\n"; } } } - - - lastVisitedBlock = currentBlock; - currentBlock = targetBlock; + // Update control flow state: last visited block = previous current block; current block = target + last_visited_block = current_block; + current_block = target_block; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Control Transfer\n"; - llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@" << targetBlock << "\n"; + llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@" << target_block << "\n"; } return true; } -bool handlePhiOpControlFlowMode(neura::PhiOp op, llvm::DenseMap &valueMap, - Block *currentBlock, Block *lastVisitedBlock) { +/** + * @brief Handles the execution of a Neura phi operation (neura.phi) in control flow mode, selecting the correct input based on the execution path. + * + * This function processes Neura's phi operations, which act as control flow merge points by selecting one of several input values based on the predecessor block + * that was most recently visited. It identifies the predecessor block that matches the last visited block, retrieves the corresponding input value, and assigns + * it to the phi operation's result. Validations ensure the current block has predecessors, the last visited block is a valid predecessor, and input count matches + * predecessor count. + * + * @param op The neura.phi operation to handle + * @param value_map Reference to the map storing input values and where the phi result will be stored + * @param current_block The block containing the phi operation (current execution block) + * @param last_visited_block The most recently visited predecessor block (determines which input to select) + * @return bool True if the phi operation is successfully processed; false for validation errors (e.g., missing predecessors, mismatched inputs) + */ +bool handlePhiOpControlFlowMode(neura::PhiOp op, llvm::DenseMap &value_map, + Block *current_block, Block *last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; } - auto predecessorsRange = currentBlock->getPredecessors(); - std::vector predecessors(predecessorsRange.begin(), predecessorsRange.end()); - size_t predCount = predecessors.size(); + // Get all predecessor blocks of the current block (possible execution paths leading to this phi) + auto predecessors_range = current_block->getPredecessors(); + std::vector predecessors(predecessors_range.begin(), predecessors_range.end()); + size_t pred_count = predecessors.size(); - if (predCount == 0) { + // Validate current block has at least one predecessor (phi requires merge of paths) + if (pred_count == 0) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.phi: Current block has no predecessors\n"; } return false; } - size_t predIndex = 0; + // Find the index of the last visited block among the predecessors (determines which input to use) + size_t pred_index = 0; bool found = false; for (auto pred : predecessors) { - if (pred == lastVisitedBlock) { + if (pred == last_visited_block) { found = true; break; } - ++predIndex; + ++pred_index; } + // Validate the last visited block is a valid predecessor of the current block if (!found) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\n"; @@ -1917,64 +2406,84 @@ bool handlePhiOpControlFlowMode(neura::PhiOp op, llvm::DenseMap= inputCount) { + // Validate the predecessor index is within the valid range of inputs + if (pred_index >= input_count) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << predIndex << ")\n"; + llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << pred_index << ")\n"; } return false; } - Value inputVal = inputs[predIndex]; - if (!valueMap.count(inputVal)) { + Value input_val = inputs[pred_index]; + if (!value_map.count(input_val)) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.phi: Input value not found in value map\n"; } return false; } - PredicatedData inputData = valueMap[inputVal]; - valueMap[op.getResult()] = inputData; + // Assign the selected input's data to the phi operation's result + PredicatedData input_data = value_map[input_val]; + value_map[op.getResult()] = input_data; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Predecessor blocks (" << predCount << ")\n"; - for (size_t i = 0; i < predCount; ++i) { - if(i < predCount - 1) { + llvm::outs() << "[neura-interpreter] ├─ Predecessor blocks (" << pred_count << ")\n"; + for (size_t i = 0; i < pred_count; ++i) { + if(i < pred_count - 1) { llvm::outs() << "[neura-interpreter] │ ├─ [" << i << "]: " << "Block@" << predecessors[i]; } else { llvm::outs() << "[neura-interpreter] │ └─ [" << i << "]: " << "Block@" << predecessors[i]; } - if (i == predIndex) { + if (i == pred_index) { llvm::outs() << " (→ current path)\n"; } else { llvm::outs() << "\n"; } } llvm::outs() << "[neura-interpreter] └─ Result : " << op.getResult() << "\n"; - llvm::outs() << "[neura-interpreter] └─ Value : " << inputData.value - << ", [Pred = " << inputData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Value : " << input_data.value + << ", [Pred = " << input_data.predicate << "]\n"; } return true; } -bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura phi operation (neura.phi) in dataflow mode, merging input values based on their validity. + * + * This function processes Neura's phi operations under dataflow analysis, focusing on merging values from multiple input paths. + * It selects the first valid input (with a true predicate) as the result. If no valid inputs exist, it falls back to the first input + * but marks its predicate as false. Additionally, it tracks whether the result has changed from its previous state (via `is_updated`) + * to support dataflow propagation of changes. + * + * Key dataflow-specific behaviors: + * - Prioritizes inputs with valid predicates (true) when merging paths. + * - Explicitly tracks updates to the result to trigger reprocessing of dependent operations. + * - Gracefully handles missing or invalid inputs by falling back to the first input with an invalid predicate. + * + * @param op The neura.phi operation to handle + * @param value_map Reference to the map storing input values and where the merged result will be stored + * @return bool Always returns true, even if no valid inputs are found (handles partial success gracefully) + */ +bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.phi(dataflow):\n"; } auto inputs = op.getInputs(); - size_t inputCount = inputs.size(); + size_t input_count = inputs.size(); - if (inputCount == 0) { + if (input_count == 0) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; } @@ -1982,49 +2491,52 @@ bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap\n"; } } } + // Initialize result with default values PredicatedData result; result.value = 0.0f; result.predicate = false; - result.isVector = false; - result.vectorData = {}; - result.isReserve = false; - result.isUpdated = false; - bool foundValidInput = false; - - for (size_t i = 0; i < inputCount; ++i) { + result.is_vector = false; + result.vector_data = {}; + result.is_reserve = false; + result.is_updated = false; + bool found_valid_input = false; + + // Select the first valid input (with true predicate) to use as the result + for (size_t i = 0; i < input_count; ++i) { Value input = inputs[i]; - if (!valueMap.count(input)) { + if (!value_map.count(input)) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Input [" << i << "] not found, skipping\n"; } continue; } - auto inputData = valueMap[input]; + auto input_data = value_map[input]; - if (inputData.predicate && !foundValidInput) { - result.value = inputData.value; - result.predicate = inputData.predicate; - result.isVector = inputData.isVector; - result.vectorData = inputData.vectorData; - foundValidInput = true; + // Use the first valid input and stop searching + if (input_data.predicate && !found_valid_input) { + result.value = input_data.value; + result.predicate = input_data.predicate; + result.is_vector = input_data.is_vector; + result.vector_data = input_data.vector_data; + found_valid_input = true; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Selected input [" << i << "] (latest valid)\n"; @@ -2033,13 +2545,14 @@ bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap 0) { - Value firstInput = inputs[0]; - if (valueMap.count(firstInput)) { - auto firstData = valueMap[firstInput]; - result.value = firstData.value; - result.isVector = firstData.isVector; - result.vectorData = firstData.vectorData; + // Fallback: if no valid inputs, use the first input but mark predicate as false + if (!found_valid_input && input_count > 0) { + Value first_input = inputs[0]; + if (value_map.count(first_input)) { + auto first_data = value_map[first_input]; + result.value = first_data.value; + result.is_vector = first_data.is_vector; + result.vector_data = first_data.vector_data; result.predicate = false; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ No valid input, using first input with pred=false\n"; @@ -2047,43 +2560,55 @@ bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura reservation operation (neura.reserve) by creating a placeholder value. + * + * This function processes Neura's reserve operations, which create a placeholder value with predefined initial properties. + * The placeholder is initialized with a value of 0.0f, a predicate of false (initially invalid), and is marked as a reserved + * value via the is_reserve flag. This operation is typically used to allocate or reserve a value slot for future use, + * where the actual value and validity will be set later. + * + * @param op The neura.reserve operation to handle + * @param value_map Reference to the map where the reserved placeholder will be stored, keyed by the operation's result value + * @return bool Always returns true as the reservation operation is guaranteed to succeed + */ +bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.reserve:\n"; } PredicatedData placeholder; - placeholder.value = 0.0f; - placeholder.predicate = false; - placeholder.isReserve = true; + placeholder.value = 0.0f; /* Initial value set to 0.0f */ + placeholder.predicate = false; /* Initially marked as invalid (predicate false) */ + placeholder.is_reserve = true; /* Flag to indicate this is a reserved placeholder */ Value result = op.getResult(); - valueMap[result] = placeholder; + value_map[result] = placeholder; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Created placeholder : " << result << "\n"; @@ -2092,11 +2617,21 @@ bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap llvm::outs() << "[neura-interpreter] └─ Type : " << result.getType() << "\n"; } - return true; } -bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura control move operation (neura.ctrl_mov) by copying a source value into a reserved target placeholder. + * + * This function processes Neura's ctrl_mov operations, which transfer data from a source value to a target placeholder that was previously reserved via a neura.reserve operation. + * It validates that the source exists in the value map, the target is a reserved placeholder, and both have matching types. If valid, it copies the source's value, predicate, vector flag, + * and vector data (if applicable) into the target, updating the reserved placeholder with the source's data. + * + * @param op The neura.ctrl_mov operation to handle + * @param value_map Reference to the map storing both the source value and the target reserved placeholder + * @return bool True if the data is successfully moved to the target; false if validation fails (e.g., missing source, invalid target, type mismatch) + */ +bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov:\n"; } @@ -2104,22 +2639,22 @@ bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap Value source = op.getValue(); Value target = op.getTarget(); - if (!valueMap.count(source)) { + if (!value_map.count(source)) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Source value not found in value map\n"; } return false; } - if (!valueMap.count(target) || !valueMap[target].isReserve) { + if (!value_map.count(target) || !value_map[target].is_reserve) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Target is not a reserve placeholder\n"; } return false; } - const auto &sourceData = valueMap[source]; - auto &targetData = valueMap[target]; + const auto &source_data = value_map[source]; + auto &target_data = value_map[target]; if (source.getType() != target.getType()) { if (isVerboseMode()) { @@ -2131,224 +2666,161 @@ bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Source: " << source <<"\n"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << sourceData.value - << ", [pred = " << sourceData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ value = " << source_data.value + << ", [pred = " << source_data.predicate << "]\n"; llvm::outs() << "[neura-interpreter] ├─ Target: " << target << "\n"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << targetData.value - << ", [pred = " << targetData.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ value = " << target_data.value + << ", [pred = " << target_data.predicate << "]\n"; } - - targetData.value = sourceData.value; - targetData.predicate = sourceData.predicate; - targetData.isVector = sourceData.isVector; - if (sourceData.isVector) { - targetData.vectorData = sourceData.vectorData; + // Copy data from source to target: value, predicate, vector flag, and vector data (if vector) + target_data.value = source_data.value; + target_data.predicate = source_data.predicate; + target_data.is_vector = source_data.is_vector; + if (source_data.is_vector) { + target_data.vector_data = source_data.vector_data; } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n"; - llvm::outs() << "[neura-interpreter] └─ value = " << targetData.value - << ", [pred = " << targetData.predicate << "]\n"; - } - - return true; -} - -bool handleCtrlMovOpDataFlowMode_(neura::CtrlMovOp op, - llvm::DenseMap &valueMap) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov(dataflow):\n"; - } - - Value source = op.getValue(); - Value target = op.getTarget(); - - if (!valueMap.count(source)) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map (execution failed)\n"; - } - return false; - } - - if (!valueMap.count(target) || !valueMap[target].isReserve) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder (execution failed)\n"; - } - return false; - } - - const auto &sourceData = valueMap[source]; - auto &targetData = valueMap[target]; - - float oldValue = targetData.value; - bool oldPredicate = targetData.predicate; - bool oldIsVector = targetData.isVector; - - targetData.value = sourceData.value; - targetData.predicate = sourceData.predicate; - targetData.isVector = sourceData.isVector; - - bool isTerminated = false; - if (!isTerminated) { - targetData.value = sourceData.value; - targetData.predicate = sourceData.predicate; - targetData.isVector = sourceData.isVector; - targetData.vectorData = sourceData.isVector ? sourceData.vectorData : std::vector(); - } - - if (sourceData.isVector) { - targetData.vectorData = sourceData.vectorData; - } else { - targetData.vectorData.clear(); - } - - std::vector oldVectorData = targetData.vectorData; - - bool isScalarUpdated = false; - bool isVectorUpdated = false; - bool isTypeUpdated = false; - - if (!isTerminated) { - if (!targetData.isVector) { - isScalarUpdated = (targetData.value != oldValue) || - (targetData.predicate != oldPredicate) || - !oldVectorData.empty(); - } - if (targetData.isVector) { - isVectorUpdated = (targetData.predicate != oldPredicate) || - (targetData.vectorData != oldVectorData); - } - isTypeUpdated = (targetData.isVector != oldIsVector); - targetData.isUpdated = isScalarUpdated || isVectorUpdated || isTypeUpdated; - } else { - targetData.isUpdated = false; - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << sourceData.value << " | " << sourceData.predicate << "\n"; - llvm::outs() << "[neura-interpreter] ├─ Target (after update): " << targetData.value << " | " << targetData.predicate << " | isUpdated=" << targetData.isUpdated << "\n"; - llvm::outs() << "[neura-interpreter] └─ Execution succeeded (data copied)\n"; + llvm::outs() << "[neura-interpreter] └─ value = " << target_data.value + << ", [pred = " << target_data.predicate << "]\n"; } return true; } +/** + * @brief Handles the execution of a Neura control move operation (neura.ctrl_mov) in dataflow mode, with explicit tracking of value updates. + * + * This function processes neura.ctrl_mov operations under dataflow analysis, focusing on conditional updates to a reserved target placeholder. + * It only updates the target if the source value's predicate is valid (true). Additionally, it tracks whether the target was actually modified + * (via the `is_updated` flag) to support dataflow propagation (e.g., triggering reprocessing of dependent operations). + * + * Key differences from standard mode: + * - Explicitly checks if the source predicate is valid before updating. + * - Tracks detailed changes (value, predicate, vector status) to set `is_updated`. + * - Focuses on propagating state changes for dataflow analysis. + * + * @param op The neura.ctrl_mov operation to handle + * @param value_map Reference to the map storing the source value and target reserved placeholder + * @return bool True if the operation is processed successfully (including skipped updates); false for critical errors (e.g., missing source/target) + */ bool handleCtrlMovOpDataFlowMode(neura::CtrlMovOp op, - llvm::DenseMap &valueMap) { + llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov(dataflow):\n"; } Value source = op.getValue(); - Value target = op.getTarget(); - - if (!valueMap.count(source)) { + if (!value_map.count(source)) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map (execution failed)\n"; + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found (execution failed)\n"; } return false; } - if (!valueMap.count(target) || !valueMap[target].isReserve) { + Value target = op.getTarget(); + auto &target_data = value_map[target]; + if (!value_map.count(target) || !target_data.is_reserve) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder (execution failed)\n"; } return false; } - const auto &sourceData = valueMap[source]; - auto &targetData = valueMap[target]; - - float oldValue = targetData.value; - bool oldPredicate = targetData.predicate; - bool oldIsVector = targetData.isVector; - std::vector oldVectorData = targetData.vectorData; + // Capture source data and target's current state (for update checks) + const auto &source_data = value_map[source]; + const float old_value = target_data.value; + const bool old_predicate = target_data.predicate; + const bool old_is_vector = target_data.is_vector; + const std::vector oldvector_data = target_data.vector_data; - bool isTerminated = false; - bool isScalarUpdated = false; - bool isVectorUpdated = false; - bool isTypeUpdated = false; - targetData.isUpdated = false; + // Reset update flag; will be set to true only if actual changes occur + target_data.is_updated = false; + // Determine if update should proceed: only if source predicate is valid (true) + const bool should_update = (source_data.predicate == 1); - bool shouldUpdate = !isTerminated && (sourceData.predicate == 1); - if (shouldUpdate) { - targetData.value = sourceData.value; - targetData.predicate = sourceData.predicate; - targetData.isVector = sourceData.isVector; - - if (sourceData.isVector) { - targetData.vectorData = sourceData.vectorData; - } else { - targetData.vectorData.clear(); - } + if (should_update) { + // Copy source data to target + target_data.value = source_data.value; + target_data.predicate = source_data.predicate; + target_data.is_vector = source_data.is_vector; + target_data.vector_data = source_data.is_vector ? source_data.vector_data : std::vector(); - if (!targetData.isVector) { - isScalarUpdated = (targetData.value != oldValue) || - (targetData.predicate != oldPredicate) || - !oldVectorData.empty(); - } - if (targetData.isVector) { - isVectorUpdated = (targetData.predicate != oldPredicate) || - (targetData.vectorData != oldVectorData); - } - isTypeUpdated = (targetData.isVector != oldIsVector); - targetData.isUpdated = isScalarUpdated || isVectorUpdated || isTypeUpdated; - } else { - if (isVerboseMode()) { - if (isTerminated) { - llvm::outs() << "[neura-interpreter] ├─ Skip update: Loop terminated\n"; - } else { - llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate is invalid (pred=" << sourceData.predicate << ")\n"; - } - } + // Check if scalar target was updated (value, predicate, or vector state changed) + const bool is_scalar_updated = !target_data.is_vector && + (target_data.value != old_value || + target_data.predicate != old_predicate || + !oldvector_data.empty()); + // Check if vector target was updated (predicate or vector elements changed) + const bool is_vector_updated = target_data.is_vector && + (target_data.predicate != old_predicate || + target_data.vector_data != oldvector_data); + target_data.is_updated = is_scalar_updated || is_vector_updated || (target_data.is_vector != old_is_vector); + } else if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate invalid (pred=" + << source_data.predicate << ")\n"; } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << sourceData.value << " | " << sourceData.predicate << "\n"; - llvm::outs() << "[neura-interpreter] ├─ Target (after update): " << targetData.value << " | " << targetData.predicate << " | isUpdated=" << targetData.isUpdated << "\n"; - llvm::outs() << "[neura-interpreter] └─ Execution succeeded (update controlled by predicate and loop status)\n"; + llvm::outs() << "[neura-interpreter] ├─ Source: " << source_data.value << " | " << source_data.predicate << "\n" + << "[neura-interpreter] ├─ Target (after): " << target_data.value << " | " << target_data.predicate + << " | is_updated=" << target_data.is_updated << "\n" + << "[neura-interpreter] └─ Execution succeeded\n"; } return true; } - -bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura return operation (neura.return) by processing and outputting return values. + * + * This function processes Neura's return operations, which return zero or more values from a function. It retrieves each return value from the value map, + * validates their existence, and prints them in a human-readable format (scalar or vector) in verbose mode. For vector values, it formats elements as a + * comma-separated list, using 0.0f for elements with an invalid predicate. If no return values are present, it indicates a void return. + * + * @param op The neura.return operation to handle + * @param value_map Reference to the map storing the return values to be processed + * @return bool True if return values are successfully processed; false if any return value is missing from the value map + */ +bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.return:\n"; } - auto returnValues = op.getValues(); - if (returnValues.empty()) { + auto return_values = op.getValues(); + // Handle void return (no values) + if (return_values.empty()) { llvm::outs() << "[neura-interpreter] → Output: (void)\n"; return true; } + // Collect and validate return values from the value map std::vector results; - for (Value val : returnValues) { - if (!valueMap.count(val)) { + for (Value val : return_values) { + if (!value_map.count(val)) { llvm::errs() << "[neura-interpreter] └─ Return value not found in value map\n"; return false; } - results.push_back(valueMap[val]); + results.push_back(value_map[val]); } if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Return values:\n"; for (size_t i = 0; i < results.size(); ++i) { const auto &data = results[i]; - // llvm::outs() << "[neura-interpreter] [" << i << "]: "; - - if (data.isVector) { + // Print vector values with predicate check (0.0f if predicate is false) + if (data.is_vector) { llvm::outs() << "[neura-interpreter] │ └─ vector = ["; - for (size_t j = 0; j < data.vectorData.size(); ++j) { - float val = data.predicate ? data.vectorData[j] : 0.0f; + for (size_t j = 0; j < data.vector_data.size(); ++j) { + float val = data.predicate ? data.vector_data[j] : 0.0f; llvm::outs() << llvm::format("%.6f", val); - if (j != data.vectorData.size() - 1) + if (j != data.vector_data.size() - 1) llvm::outs() << ", "; } llvm::outs() << "]"; } else { + // Print scalar value with predicate check (0.0f if predicate is false) float val = data.predicate ? data.value : 0.0f; llvm::outs() << "[neura-interpreter] │ └─" << llvm::format("%.6f", val); } @@ -2360,7 +2832,18 @@ bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura conditional grant operation (neura.grant_predicate) by updating a value's predicate based on a new condition. + * + * This function processes Neura's grant_predicate operations, which take exactly 2 operands: a source value and a new predicate value. It updates the source value's + * predicate to be the logical AND of the source's original predicate, the new predicate's validity, and the new predicate's non-zero value (treating non-zero as true). + * The result retains the source's value but uses the computed combined predicate. Errors are returned for invalid operand counts or missing operands in the value map. + * + * @param op The neura.grant_predicate operation to handle + * @param value_map Reference to the map where the updated result will be stored, keyed by the operation's result value + * @return bool True if the operation is successfully executed; false for invalid operands or missing entries in the value map + */ +bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_predicate:\n"; } @@ -2372,51 +2855,63 @@ bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura one-time grant operation (neura.grant_once) by granting validity to a value exactly once. + * + * This function processes Neura's grant_once operations, which take either a source value operand or a constant value attribute (but not both). + * It grants validity (sets predicate to true) on the first execution and denies validity (sets predicate to false) on all subsequent executions. + * The result retains the source/constant value but uses the one-time predicate. Errors are returned for invalid operand/attribute combinations + * or unsupported constant types. + * + * @param op The neura.grant_once operation to handle + * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the operation is successfully executed; false for invalid inputs or unsupported types + */ +bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &value_map) { if(isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_once:\n"; } - - bool hasValue = op.getValue() != nullptr; - bool hasConstant = op.getConstantValue().has_value(); + // Check if either a value operand or constant attribute is provided + bool has_value = op.getValue() != nullptr; + bool has_constant = op.getConstantValue().has_value(); - if (hasValue == hasConstant) { + if (has_value == has_constant) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ grant_once requires exactly one of (value operand, constant_value attribute)\n"; } @@ -2424,32 +2919,32 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap(constantAttr)) { - source.value = intAttr.getInt(); + // Extract and convert constant value from attribute + Attribute constant_attr = op.getConstantValue().value(); + if (auto int_attr = mlir::dyn_cast(constant_attr)) { + source.value = int_attr.getInt(); source.predicate = false; - source.isVector = false; - // 使用全局的mlir::dyn_cast替代成员函数dyn_cast - } else if (auto floatAttr = mlir::dyn_cast(constantAttr)) { - source.value = floatAttr.getValueAsDouble(); + source.is_vector = false; + + } else if (auto float_attr = mlir::dyn_cast(constant_attr)) { + source.value = float_attr.getValueAsDouble(); source.predicate = false; - source.isVector = false; + source.is_vector = false; } else { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported constant_value type\n"; @@ -2461,12 +2956,12 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap granted; - bool hasGranted = granted[op.getOperation()]; - bool resultPredicate = !hasGranted; + bool has_granted = granted[op.getOperation()]; + bool result_predicate = !has_granted; - if (!hasGranted) { + if (!has_granted) { granted[op.getOperation()] = true; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ First access - granting predicate\n"; @@ -2478,19 +2973,29 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura unconditional grant operation (neura.grant_always) by unconditionally validating a value's predicate. + * + * This function processes Neura's grant_always operations, which take exactly 1 operand (a source value) and return a copy of that value with its predicate set to true, + * regardless of the original predicate. This operation effectively "grants" validity to the value unconditionally. Errors are returned if the operand count is not exactly 1. + * + * @param op The neura.grant_always operation to handle + * @param value_map Reference to the map where the granted result will be stored, keyed by the operation's result value + * @return bool True if the operation is successfully executed; false if the operand count is invalid + */ +bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap &value_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_always:\n"; } @@ -2502,355 +3007,299 @@ bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap& valueMap, +/** + * @brief Executes a single operation, handles type-specific processing, tracks value updates, and propagates changes to dependent operations. + * + * This function processes the given operation using specialized handlers based on its type. It first saves the current values of the operation's + * results to detect updates after execution. If execution succeeds, it checks for changes in the results' values. If updates are detected, + * it marks the values as updated and adds all dependent operations (users) to the next work list for subsequent processing. + * + * @param op The operation to execute + * @param value_map Reference to the map storing predicated data for values (tracks current state and updates) + * @param mem Reference to the memory object for handling load/store operations + * @param next_work_list Reference to the list of operations to process in the next iteration (receives dependent operations on updates) + * @return bool True if the operation executes successfully; false if execution fails (e.g., unhandled operation type) + */ +bool executeOperation(Operation* op, + llvm::DenseMap& value_map, Memory& mem, - llvm::SmallVector& nextWorklist) { - llvm::DenseMap oldValues; + llvm::SmallVector& next_work_list) { + llvm::DenseMap old_values; for (Value result : op->getResults()) { - if (valueMap.count(result)) { - oldValues[result] = valueMap[result]; - } - } - - bool executionSuccess = true; - if (auto constOp = dyn_cast(op)) { - executionSuccess = handleArithConstantOp(constOp, valueMap); - } else if (auto constOp = dyn_cast(op)) { - executionSuccess = handleNeuraConstantOp(constOp, valueMap); - } else if (auto movOp = dyn_cast(op)) { - valueMap[movOp.getResult()] = valueMap[movOp.getOperand()]; - } else if (auto addOp = dyn_cast(op)) { - executionSuccess = handleAddOp(addOp, valueMap); - } else if (auto subOp = dyn_cast(op)) { - executionSuccess = handleSubOp(subOp, valueMap); - } else if (auto faddOp = dyn_cast(op)) { - executionSuccess = handleFAddOp(faddOp, valueMap); - } else if (auto fsubOp = dyn_cast(op)) { - executionSuccess = handleFSubOp(fsubOp, valueMap); - } else if (auto fmulOp = dyn_cast(op)) { - executionSuccess = handleFMulOp(fmulOp, valueMap); - } else if (auto fdivOp = dyn_cast(op)) { - executionSuccess = handleFDivOp(fdivOp, valueMap); - } else if (auto vfmulOp = dyn_cast(op)) { - executionSuccess = handleVFMulOp(vfmulOp, valueMap); - } else if (auto faddFaddOp = dyn_cast(op)) { - executionSuccess = handleFAddFAddOp(faddFaddOp, valueMap); - } else if (auto fmulFaddOp = dyn_cast(op)) { - executionSuccess = handleFMulFAddOp(fmulFaddOp, valueMap); - } else if (auto retOp = dyn_cast(op)) { - executionSuccess = handleFuncReturnOp(retOp, valueMap); - } else if (auto fcmpOp = dyn_cast(op)) { - executionSuccess = handleFCmpOp(fcmpOp, valueMap); - } else if (auto icmpOp = dyn_cast(op)) { - executionSuccess = handleICmpOp(icmpOp, valueMap); - } else if (auto orOp = dyn_cast(op)) { - executionSuccess = handleOrOp(orOp, valueMap); - } else if (auto notOp = dyn_cast(op)) { - executionSuccess = handleNotOp(notOp, valueMap); - } else if (auto selOp = dyn_cast(op)) { - executionSuccess = handleSelOp(selOp, valueMap); - } else if (auto castOp = dyn_cast(op)) { - executionSuccess = handleCastOp(castOp, valueMap); - } else if (auto loadOp = dyn_cast(op)) { - executionSuccess = handleLoadOp(loadOp, valueMap, mem); - } else if (auto storeOp = dyn_cast(op)) { - executionSuccess = handleStoreOp(storeOp, valueMap, mem); - } else if (auto gepOp = dyn_cast(op)) { - executionSuccess = handleGEPOp(gepOp, valueMap); - } else if (auto loadIndexOp = dyn_cast(op)) { - executionSuccess = handleLoadIndexedOp(loadIndexOp, valueMap, mem); - } else if (auto storeIndexOp = dyn_cast(op)) { - executionSuccess = handleStoreIndexedOp(storeIndexOp, valueMap, mem); - } else if (auto brOp = dyn_cast(op)) { - // executionSuccess = handleBrOp(brOp, valueMap, currentBlock, lastVisitedBlock); - } else if (auto condBrOp = dyn_cast(op)) { - // executionSuccess = handleCondBrOp(condBrOp, valueMap, currentBlock, lastVisitedBlock); - } else if (auto phiOp = dyn_cast(op)) { - executionSuccess = handlePhiOpDataFlowMode(phiOp, valueMap); - } else if (auto reserveOp = dyn_cast(op)) { - executionSuccess = handleReserveOp(reserveOp, valueMap); - } else if (auto ctrlMovOp = dyn_cast(op)) { - executionSuccess = handleCtrlMovOpDataFlowMode(ctrlMovOp, valueMap); - } else if (auto returnOp = dyn_cast(op)) { - executionSuccess = handleNeuraReturnOp(returnOp, valueMap); - } else if (auto grantPredOp = dyn_cast(op)) { - executionSuccess = handleGrantPredicateOp(grantPredOp, valueMap); - } else if (auto grantOnceOp = dyn_cast(op)) { - executionSuccess = handleGrantOnceOp(grantOnceOp, valueMap); - } else if (auto grantAlwaysOp = dyn_cast(op)) { - executionSuccess = handleGrantAlwaysOp(grantAlwaysOp, valueMap); + if (value_map.count(result)) { + old_values[result] = value_map[result]; + } + } + + bool execution_success = true; + if (auto const_op = dyn_cast(op)) { + execution_success = handleArithConstantOp(const_op, value_map); + } else if (auto const_op = dyn_cast(op)) { + execution_success = handleNeuraConstantOp(const_op, value_map); + } else if (auto mov_op = dyn_cast(op)) { + value_map[mov_op.getResult()] = value_map[mov_op.getOperand()]; + } else if (auto add_op = dyn_cast(op)) { + execution_success = handleAddOp(add_op, value_map); + } else if (auto sub_op = dyn_cast(op)) { + execution_success = handleSubOp(sub_op, value_map); + } else if (auto fadd_op = dyn_cast(op)) { + execution_success = handleFAddOp(fadd_op, value_map); + } else if (auto fsub_op = dyn_cast(op)) { + execution_success = handleFSubOp(fsub_op, value_map); + } else if (auto fmul_op = dyn_cast(op)) { + execution_success = handleFMulOp(fmul_op, value_map); + } else if (auto fdiv_op = dyn_cast(op)) { + execution_success = handleFDivOp(fdiv_op, value_map); + } else if (auto vfmul_op = dyn_cast(op)) { + execution_success = handleVFMulOp(vfmul_op, value_map); + } else if (auto fadd_fadd_op = dyn_cast(op)) { + execution_success = handleFAddFAddOp(fadd_fadd_op, value_map); + } else if (auto fmul_fadd_op = dyn_cast(op)) { + execution_success = handleFMulFAddOp(fmul_fadd_op, value_map); + } else if (auto ret_op = dyn_cast(op)) { + execution_success = handleFuncReturnOp(ret_op, value_map); + } else if (auto fcmp_op = dyn_cast(op)) { + execution_success = handleFCmpOp(fcmp_op, value_map); + } else if (auto icmp_op = dyn_cast(op)) { + execution_success = handleICmpOp(icmp_op, value_map); + } else if (auto or_op = dyn_cast(op)) { + execution_success = handleOrOp(or_op, value_map); + } else if (auto not_op = dyn_cast(op)) { + execution_success = handleNotOp(not_op, value_map); + } else if (auto sel_op = dyn_cast(op)) { + execution_success = handleSelOp(sel_op, value_map); + } else if (auto cast_op = dyn_cast(op)) { + execution_success = handleCastOp(cast_op, value_map); + } else if (auto load_op = dyn_cast(op)) { + execution_success = handleLoadOp(load_op, value_map, mem); + } else if (auto store_op = dyn_cast(op)) { + execution_success = handleStoreOp(store_op, value_map, mem); + } else if (auto gep_op = dyn_cast(op)) { + execution_success = handleGEPOp(gep_op, value_map); + } else if (auto load_index_op = dyn_cast(op)) { + execution_success = handleLoadIndexedOp(load_index_op, value_map, mem); + } else if (auto store_index_op = dyn_cast(op)) { + execution_success = handleStoreIndexedOp(store_index_op, value_map, mem); + } else if (auto br_op = dyn_cast(op)) { + // execution_success = handleBrOp(br_op, value_map, current_block, last_visited_block); + } else if (auto cond_br_op = dyn_cast(op)) { + // execution_success = handleCondBrOp(cond_br_op, value_map, current_block, last_visited_block); + } else if (auto phi_op = dyn_cast(op)) { + execution_success = handlePhiOpDataFlowMode(phi_op, value_map); + } else if (auto reserve_op = dyn_cast(op)) { + execution_success = handleReserveOp(reserve_op, value_map); + } else if (auto ctrl_mov_op = dyn_cast(op)) { + execution_success = handleCtrlMovOpDataFlowMode(ctrl_mov_op, value_map); + } else if (auto return_op = dyn_cast(op)) { + execution_success = handleNeuraReturnOp(return_op, value_map); + } else if (auto grant_pred_op = dyn_cast(op)) { + execution_success = handleGrantPredicateOp(grant_pred_op, value_map); + } else if (auto grant_once_op = dyn_cast(op)) { + execution_success = handleGrantOnceOp(grant_once_op, value_map); + } else if (auto grant_always_op = dyn_cast(op)) { + execution_success = handleGrantAlwaysOp(grant_always_op, value_map); } else { llvm::errs() << "[neura-interpreter] Unhandled op: "; op->print(llvm::errs()); llvm::errs() << "\n"; - executionSuccess = false; + execution_success = false; } - if (!executionSuccess) { + // If execution failed, exit early without propagating updates + if (!execution_success) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation failed, no propagation\n"; } - return; + return execution_success; } - bool hasUpdate = false; + // Check if any of the operation's results were updated during execution + bool has_update = false; for (Value result : op->getResults()) { - if (!valueMap.count(result)) continue; - PredicatedData newData = valueMap[result]; - - if (!oldValues.count(result)) { - newData.isUpdated = true; - valueMap[result] = newData; - hasUpdate = true; + if (!value_map.count(result)) continue; + PredicatedData new_data = value_map[result]; + + // New result (not in old_values) is considered an update + if (!old_values.count(result)) { + new_data.is_updated = true; + value_map[result] = new_data; + has_update = true; continue; } - PredicatedData oldData = oldValues[result]; - if (isDataUpdated(oldData, newData)) { - newData.isUpdated = true; - valueMap[result] = newData; - hasUpdate = true; + // Compare new value with old value to detect updates + PredicatedData old_data = old_values[result]; + if (isDataUpdated(old_data, new_data)) { + new_data.is_updated = true; + value_map[result] = new_data; + has_update = true; } else { - newData.isUpdated = false; - valueMap[result] = newData; + new_data.is_updated = false; + value_map[result] = new_data; } } + // Special case: check for updates in operations with no results (e.g., CtrlMovOp) if (op->getNumResults() == 0) { - if (auto ctrlMovOp = dyn_cast(op)) { - Value target = ctrlMovOp.getTarget(); - if (valueMap.count(target) && valueMap[target].isUpdated) { - hasUpdate = true; + if (auto ctrl_mov_op = dyn_cast(op)) { + Value target = ctrl_mov_op.getTarget(); + if (value_map.count(target) && value_map[target].is_updated) { + has_update = true; } else { llvm::outs() << "[neura-interpreter] No update for ctrl_mov target: " << target << "\n"; } } } - if (hasUpdate) { + // If updates occurred, propagate to dependent operations (users) + if (has_update) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; } - llvm::SmallVector affectedValues; + // Collect all values affected by the update + llvm::SmallVector affected_values; for (Value result : op->getResults()) { - if (valueMap.count(result) && valueMap[result].isUpdated) { - affectedValues.push_back(result); + if (value_map.count(result) && value_map[result].is_updated) { + affected_values.push_back(result); } } + // Include targets from the operations without result (e.g., CtrlMovOp) if (op->getNumResults() == 0) { - if (auto ctrlMovOp = dyn_cast(op)) { - Value target = ctrlMovOp.getTarget(); - if (valueMap.count(target) && valueMap[target].isUpdated) { - affectedValues.push_back(target); + if (auto ctrl_mov_op = dyn_cast(op)) { + Value target = ctrl_mov_op.getTarget(); + if (value_map.count(target) && value_map[target].is_updated) { + affected_values.push_back(target); } } } - for (Value val : affectedValues) { - if (!valueUsers.count(val)) continue; - for (Operation* userOp : valueUsers[val]) { - if (!inWorklist[userOp]) { - nextWorklist.push_back(userOp); - inWorklist[userOp] = true; + // Add all users of affected values to the next work list (if not already present) + for (Value val : affected_values) { + if (!value_users.count(val)) continue; + for (Operation* user_op : value_users[val]) { + if (!in_work_list[user_op]) { + next_work_list.push_back(user_op); + in_work_list[user_op] = true; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Added user to next worklist: "; - userOp->print(llvm::outs()); + llvm::outs() << "[neura-interpreter] Added user to next work_list: "; + user_op->print(llvm::outs()); llvm::outs() << "\n"; } } } } } -} - -void initializeStaticOps(func::FuncOp func, - llvm::DenseMap& valueMap, - Memory& mem, - llvm::SmallVector& nextWorklist) { - for (auto& block : func.getBody()) { - for (auto& op : block.getOperations()) { - if (isa(op) || - isa(op) || - // isa(op) || - isa(op)) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Init phase: executing "; - op.print(llvm::outs()); - llvm::outs() << "\n"; - } - - executeOperation(&op, valueMap, mem, nextWorklist); - } - } - } -} - -int runDataFlowMode_(func::FuncOp func, - llvm::DenseMap& valueMap, - Memory& mem) { - - llvm::SmallVector nextWorklist; - initializeStaticOps(func, valueMap, mem, nextWorklist); - worklist.clear(); - inWorklist.clear(); - llvm::SmallVector delayQueue; - - for (auto& block : func.getBody()) { - for (auto& op : block.getOperations()) { - if (isa(op) || isa(op)) { - delayQueue.push_back(&op); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Return op added to delay queue: "; - op.print(llvm::outs()); - llvm::outs() << "\n"; - } - } else if (!(isa(op) || isa(op) || - isa(op) || isa(op))) { - worklist.push_back(&op); - inWorklist[&op] = true; - } - } - } - int iterCount = 0; - while (!worklist.empty() || !delayQueue.empty()) { - iterCount++; - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Iteration " << iterCount - << " | worklist: " << worklist.size() - << " | delayQueue: " << delayQueue.size() << "\n"; - } - - for (Operation* op : worklist) { - inWorklist[op] = false; - executeOperation(op, valueMap, mem, nextWorklist); - } - - bool canExecuteReturn = true; - if (!delayQueue.empty()) { - Operation* returnOp = delayQueue[0]; - for (Value input : returnOp->getOperands()) { - if (!valueMap.count(input) || !valueMap[input].predicate) { - canExecuteReturn = false; - break; - } - } - } - - if (canExecuteReturn && !delayQueue.empty()) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] All return inputs are valid, executing return\n"; - } - for (Operation* returnOp : delayQueue) { - nextWorklist.push_back(returnOp); - inWorklist[returnOp] = true; - } - delayQueue.clear(); - } else { - if (isVerboseMode() && !delayQueue.empty()) { - llvm::outs() << "[neura-interpreter] Some return inputs are invalid, keeping return in delay queue\n"; - } - } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter]" << - " | next work: " << nextWorklist.size() << "\n"; - } - worklist = std::move(nextWorklist); - - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Total iterations: " << iterCount << "\n"; - } - return 0; + return execution_success; } +/** + * @brief Executes a function in data flow mode, processing operations based on data availability and managing return operations separately. + * + * This function processes operations in a work list, where each operation is executed once its dependencies are satisfied. + * Return operations are held in a delay queue until all their input values are valid, ensuring they execute only when all prerequisites are met. + * It iteratively processes the work list, propagating data through the graph until all operations (including returns) are completed. + * + * @param func The function to execute in data flow mode + * @param value_map Reference to a map storing predicated data for each value (tracks valid/invalid state) + * @param mem Reference to the memory object for handling load/store operations + * @return int 0 if execution completes successfully, 1 if an error occurs during operation execution + */ int runDataFlowMode(func::FuncOp func, - llvm::DenseMap& valueMap, + llvm::DenseMap& value_map, Memory& mem) { - llvm::SmallVector delayQueue; + // Queue to hold return operations until their dependencies are satisfied + llvm::SmallVector delay_queue; + // Initialize work list with all operations except return ops (which go to delay queue) for (auto& block : func.getBody()) { for (auto& op : block.getOperations()) { if (isa(op) || isa(op)) { - delayQueue.push_back(&op); + delay_queue.push_back(&op); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Return op added to delay queue: "; op.print(llvm::outs()); llvm::outs() << "\n"; } } else { - worklist.push_back(&op); - inWorklist[&op] = true; + // Add non-return operations to the work list and mark them as present + work_list.push_back(&op); + in_work_list[&op] = true; } } } - int iterCount = 0; - while (!worklist.empty() || !delayQueue.empty()) { - iterCount++; + int iter_count = 0; + // Main loop: process work list and delay queue until both are empty + while (!work_list.empty() || !delay_queue.empty()) { + iter_count++; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Iteration " << iterCount - << " | worklist: " << worklist.size() - << " | delayQueue: " << delayQueue.size() << "\n"; + llvm::outs() << "[neura-interpreter] Iteration " << iter_count + << " | work_list: " << work_list.size() + << " | delay_queue: " << delay_queue.size() << "\n"; } - llvm::SmallVector nextWorklist; + llvm::SmallVector next_work_list; - for (Operation* op : worklist) { - inWorklist[op] = false; - executeOperation(op, valueMap, mem, nextWorklist); + // Process all operations in the current work list + for (Operation* op : work_list) { + in_work_list[op] = false; + if (!executeOperation(op, value_map, mem, next_work_list)) { + return 1; + } } - if (!delayQueue.empty()) { - Operation* returnOp = delayQueue[0]; - bool canExecuteReturn = true; - for (Value input : returnOp->getOperands()) { - if (!valueMap.count(input) || !valueMap[input].predicate) { - canExecuteReturn = false; + // Check if return operations in the delay queue can be executed + if (!delay_queue.empty()) { + Operation* return_op = delay_queue[0]; + bool can_execute_return = true; + // Verify all input operands of the return op are valid (exist and have true predicate) + for (Value input : return_op->getOperands()) { + if (!value_map.count(input) || !value_map[input].predicate) { + can_execute_return = false; break; } } - if (canExecuteReturn) { + if (can_execute_return) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] All return inputs are valid, executing return\n"; } - for (Operation* op : delayQueue) { - nextWorklist.push_back(op); - inWorklist[op] = true; + for (Operation* op : delay_queue) { + next_work_list.push_back(op); + in_work_list[op] = true; } - delayQueue.clear(); + delay_queue.clear(); } else { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Some return inputs are invalid, keeping return in delay queue\n"; @@ -2858,135 +3307,152 @@ int runDataFlowMode(func::FuncOp func, } } - worklist = std::move(nextWorklist); + // Prepare work list for the next iteration + work_list = std::move(next_work_list); } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Total iterations: " << iterCount << "\n"; + llvm::outs() << "[neura-interpreter] Total iterations: " << iter_count << "\n"; } return 0; } - +/** + * @brief Executes the control flow of a function by processing operations in sequence and handling control transfers. + * + * This function iterates through the operations within the function's blocks, processing each operation + * according to its type using specialized handler functions. It manages control flow transfers (branches, + * conditional branches) and maintains the current execution state including the active block and operation index. + * + * @param func The function to execute in control flow mode + * @param value_map Reference to a map storing predicated data for each value + * @param mem Reference to the memory object for handling load/store operations + * @return int 0 if execution completes successfully, 1 if an error occurs (unhandled operation or handler failure) + */ int runControlFlowMode(func::FuncOp func, - llvm::DenseMap& valueMap, + llvm::DenseMap& value_map, Memory& mem) { - Block* currentBlock = &func.getBody().front(); - Block* lastVisitedBlock = nullptr; - size_t opIndex = 0; - bool isTerminated = false; - - while (!isTerminated && currentBlock) { - auto& operations = currentBlock->getOperations(); + Block* current_block = &func.getBody().front(); /* Initialize execution state - start at the first block of the function */ + Block* last_visited_block = nullptr; /* Track the previous block for handling phi operations */ + size_t op_index = 0; /* Current position within the operations of the current block */ + bool is_terminated = false; /* Flag to indicate if execution has been terminated (e.g., by return operation) */ + + // Main execution loop - continues until termination or no more blocks to process + while (!is_terminated && current_block) { + auto& operations = current_block->getOperations(); - if (opIndex >= operations.size()) { + if (op_index >= operations.size()) { break; } - Operation& op = *std::next(operations.begin(), opIndex); - - if (auto constOp = dyn_cast(op)) { - if (!handleArithConstantOp(constOp, valueMap)) return 1; - ++opIndex; - } else if (auto constOp = dyn_cast(op)) { - if (!handleNeuraConstantOp(constOp, valueMap)) return 1; - ++opIndex; - } else if (auto movOp = dyn_cast(op)) { - valueMap[movOp.getResult()] = valueMap[movOp.getOperand()]; - ++opIndex; - } else if (auto addOp = dyn_cast(op)) { - if (!handleAddOp(addOp, valueMap)) return 1; - ++opIndex; - } else if (auto subOp = dyn_cast(op)) { - if (!handleSubOp(subOp, valueMap)) return 1; - ++opIndex; - } else if (auto faddOp = dyn_cast(op)) { - if (!handleFAddOp(faddOp, valueMap)) return 1; - ++opIndex; - } else if (auto fsubOp = dyn_cast(op)) { - if (!handleFSubOp(fsubOp, valueMap)) return 1; - ++opIndex; - } else if (auto fmulOp = dyn_cast(op)) { - if (!handleFMulOp(fmulOp, valueMap)) return 1; - ++opIndex; - } else if (auto fdivOp = dyn_cast(op)) { - if (!handleFDivOp(fdivOp, valueMap)) return 1; - ++opIndex; - } else if (auto vfmulOp = dyn_cast(op)) { - if (!handleVFMulOp(vfmulOp, valueMap)) return 1; - opIndex++; - } else if (auto faddFaddOp = dyn_cast(op)) { - if (!handleFAddFAddOp(faddFaddOp, valueMap)) return 1; - ++opIndex; - } else if (auto fmulFaddOp = dyn_cast(op)) { - if (!handleFMulFAddOp(fmulFaddOp, valueMap)) return 1; - ++opIndex; - } else if (auto retOp = dyn_cast(op)) { - if (!handleFuncReturnOp(retOp, valueMap)) return 1; - isTerminated = true; - ++opIndex; - } else if (auto fcmpOp = dyn_cast(op)) { - if (!handleFCmpOp(fcmpOp, valueMap)) return 1; - ++opIndex; - } else if (auto icmpOp = dyn_cast(op)) { - if (!handleICmpOp(icmpOp, valueMap)) return 1; - ++opIndex; - } else if (auto orOp = dyn_cast(op)) { - if (!handleOrOp(orOp, valueMap)) return 1; - ++opIndex; - } else if (auto notOp = dyn_cast(op)) { - if (!handleNotOp(notOp, valueMap)) return 1; - ++opIndex; - } else if (auto selOp = dyn_cast(op)) { - if (!handleSelOp(selOp, valueMap)) return 1; - ++opIndex; - } else if (auto castOp = dyn_cast(op)) { - if (!handleCastOp(castOp, valueMap)) return 1; - ++opIndex; - } else if (auto loadOp = dyn_cast(op)) { - if (!handleLoadOp(loadOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto storeOp = dyn_cast(op)) { - if (!handleStoreOp(storeOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto gepOp = dyn_cast(op)) { - if (!handleGEPOp(gepOp, valueMap)) return 1; - ++opIndex; - } else if (auto loadIndexOp = dyn_cast(op)) { - if (!handleLoadIndexedOp(loadIndexOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto storeIndexOp = dyn_cast(op)) { - if (!handleStoreIndexedOp(storeIndexOp, valueMap, mem)) return 1; - ++opIndex; - } else if (auto brOp = dyn_cast(op)) { - if (!handleBrOp(brOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - opIndex = 0; - } else if (auto condBrOp = dyn_cast(op)) { - if (!handleCondBrOp(condBrOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - opIndex = 0; - } else if (auto phiOp = dyn_cast(op)) { - if (!handlePhiOpControlFlowMode(phiOp, valueMap, currentBlock, lastVisitedBlock)) return 1; - ++opIndex; - } else if (auto reserveOp = dyn_cast(op)) { - if (!handleReserveOp(reserveOp, valueMap)) return 1; - ++opIndex; - } else if (auto ctrlMovOp = dyn_cast(op)) { - if (!handleCtrlMovOp(ctrlMovOp, valueMap)) return 1; - ++opIndex; - } else if (auto returnOp = dyn_cast(op)) { - if (!handleNeuraReturnOp(returnOp, valueMap)) return 1; - isTerminated = true; - ++opIndex; - } else if (auto grantPredOp = dyn_cast(op)) { - if (!handleGrantPredicateOp(grantPredOp, valueMap)) return 1; - ++opIndex; - } else if (auto grantOnceOp = dyn_cast(op)) { - if (!handleGrantOnceOp(grantOnceOp, valueMap)) return 1; - ++opIndex; - } else if (auto grantAlwaysOp = dyn_cast(op)) { - if (!handleGrantAlwaysOp(grantAlwaysOp, valueMap)) return 1; - ++opIndex; + Operation& op = *std::next(operations.begin(), op_index); + + // Handle each operation type with specialized handlers + if (auto const_op = dyn_cast(op)) { + if (!handleArithConstantOp(const_op, value_map)) return 1; + ++op_index; + } else if (auto const_op = dyn_cast(op)) { + if (!handleNeuraConstantOp(const_op, value_map)) return 1; + ++op_index; + } else if (auto mov_op = dyn_cast(op)) { + value_map[mov_op.getResult()] = value_map[mov_op.getOperand()]; + ++op_index; + } else if (auto add_op = dyn_cast(op)) { + if (!handleAddOp(add_op, value_map)) return 1; + ++op_index; + } else if (auto sub_op = dyn_cast(op)) { + if (!handleSubOp(sub_op, value_map)) return 1; + ++op_index; + } else if (auto fadd_op = dyn_cast(op)) { + if (!handleFAddOp(fadd_op, value_map)) return 1; + ++op_index; + } else if (auto fsub_op = dyn_cast(op)) { + if (!handleFSubOp(fsub_op, value_map)) return 1; + ++op_index; + } else if (auto fmul_op = dyn_cast(op)) { + if (!handleFMulOp(fmul_op, value_map)) return 1; + ++op_index; + } else if (auto fdiv_op = dyn_cast(op)) { + if (!handleFDivOp(fdiv_op, value_map)) return 1; + ++op_index; + } else if (auto vfmul_op = dyn_cast(op)) { + if (!handleVFMulOp(vfmul_op, value_map)) return 1; + ++op_index; + } else if (auto fadd_fadd_op = dyn_cast(op)) { + if (!handleFAddFAddOp(fadd_fadd_op, value_map)) return 1; + ++op_index; + } else if (auto fmul_fadd_op = dyn_cast(op)) { + if (!handleFMulFAddOp(fmul_fadd_op, value_map)) return 1; + ++op_index; + } else if (auto ret_op = dyn_cast(op)) { + if (!handleFuncReturnOp(ret_op, value_map)) return 1; + is_terminated = true; + ++op_index; + } else if (auto fcmp_op = dyn_cast(op)) { + if (!handleFCmpOp(fcmp_op, value_map)) return 1; + ++op_index; + } else if (auto icmp_op = dyn_cast(op)) { + if (!handleICmpOp(icmp_op, value_map)) return 1; + ++op_index; + } else if (auto or_op = dyn_cast(op)) { + if (!handleOrOp(or_op, value_map)) return 1; + ++op_index; + } else if (auto not_op = dyn_cast(op)) { + if (!handleNotOp(not_op, value_map)) return 1; + ++op_index; + } else if (auto sel_op = dyn_cast(op)) { + if (!handleSelOp(sel_op, value_map)) return 1; + ++op_index; + } else if (auto cast_op = dyn_cast(op)) { + if (!handleCastOp(cast_op, value_map)) return 1; + ++op_index; + } else if (auto load_op = dyn_cast(op)) { + if (!handleLoadOp(load_op, value_map, mem)) return 1; + ++op_index; + } else if (auto store_op = dyn_cast(op)) { + if (!handleStoreOp(store_op, value_map, mem)) return 1; + ++op_index; + } else if (auto gep_op = dyn_cast(op)) { + if (!handleGEPOp(gep_op, value_map)) return 1; + ++op_index; + } else if (auto load_index_op = dyn_cast(op)) { + if (!handleLoadIndexedOp(load_index_op, value_map, mem)) return 1; + ++op_index; + } else if (auto store_index_op = dyn_cast(op)) { + if (!handleStoreIndexedOp(store_index_op, value_map, mem)) return 1; + ++op_index; + } else if (auto br_op = dyn_cast(op)) { + // Branch operations change the current block - reset index to start of new block + if (!handleBrOp(br_op, value_map, current_block, last_visited_block)) return 1; + op_index = 0; + } else if (auto cond_br_op = dyn_cast(op)) { + // Conditional branches change the current block - reset index to start of new block + if (!handleCondBrOp(cond_br_op, value_map, current_block, last_visited_block)) return 1; + op_index = 0; + } else if (auto phi_op = dyn_cast(op)) { + // Phi operations depend on previous block for value selection + if (!handlePhiOpControlFlowMode(phi_op, value_map, current_block, last_visited_block)) return 1; + ++op_index; + } else if (auto reserve_op = dyn_cast(op)) { + if (!handleReserveOp(reserve_op, value_map)) return 1; + ++op_index; + } else if (auto ctrl_mov_op = dyn_cast(op)) { + if (!handleCtrlMovOp(ctrl_mov_op, value_map)) return 1; + ++op_index; + } else if (auto return_op = dyn_cast(op)) { + if (!handleNeuraReturnOp(return_op, value_map)) return 1; + is_terminated = true; + ++op_index; + } else if (auto grant_pred_op = dyn_cast(op)) { + if (!handleGrantPredicateOp(grant_pred_op, value_map)) return 1; + ++op_index; + } else if (auto grant_once_op = dyn_cast(op)) { + if (!handleGrantOnceOp(grant_once_op, value_map)) return 1; + ++op_index; + } else if (auto grant_always_op = dyn_cast(op)) { + if (!handleGrantAlwaysOp(grant_always_op, value_map)) return 1; + ++op_index; } else { llvm::errs() << "[neura-interpreter] Unhandled op: "; op.print(llvm::errs()); @@ -3019,37 +3485,37 @@ int main(int argc, char **argv) { MLIRContext context; context.appendDialectRegistry(registry); - llvm::SourceMgr sourceMgr; - auto fileOrErr = mlir::openInputFile(argv[1]); - if (!fileOrErr) { + llvm::SourceMgr source_mgr; + auto file_or_err = mlir::openInputFile(argv[1]); + if (!file_or_err) { llvm::errs() << "[neura-interpreter] Error opening file\n"; return 1; } - sourceMgr.AddNewSourceBuffer(std::move(fileOrErr), llvm::SMLoc()); + source_mgr.AddNewSourceBuffer(std::move(file_or_err), llvm::SMLoc()); - OwningOpRef module = parseSourceFile(sourceMgr, &context); + OwningOpRef module = parseSourceFile(source_mgr, &context); if (!module) { llvm::errs() << "[neura-interpreter] Failed to parse MLIR input file\n"; return 1; } // Changes map to store PredicatedData instead of just float. - llvm::DenseMap valueMap; + llvm::DenseMap value_map; Memory mem(1024); // 1MB if (isDataflowMode()) { buildDependencyGraph(*module); for (auto func : module->getOps()) { - if (runDataFlowMode(func, valueMap, mem)) { + if (runDataFlowMode(func, value_map, mem)) { llvm::errs() << "[neura-interpreter] Data Flow execution failed\n"; return 1; } } } else { for (auto func : module->getOps()) { - if (runControlFlowMode(func, valueMap, mem)) { + if (runControlFlowMode(func, value_map, mem)) { llvm::errs() << "[neura-interpreter] Control Flow execution failed\n"; return 1; } @@ -3057,4 +3523,4 @@ int main(int argc, char **argv) { } return 0; -} +} \ No newline at end of file From 0305dc95ae6845ddb3d4efcf6b4e0c25745dc87f Mon Sep 17 00:00:00 2001 From: item Date: Sat, 9 Aug 2025 11:14:17 +0800 Subject: [PATCH 3/6] add interpreter dataflow mode --- .../interpreter/lower_and_interpret.mlir | 4 +- tools/neura-interpreter/neura-interpreter.cpp | 1799 +++++++---------- 2 files changed, 774 insertions(+), 1029 deletions(-) diff --git a/test/neura/interpreter/lower_and_interpret.mlir b/test/neura/interpreter/lower_and_interpret.mlir index 56bf20b8..9d50c317 100644 --- a/test/neura/interpreter/lower_and_interpret.mlir +++ b/test/neura/interpreter/lower_and_interpret.mlir @@ -31,8 +31,8 @@ module { %arg0 = arith.constant 9.0 : f32 %cst = arith.constant 2.0 : f32 %0 = arith.addf %arg0, %cst : f32 - // // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] - // // CHECK: [neura-interpreter] → Output: [[OUTPUT]] + // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] + // CHECK: [neura-interpreter] → Output: [[OUTPUT]] return %0 : f32 } } diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index c462ac0d..95fb1b9b 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -192,11 +192,37 @@ struct PredicatedData { std::vector vector_data; /* Vector data (valid when is_vector is true) */ bool is_reserve; /* Reserve flag (may be used for memory reservation or temporary storage marking) */ bool is_updated; /* Update flag (indicates whether the data has been modified) */ + + /** + * @brief Compares this PredicatedData instance with another to check if the data has been updated. + * + * This function determines whether any part of the current PredicatedData differs from another instance. + * It compares scalar values, predicate flags, vector flags, and vector contents (if applicable). + * This is useful in dataflow analysis to determine whether a value change should trigger downstream updates. + * + * @param other The PredicatedData instance to compare against. + * @return bool True if any field differs, indicating an update; false if all fields are equal. + */ + bool isUpdatedComparedTo(const PredicatedData& other) const; +}; + +bool PredicatedData::isUpdatedComparedTo(const PredicatedData& other) const { + if (value != other.value) return true; + if (predicate != other.predicate) return true; + if (is_vector != other.is_vector) return true; + if (is_vector && vector_data != other.vector_data) return true; + return false; +} + +// Define structure to hold operation handling results and control flow information +struct OperationHandleResult { + bool success; /* Indicates if the operation was processed successfully */ + bool is_terminated; /* Indicates if execution should terminate (e.g., after return operations) */ + bool is_branch; /* Indicates if the operation is a branch (requires special index handling) */ }; -static llvm::DenseMap> value_users; /* Dependency graph tracking: Maps each value to the set of operations that depend on/use it */ -static llvm::SmallVector work_list; /* List of operations to process */ -static llvm::DenseMap in_work_list; /* Marks whether an operation is already in work_list */ +static llvm::SmallVector pending_operation_queue; /* List of operations to execute in current iteration */ +static llvm::DenseMap is_operation_enqueued; /* Marks whether an operation is already in pending_operation_queue */ static bool verbose = false; /* Verbose logging mode switch: outputs debug information when true */ static bool dataflow = false; /* Dataflow analysis mode switch: enables dataflow-related analysis logic when true */ @@ -217,77 +243,29 @@ inline bool isVerboseMode() { return verbose; } -/** - * @brief Builds a dependency graph tracking value-to-user relationships within a module. - * - * This function constructs a graph where each entry maps a Value to the set of Operations - * that use it as an operand. It skips certain operation types (returns, constants, memory grants) - * that don't contribute to data flow dependencies. - * - * @param module The MLIR ModuleOp to analyze for value dependencies - * @return void - */ -void buildDependencyGraph(ModuleOp module) { - - value_users.clear(); - - // Traverse all operations in the module - module.walk([&](Operation* op) { - if (isa(op) || isa(op)) { - return; - } - - if (isa(op) || - isa(op) || - isa(op) || - isa(op) || - isa(op)) { - return; - } - - // Record each operand's relationship with the current operation - for (Value operand : op->getOperands()) { - value_users[operand].insert(op); - } - }); +// /** +// * @brief Adds an operation to the pending operation queue if it's not already present. +// * +// * This function checks if the given operation is valid and not already in the pending operation queue +// * before adding it. It also maintains a flag to track presence in the pending operation queue for efficiency. +// * Verbose mode will log the addition of operations. +// * +// * @param op The Operation to be added to the pending operation queue. +// * @return void +// */ +// void addToWorkList(Operation* op) { +// if (op == nullptr) +// return; +// if (is_operation_enqueued.lookup(op)) +// return; + +// pending_operation_queue.push_back(op); +// is_operation_enqueued[op] = true; - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Dependency Graph:\n"; - for (auto& entry : value_users) { - llvm::outs() << "[neura-interpreter] Value: "; - entry.first.print(llvm::outs()); - llvm::outs() << " -> Users: "; - for (auto* user_op : entry.second) { - llvm::outs() << user_op->getName() << ", "; - } - llvm::outs() << "\n"; - } - } -} - -/** - * @brief Adds an operation to the work list if it's not already present. - * - * This function checks if the given operation is valid and not already in the work list - * before adding it. It also maintains a flag to track presence in the work list for efficiency. - * Verbose mode will log the addition of operations. - * - * @param op The Operation to be added to the work list. - * @return void - */ -void addToWorkList(Operation* op) { - if (op == nullptr) - return; - if (in_work_list.lookup(op)) - return; - - work_list.push_back(op); - in_work_list[op] = true; - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] work_list Added: " << op->getName() << "\n"; - } -} +// if (isVerboseMode()) { +// llvm::outs() << "[neura-interpreter] pending_operation_queue Added: " << op->getName() << "\n"; +// } +// } /** * @brief Handles the execution of an arithmetic constant operation (arith.constant) by parsing its value and storing it in the value map. @@ -296,11 +274,11 @@ void addToWorkList(Operation* op) { * attribute, converts it to a floating-point representation (supporting floats, integers, and booleans), and stores it in the value map with a * predicate set to true (since constants are always valid). Unsupported constant types result in an error. * - * @param op The arith.constant operation to handle - * @param value_map Reference to the map where the parsed constant value will be stored, keyed by the operation's result value - * @return bool True if the constant is successfully parsed and stored; false if the constant type is unsupported + * @param op The arith.constant operation to handle + * @param value_to_predicated_data_map Reference to the map where the parsed constant value will be stored, keyed by the operation's result value + * @return bool True if the constant is successfully parsed and stored; false if the constant type is unsupported */ -bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_map) { +bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_to_predicated_data_map) { auto attr = op.getValue(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing arith.constant:\n"; @@ -340,8 +318,8 @@ bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_map) { +bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap& value_to_predicated_data_map) { auto attr = op.getValue(); if (isVerboseMode()) { @@ -373,8 +351,8 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap(attr)) { @@ -387,8 +365,8 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap(attr)) { @@ -413,8 +391,8 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap &value_map) { +bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.add:\n"; } @@ -454,8 +432,8 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_m return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -468,7 +446,7 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_m bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -483,7 +461,7 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_m result.predicate = final_predicate; result.is_vector = false; - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; @@ -500,11 +478,11 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_m * predicates of all operands (including the optional predicate if present), and stores the result in the value map. * The operation requires at least two operands; fewer will result in an error. * - * @param op The neura.sub operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the subtraction is successfully computed; false if there are fewer than 2 operands + * @param op The neura.sub operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the subtraction is successfully computed; false if there are fewer than 2 operands */ -bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_map) { +bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sub:\n"; } @@ -516,8 +494,8 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_m return false; } - auto lhs = value_map[op.getOperand(0)]; - auto rhs = value_map[op.getOperand(1)]; + auto lhs = value_to_predicated_data_map[op.getOperand(0)]; + auto rhs = value_to_predicated_data_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -528,7 +506,7 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_m bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -549,7 +527,7 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_m llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -561,11 +539,11 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_m * (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; * fewer will result in an error. * - * @param op The neura.fadd operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point addition is successfully computed; false if there are fewer than 2 operands + * @param op The neura.fadd operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point addition is successfully computed; false if there are fewer than 2 operands */ -bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value_map) { +bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd:\n"; } @@ -577,8 +555,8 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { @@ -588,7 +566,7 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value } if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -605,7 +583,7 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -617,11 +595,11 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; * fewer will result in an error. * - * @param op The neura.fsub operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point subtraction is successfully computed; false if there are fewer than 2 operands + * @param op The neura.fsub operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point subtraction is successfully computed; false if there are fewer than 2 operands */ -bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value_map) { +bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fsub:\n"; } @@ -633,8 +611,8 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { @@ -644,7 +622,7 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value } if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -661,7 +639,7 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -673,11 +651,11 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; * fewer will result in an error. * - * @param op The neura.fmul operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point multiplication is successfully computed; false if there are fewer than 2 operands + * @param op The neura.fmul operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point multiplication is successfully computed; false if there are fewer than 2 operands */ -bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value_map) { +bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul:\n"; } @@ -689,8 +667,8 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getOperand(0)]; - auto rhs = value_map[op.getOperand(1)]; + auto lhs = value_to_predicated_data_map[op.getOperand(0)]; + auto rhs = value_to_predicated_data_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -701,7 +679,7 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -722,7 +700,7 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -734,11 +712,11 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value * combines the predicates of all operands (including the optional predicate if present), and stores the result in the value map. The operation requires * at least two operands; fewer will result in an error. * - * @param op The neura.fdiv operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point division is successfully computed (including division by zero cases); false if there are fewer than 2 operands + * @param op The neura.fdiv operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the floating-point division is successfully computed; false if there are fewer than 2 operands */ -bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value_map) { +bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fdiv:\n"; } @@ -750,8 +728,8 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getOperand(0)]; - auto rhs = value_map[op.getOperand(1)]; + auto lhs = value_to_predicated_data_map[op.getOperand(0)]; + auto rhs = value_to_predicated_data_map[op.getOperand(1)]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -762,7 +740,7 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -793,7 +771,7 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -805,11 +783,11 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value * combines the predicates of all operands (including the optional scalar predicate if present), and stores the resulting vector in the value map. * Errors are returned for invalid operand types (non-vectors), size mismatches, or vector predicates. * - * @param op The neura.vfmul operation to handle - * @param value_map Reference to the map where the resulting vector will be stored, keyed by the operation's result value - * @return bool True if the vector multiplication is successfully computed; false if there are invalid operands, size mismatches, or other errors + * @param op The neura.vfmul operation to handle + * @param value_to_predicated_data_map Reference to the map where the resulting vector will be stored, keyed by the operation's result value + * @return bool True if the vector multiplication is successfully computed; false if there are invalid operands, size mismatches, or other errors */ -bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &value_map) { +bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.vfmul:\n"; } @@ -821,8 +799,8 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; if (!lhs.is_vector || !rhs.is_vector) { if (isVerboseMode()) { @@ -861,7 +839,7 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; if (pred.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Predicate operand must be a scalar in neura.vfmul\n"; @@ -890,7 +868,7 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val llvm::outs() << ", [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -902,11 +880,11 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val * (including the optional predicate if present), and stores the result in the value map. The operation requires at least three operands; * fewer will result in an error. * - * @param op The neura.fadd_fadd operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the chained floating-point addition is successfully computed; false if there are fewer than 3 operands + * @param op The neura.fadd_fadd operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool True if the chained floating-point addition is successfully computed; false if there are fewer than 3 operands */ -bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_map) { +bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd_fadd:\n"; } @@ -918,9 +896,9 @@ bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap 3) { - auto pred_operand = value_map[op.getOperand(3)]; + auto pred_operand = value_to_predicated_data_map[op.getOperand(3)]; final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -959,7 +937,7 @@ bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_map) { +bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul_fadd:\n"; } @@ -986,9 +964,9 @@ bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap 3) { - auto pred_operand = value_map[op.getOperand(3)]; + auto pred_operand = value_to_predicated_data_map[op.getOperand(3)]; final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1028,7 +1006,7 @@ bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &value_map) { +bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing func.return:\n"; } @@ -1058,7 +1036,7 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap return true; } - auto result = value_map[op.getOperand(0)]; + auto result = value_to_predicated_data_map[op.getOperand(0)]; // Print vector return value if the result is a vector if (result.is_vector) { llvm::outs() << "[neura-interpreter] → Output: ["; @@ -1084,11 +1062,11 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap * combines the predicates of all operands (including the optional predicate if present), and stores the result as a boolean scalar (1.0f for true, 0.0f for false) * in the value map. Errors are returned for insufficient operands or unsupported comparison types. * - * @param op The neura.fcmp operation to handle - * @param value_map Reference to the map where the comparison result will be stored, keyed by the operation's result value - * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + * @param op The neura.fcmp operation to handle + * @param value_to_predicated_data_map Reference to the map where the comparison result will be stored, keyed by the operation's result value + * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type */ -bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value_map) { +bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fcmp:\n"; } @@ -1099,8 +1077,8 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -1112,7 +1090,7 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value bool pred = true; if (op.getNumOperands() > 2) { - auto pred_data = value_map[op.getPredicate()]; + auto pred_data = value_to_predicated_data_map[op.getPredicate()]; pred = pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1160,7 +1138,7 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value << result_value << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1173,11 +1151,11 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value * the result as a boolean scalar (1.0f for true, 0.0f for false) in the value map. Errors are returned for insufficient operands or unsupported * comparison types. * - * @param op The neura.icmp operation to handle - * @param value_map Reference to the map where the comparison result will be stored, keyed by the operation's result value - * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + * @param op The neura.icmp operation to handle + * @param value_to_predicated_data_map Reference to the map where the comparison result will be stored, keyed by the operation's result value + * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type */ -bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value_map) { +bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.icmp:\n"; } @@ -1188,8 +1166,8 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value return false; } - auto lhs = value_map[op.getLhs()]; - auto rhs = value_map[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -1201,7 +1179,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value bool pred = true; if (op.getNumOperands() > 2) { - auto pred_data = value_map[op.getPredicate()]; + auto pred_data = value_to_predicated_data_map[op.getPredicate()]; pred = pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1283,76 +1261,10 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } -// bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map) { -// if (isVerboseMode()) { -// llvm::outs() << "[neura-interpreter] Executing neura.or:\n"; -// } - -// if (op.getNumOperands() < 2) { -// if (isVerboseMode()) { -// llvm::errs() << "[neura-interpreter] └─ neura.or expects at least two operands\n"; -// } -// return false; -// } - -// auto lhs = value_map[op.getOperand(0)]; -// auto rhs = value_map[op.getOperand(1)]; - -// if (lhs.is_vector || rhs.is_vector) { -// if (isVerboseMode()) { -// llvm::errs() << "[neura-interpreter] └─ neura.or requires scalar operands\n"; -// } -// return false; -// } - -// if (isVerboseMode()) { -// llvm::outs() << "[neura-interpreter] ├─ Operands \n"; -// llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value << ", [pred = " << lhs.predicate << "]\n"; -// llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << ", [pred = " << rhs.predicate << "]\n"; -// } - -// int64_t lhs_int = static_cast(std::round(lhs.value)); -// int64_t rhs_int = static_cast(std::round(rhs.value)); -// int64_t result_int = lhs_int | rhs_int; - -// bool final_predicate = lhs.predicate && rhs.predicate; -// if (op.getNumOperands() > 2) { -// auto pred = value_map[op.getOperand(2)]; -// final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); -// llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; -// llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value -// << ", [pred = " << pred.predicate << "]\n"; -// } - -// if (isVerboseMode()) { -// llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; -// llvm::outs() << "[neura-interpreter] │ └─ Bitwise OR : " << lhs_int; -// if (lhs_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; -// llvm::outs() << " | " << rhs_int; -// if (rhs_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; -// llvm::outs() << " = " << result_int; -// if (result_int == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; -// llvm::outs() << "\n"; -// } - -// PredicatedData result; -// result.value = static_cast(result_int); -// result.predicate = final_predicate; -// result.is_vector = false; - -// if (isVerboseMode()) { -// llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value -// << ", [pred = " << final_predicate << "]\n"; -// } - -// value_map[op.getResult()] = result; -// return true; -// } - /** * @brief Handles the execution of a Neura logical OR operation (neura.or) for scalar boolean values. * @@ -1361,11 +1273,11 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value * as a predicate to further validate the result. The operation requires scalar operands (no vectors) and returns * a scalar result with a combined validity predicate. * - * @param op The neura.or operation to handle (modified for logical OR) - * @param value_map Reference to the map storing operands and where the result will be stored - * @return bool True if the logical OR is successfully executed; false for invalid operands (e.g., vectors, insufficient count) + * @param op The neura.or operation to handle (modified for logical OR) + * @param value_to_predicated_data_map Reference to the map storing operands and where the result will be stored + * @return bool True if the logical OR is successfully executed; false for invalid operands (e.g., vectors, insufficient count) */ -bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map) { +bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.or (logical OR):\n"; } @@ -1378,8 +1290,8 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map } // Retrieve left and right operands - auto lhs = value_map[op.getOperand(0)]; - auto rhs = value_map[op.getOperand(1)]; + auto lhs = value_to_predicated_data_map[op.getOperand(0)]; + auto rhs = value_to_predicated_data_map[op.getOperand(1)]; if (lhs.is_vector || rhs.is_vector) { if (isVerboseMode()) { @@ -1405,7 +1317,7 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map // Compute final validity predicate (combines operand predicates and optional predicate) bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { - auto pred = value_map[op.getOperand(2)]; + auto pred = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred.predicate && (pred.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1430,7 +1342,7 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map << " (boolean: " << (result_bool ? "true" : "false") << "), [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1442,12 +1354,12 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_map * and stores the result as a floating-point value (1.0f for true, 0.0f for false) in the value map. The result's predicate is inherited * from the input operand's predicate. * - * @param op The neura.not operation to handle - * @param value_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool Always returns true as the operation is guaranteed to execute successfully with valid input + * @param op The neura.not operation to handle + * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value + * @return bool Always returns true as the operation is guaranteed to execute successfully with valid input */ -bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_map) { - auto input = value_map[op.getOperand()]; +bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_to_predicated_data_map) { + auto input = value_to_predicated_data_map[op.getOperand()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.not:\n"; @@ -1477,7 +1389,7 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_m << ", [pred = " << result.predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1489,11 +1401,11 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_m * selects the corresponding value (either "if_true" or "if_false"), and combines the predicate of the condition with the predicate of the selected value. * The result is marked as a vector only if both input values are vectors. Errors are returned if the operand count is not exactly 3. * - * @param op The neura.sel operation to handle - * @param value_map Reference to the map where the selected result will be stored, keyed by the operation's result value - * @return bool True if the selection is successfully computed; false if the operand count is invalid + * @param op The neura.sel operation to handle + * @param value_to_predicated_data_map Reference to the map where the selected result will be stored, keyed by the operation's result value + * @return bool True if the selection is successfully computed; false if the operand count is invalid */ -bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_map) { +bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sel:\n"; } @@ -1505,9 +1417,9 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_m return false; } - auto cond = value_map[op.getCond()]; /* Condition to evaluate */ - auto if_true = value_map[op.getIfTrue()]; /* Value if condition is true */ - auto if_false = value_map[op.getIfFalse()]; /* Value if condition is false */ + auto cond = value_to_predicated_data_map[op.getCond()]; /* Condition to evaluate */ + auto if_true = value_to_predicated_data_map[op.getIfTrue()]; /* Value if condition is true */ + auto if_false = value_to_predicated_data_map[op.getIfFalse()]; /* Value if condition is false */ // Evaluate the condition: true if the value is non-zero and its predicate is true bool cond_value = (cond.value != 0.0f) && cond.predicate; @@ -1545,7 +1457,7 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_m << ", predicate = " << result.predicate << "\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1557,11 +1469,11 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_m * The result's predicate is combined with the optional predicate operand (if present), and the result inherits the input's vector flag. Errors are returned for * invalid operand counts, unsupported conversion types, or mismatched input types for the specified conversion. * - * @param op The neura.cast operation to handle - * @param value_map Reference to the map where the converted result will be stored, keyed by the operation's result value - * @return bool True if the conversion is successfully computed; false for invalid operands, unsupported types, or mismatched input types + * @param op The neura.cast operation to handle + * @param value_to_predicated_data_map Reference to the map where the converted result will be stored, keyed by the operation's result value + * @return bool True if the conversion is successfully computed; false for invalid operands, unsupported types, or mismatched input types */ -bool handleCastOp(neura::CastOp op, llvm::DenseMap &value_map) { +bool handleCastOp(neura::CastOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cast:\n"; } @@ -1572,7 +1484,7 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value return false; } - auto input = value_map[op.getOperand(0)]; + auto input = value_to_predicated_data_map[op.getOperand(0)]; std::string cast_type = op.getCastType().str(); if (isVerboseMode()) { @@ -1583,7 +1495,7 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value bool final_predicate = input.predicate; if (op.getOperation()->getNumOperands() > 1) { - auto pred_operand = value_map[op.getOperand(1)]; + auto pred_operand = value_to_predicated_data_map[op.getOperand(1)]; final_predicate = final_predicate && pred_operand.predicate && (pred_operand.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1670,7 +1582,7 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1682,12 +1594,12 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value * is skipped if the combined predicate (input address predicate + optional predicate operand) is false, returning a default value of 0.0f. Errors are * returned for invalid operand counts or unsupported data types. * - * @param op The neura.load operation to handle - * @param value_map Reference to the map where the loaded value will be stored, keyed by the operation's result value - * @param mem Reference to the memory object used to read the value - * @return bool True if the load is successfully executed (including skipped loads); false for invalid operands or unsupported types + * @param op The neura.load operation to handle + * @param value_to_predicated_data_map Reference to the map where the loaded value will be stored, keyed by the operation's result value + * @param mem Reference to the memory object used to read the value + * @return bool True if the load is successfully executed (including skipped loads); false for invalid operands or unsupported types */ -bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value_map, Memory &mem) { +bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.load:\n"; } @@ -1699,11 +1611,11 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value return false; } // Convert address from float to size_t (memory address type) - auto addr_val = value_map[op.getOperand(0)]; + auto addr_val = value_to_predicated_data_map[op.getOperand(0)]; bool final_predicate = addr_val.predicate; if (op.getNumOperands() > 1) { - auto pred_val = value_map[op.getOperand(1)]; + auto pred_val = value_to_predicated_data_map[op.getOperand(1)]; final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1741,7 +1653,7 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value << val << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = { val, final_predicate }; + value_to_predicated_data_map[op.getResult()] = { val, final_predicate }; return true; } @@ -1753,12 +1665,12 @@ bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value * optional predicate operand) is true. Supported types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). The operation * is skipped if the predicate is false. Errors are returned for insufficient operands or unsupported data types. * - * @param op The neura.store operation to handle - * @param value_map Reference to the map storing the value and address to be used for the store - * @param mem Reference to the memory object used to write the value - * @return bool True if the store is successfully executed (including skipped stores); false for invalid operands or unsupported types + * @param op The neura.store operation to handle + * @param value_to_predicated_data_map Reference to the map storing the value and address to be used for the store + * @param mem Reference to the memory object used to write the value + * @return bool True if the store is successfully executed; false for invalid operands or unsupported types */ -bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &value_map, Memory &mem) { +bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.store:\n"; } @@ -1770,12 +1682,12 @@ bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &val return false; } - auto val_data = value_map[op.getOperand(0)]; /* Value to store */ - auto addr_val = value_map[op.getOperand(1)]; /* Target address */ + auto val_data = value_to_predicated_data_map[op.getOperand(0)]; /* Value to store */ + auto addr_val = value_to_predicated_data_map[op.getOperand(1)]; /* Target address */ bool final_predicate = addr_val.predicate; /* Base predicate from address validity */ if (op.getNumOperands() > 2) { - auto pred_val = value_map[op.getOperand(2)]; + auto pred_val = value_to_predicated_data_map[op.getOperand(2)]; final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -1830,11 +1742,11 @@ bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &val * - Combines predicates from base address and optional predicate operand * - Returns the final address (base + offset) with the combined predicate * - * @param op The neura.gep operation to handle - * @param value_map Reference to the map storing predicated data for values (base address, indices, predicate) - * @return bool True if the address is successfully computed; false for invalid operands, missing strides, or mismatched indices/strides + * @param op The neura.gep operation to handle + * @param value_to_predicated_data_map Reference to the map storing predicated data for values (base address, indices, predicate) + * @return bool True if the address is successfully computed; false for invalid operands, missing strides, or mismatched indices/strides */ -bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map) { +bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.gep:\n"; } @@ -1846,7 +1758,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map return false; } - auto base_val = value_map[op.getOperand(0)]; + auto base_val = value_to_predicated_data_map[op.getOperand(0)]; size_t base_addr = static_cast(base_val.value); bool final_predicate = base_val.predicate; @@ -1898,7 +1810,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map // Calculate total offset by scaling each index with its stride and summing size_t offset = 0; for (unsigned i = 0; i < index_count; ++i) { - auto idx_val = value_map[op.getOperand(i + 1)]; + auto idx_val = value_to_predicated_data_map[op.getOperand(i + 1)]; if (!idx_val.predicate) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ GEP index " << i << " has false predicate\n"; @@ -1915,7 +1827,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map } if (has_predicate) { - auto pred_val = value_map[op.getOperand(num_operands - 1)]; + auto pred_val = value_to_predicated_data_map[op.getOperand(num_operands - 1)]; final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Predicate operand: value = " << pred_val.value @@ -1936,7 +1848,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = result; + value_to_predicated_data_map[op.getResult()] = result; return true; } @@ -1947,20 +1859,20 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_map * It supports scalar values only (no vectors) for the base address, indices, and predicate. The operation loads data from the computed address if the combined * predicate (validity of base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). * - * @param op The neura.load_indexed operation to handle - * @param value_map Reference to the map storing values (base address, indices, predicate) and where the loaded result will be stored - * @param mem Reference to the memory object used to read the value - * @return bool True if the indexed load is successfully executed (including skipped loads); false for vector inputs, unsupported types, or errors + * @param op The neura.load_indexed operation to handle + * @param value_to_predicated_data_map Reference to the map storing values (base address, indices, predicate) and where the loaded result will be stored + * @param mem Reference to the memory object used to read the value + * @return bool True if the indexed load is successfully executed; false for vector inputs, unsupported types, or errors */ bool handleLoadIndexedOp(neura::LoadIndexedOp op, - llvm::DenseMap &value_map, + llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Executing neura.load_indexed:\n"; } // Retrieve base address and validate it is not a vector - auto base_val = value_map[op.getBase()]; + auto base_val = value_to_predicated_data_map[op.getBase()]; if (base_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector base not supported in load_indexed\n"; @@ -1975,7 +1887,7 @@ bool handleLoadIndexedOp(neura::LoadIndexedOp op, // Todo: multi-dimensional index will be supported in the future float offset = 0.0f; for (Value idx : op.getIndices()) { - auto idx_val = value_map[idx]; + auto idx_val = value_to_predicated_data_map[idx]; if (idx_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector index not supported in load_indexed\n"; @@ -1990,7 +1902,7 @@ bool handleLoadIndexedOp(neura::LoadIndexedOp op, // Incorporate optional predicate operand (validate it is not a vector) if (op.getPredicate()) { Value pred_operand = op.getPredicate(); - auto pred_val = value_map[pred_operand]; + auto pred_val = value_to_predicated_data_map[pred_operand]; if (pred_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; @@ -2026,7 +1938,7 @@ bool handleLoadIndexedOp(neura::LoadIndexedOp op, << val << ", [pred = " << final_predicate << "]\n"; } - value_map[op.getResult()] = { val, final_predicate, false, {}, false }; + value_to_predicated_data_map[op.getResult()] = { val, final_predicate, false, {}, false }; return true; } @@ -2037,19 +1949,19 @@ bool handleLoadIndexedOp(neura::LoadIndexedOp op, * It supports scalar values only (no vectors) for the value to store, base address, indices, and predicate. The operation performs the store only if the combined predicate (validity of the value, * base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). * - * @param op The neura.store_indexed operation to handle - * @param value_map Reference to the map storing the value to store, base address, indices, and predicate - * @param mem Reference to the memory object used to write the value - * @return bool True if the indexed store is successfully executed (including skipped stores); false for vector inputs, unsupported types, or errors + * @param op The neura.store_indexed operation to handle + * @param value_to_predicated_data_map Reference to the map storing the value to store, base address, indices, and predicate + * @param mem Reference to the memory object used to write the value + * @return bool True if the indexed store is successfully executed; false for vector inputs, unsupported types, or errors */ bool handleStoreIndexedOp(neura::StoreIndexedOp op, - llvm::DenseMap &value_map, + llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.store_indexed:\n"; } - auto val_to_store = value_map[op.getValue()]; + auto val_to_store = value_to_predicated_data_map[op.getValue()]; if (val_to_store.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector value not supported in store_indexed\n"; @@ -2059,7 +1971,7 @@ bool handleStoreIndexedOp(neura::StoreIndexedOp op, float value = val_to_store.value; bool final_predicate = val_to_store.predicate; - auto base_val = value_map[op.getBase()]; + auto base_val = value_to_predicated_data_map[op.getBase()]; if (base_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector base not supported in store_indexed\n"; @@ -2075,7 +1987,7 @@ bool handleStoreIndexedOp(neura::StoreIndexedOp op, // Todo: multi-dimensional index will be supported in the future float offset = 0.0f; for (Value idx : op.getIndices()) { - auto idx_val = value_map[idx]; + auto idx_val = value_to_predicated_data_map[idx]; if (idx_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector index not supported in store_indexed\n"; @@ -2088,7 +2000,7 @@ bool handleStoreIndexedOp(neura::StoreIndexedOp op, if (op.getPredicate()) { Value pred_operand = op.getPredicate(); - auto pred_val = value_map[pred_operand]; + auto pred_val = value_to_predicated_data_map[pred_operand]; if (pred_val.is_vector) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; @@ -2131,13 +2043,13 @@ bool handleStoreIndexedOp(neura::StoreIndexedOp op, * It validates the target block exists, ensures the number of branch arguments matches the target block's parameters, and copies argument * values to the target's parameters. Finally, it updates the current and last visited blocks to reflect the control transfer. * - * @param op The neura.br operation to handle - * @param value_map Reference to the map storing branch arguments and where target parameters will be updated - * @param current_block Reference to the current block; updated to the target block after branch - * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch - * @return bool True if the branch is successfully executed; false for invalid target block or argument/parameter mismatch + * @param op The neura.br operation to handle + * @param value_to_predicated_data_map Reference to the map storing branch arguments and where target parameters will be updated + * @param current_block Reference to the current block; updated to the target block after branch + * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch + * @return bool True if the branch is successfully executed; false for invalid target block or argument/parameter mismatch */ -bool handleBrOp(neura::Br op, llvm::DenseMap &value_map, +bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_predicated_data_map, Block *¤t_block, Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.br:\n"; @@ -2187,7 +2099,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_map, Value dest_param = dest_params[i]; Value src_arg = args[i]; - if (!value_map.count(src_arg)) { + if (!value_to_predicated_data_map.count(src_arg)) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.br: Argument " << i << " (source value) not found in value map\n"; @@ -2196,13 +2108,13 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_map, } // Transfer argument value to target parameter - value_map[dest_param] = value_map[src_arg]; + value_to_predicated_data_map[dest_param] = value_to_predicated_data_map[src_arg]; if (isVerboseMode() && i < dest_params.size() - 1) { llvm::outs() << "[neura-interpreter] │ ├─ Param[" << i << "]: value = " - << value_map[src_arg].value << "\n"; + << value_to_predicated_data_map[src_arg].value << "\n"; } else if (isVerboseMode() && i == dest_params.size() - 1) { llvm::outs() << "[neura-interpreter] │ └─ Param[" << i << "]: value = " - << value_map[src_arg].value << "\n"; + << value_to_predicated_data_map[src_arg].value << "\n"; } } @@ -2223,13 +2135,13 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_map, * It validates the operation's operands (1 mandatory condition + 1 optional predicate), checks that the condition is a boolean (i1 type), and computes a final predicate to determine if the branch is valid. * If valid, it selects the target block based on the condition's value, passes arguments to the target block's parameters, and updates the current and last visited blocks to reflect the control transfer. * - * @param op The neura.cond_br operation to handle - * @param value_map Reference to the map storing values (including the condition and optional predicate) - * @param current_block Reference to the current block; updated to the target block after branch - * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch - * @return bool True if the branch is successfully executed; false for invalid operands, missing values, type mismatches, or invalid predicates + * @param op The neura.cond_br operation to handle + * @param value_to_predicated_data_map Reference to the map storing values (including the condition and optional predicate) + * @param current_block Reference to the current block; updated to the target block after branch + * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch + * @return bool True if the branch is successfully executed; false for invalid operands, missing values, type mismatches, or invalid predicates */ -bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &value_map, +bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &value_to_predicated_data_map, Block *¤t_block, Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cond_br:\n"; @@ -2243,13 +2155,13 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } auto cond_value = op.getCondition(); - if (!value_map.count(cond_value)) { + if (!value_to_predicated_data_map.count(cond_value)) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ cond_br: condition value not found in value_map! (SSA name missing)\n"; + llvm::errs() << "[neura-interpreter] └─ cond_br: condition value not found in value_to_predicated_data_map! (SSA name missing)\n"; } return false; } - auto cond_data = value_map[op.getCondition()]; + auto cond_data = value_to_predicated_data_map[op.getCondition()]; if (!op.getCondition().getType().isInteger(1)) { if (isVerboseMode()) { @@ -2267,7 +2179,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val // Compute final predicate (combines condition's predicate and optional predicate operand) bool final_predicate = cond_data.predicate; if (op.getNumOperands() > 1) { - auto pred_data = value_map[op.getPredicate()]; + auto pred_data = value_to_predicated_data_map[op.getPredicate()]; final_predicate = final_predicate && pred_data.predicate && (pred_data.value != 0.0f); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; @@ -2319,7 +2231,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val return false; } - // Pass branch arguments to target block parameters (update value_map) + // Pass branch arguments to target block parameters (update value_to_predicated_data_map) if (!branch_args.empty()) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; @@ -2327,16 +2239,16 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } for (size_t i = 0; i < branch_args.size(); ++i) { - value_map[target_params[i]] = value_map[branch_args[i]]; + value_to_predicated_data_map[target_params[i]] = value_to_predicated_data_map[branch_args[i]]; if (i < branch_args.size() - 1) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ ├─ param[" << i << "]: value = " - << value_map[branch_args[i]].value << "\n"; + << value_to_predicated_data_map[branch_args[i]].value << "\n"; } } else { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ param[" << i << "]: value = " - << value_map[branch_args[i]].value << "\n"; + << value_to_predicated_data_map[branch_args[i]].value << "\n"; } } } @@ -2354,235 +2266,219 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } /** - * @brief Handles the execution of a Neura phi operation (neura.phi) in control flow mode, selecting the correct input based on the execution path. - * - * This function processes Neura's phi operations, which act as control flow merge points by selecting one of several input values based on the predecessor block - * that was most recently visited. It identifies the predecessor block that matches the last visited block, retrieves the corresponding input value, and assigns - * it to the phi operation's result. Validations ensure the current block has predecessors, the last visited block is a valid predecessor, and input count matches - * predecessor count. - * - * @param op The neura.phi operation to handle - * @param value_map Reference to the map storing input values and where the phi result will be stored - * @param current_block The block containing the phi operation (current execution block) - * @param last_visited_block The most recently visited predecessor block (determines which input to select) - * @return bool True if the phi operation is successfully processed; false for validation errors (e.g., missing predecessors, mismatched inputs) + * @brief Unified handler for Neura phi operations, supporting both control flow and data flow modes. + * + * In ControlFlow mode: Selects the input based on the most recently visited predecessor block, + * requiring validation of block relationships and input-predecessor count matching. + * + * In DataFlow mode: Merges inputs by selecting the first valid (true predicate) input. Falls back to + * the first input with a false predicate if no valid inputs exist. Tracks result updates for propagation. + * + * @param op The neura.phi operation to process + * @param value_to_predicated_data_map Reference to the map storing input values and where the result will be stored + * @param current_block [ControlFlow only] Block containing the phi operation (nullptr for DataFlow) + * @param last_visited_block [ControlFlow only] Most recently visited predecessor block (nullptr for DataFlow) + * @return bool True if processing succeeds; false for validation failures in ControlFlow mode (always true for DataFlow) */ -bool handlePhiOpControlFlowMode(neura::PhiOp op, llvm::DenseMap &value_map, - Block *current_block, Block *last_visited_block) { +bool handlePhiOp(neura::PhiOp op, + llvm::DenseMap &value_to_predicated_data_map, + Block *current_block = nullptr, + Block *last_visited_block = nullptr) { if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; - } - - // Get all predecessor blocks of the current block (possible execution paths leading to this phi) - auto predecessors_range = current_block->getPredecessors(); - std::vector predecessors(predecessors_range.begin(), predecessors_range.end()); - size_t pred_count = predecessors.size(); - - // Validate current block has at least one predecessor (phi requires merge of paths) - if (pred_count == 0) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Current block has no predecessors\n"; - } - return false; - } - - // Find the index of the last visited block among the predecessors (determines which input to use) - size_t pred_index = 0; - bool found = false; - for (auto pred : predecessors) { - if (pred == last_visited_block) { - found = true; - break; + if (!isDataflowMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; + } else { + llvm::outs() << "[neura-interpreter] Executing neura.phi(dataflow):\n"; } - ++pred_index; - } - - // Validate the last visited block is a valid predecessor of the current block - if (!found) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\n"; - } - return false; } auto inputs = op.getInputs(); size_t input_count = inputs.size(); - - // Validate input count matches predecessor count (one input per path) - if (input_count != pred_count) { + if (input_count == 0) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Input count (" << input_count - << ") != predecessor count (" << pred_count << ")\n"; + llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; } - return false; + return false; // No inputs is a failure in both modes } - // Validate the predecessor index is within the valid range of inputs - if (pred_index >= input_count) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << pred_index << ")\n"; + // Store the finally selected input data + PredicatedData selected_input_data; + bool selection_success = false; + + // -------------------------- + // Mode-specific logic: Input selection and validation + // -------------------------- + if (!isDataflowMode()) { + // ControlFlow mode: Validate required block parameters + if (!current_block || !last_visited_block) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ ControlFlow mode requires current_block and last_visited_block\n"; + } + return false; } - return false; - } - Value input_val = inputs[pred_index]; - if (!value_map.count(input_val)) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Input value not found in value map\n"; + // ControlFlow mode: Get predecessors and validate count + auto predecessors_range = current_block->getPredecessors(); + std::vector predecessors(predecessors_range.begin(), predecessors_range.end()); + size_t pred_count = predecessors.size(); + if (pred_count == 0) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Current block has no predecessors\n"; + } + return false; } - return false; - } - // Assign the selected input's data to the phi operation's result - PredicatedData input_data = value_map[input_val]; - value_map[op.getResult()] = input_data; + // ControlFlow mode: Validate input count matches predecessor count + if (input_count != pred_count) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Input count (" << input_count + << ") != predecessor count (" << pred_count << ")\n"; + } + return false; + } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Predecessor blocks (" << pred_count << ")\n"; - for (size_t i = 0; i < pred_count; ++i) { - if(i < pred_count - 1) { - llvm::outs() << "[neura-interpreter] │ ├─ [" << i << "]: " << "Block@" << predecessors[i]; - } else { - llvm::outs() << "[neura-interpreter] │ └─ [" << i << "]: " << "Block@" << predecessors[i]; + // ControlFlow mode: Find index of last visited block among predecessors + size_t pred_index = 0; + bool found = false; + for (auto pred : predecessors) { + if (pred == last_visited_block) { + found = true; + break; } - if (i == pred_index) { - llvm::outs() << " (→ current path)\n"; - } else { - llvm::outs() << "\n"; + ++pred_index; + } + if (!found) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\n"; } + return false; } - llvm::outs() << "[neura-interpreter] └─ Result : " << op.getResult() << "\n"; - llvm::outs() << "[neura-interpreter] └─ Value : " << input_data.value - << ", [Pred = " << input_data.predicate << "]\n"; - } - return true; -} -/** - * @brief Handles the execution of a Neura phi operation (neura.phi) in dataflow mode, merging input values based on their validity. - * - * This function processes Neura's phi operations under dataflow analysis, focusing on merging values from multiple input paths. - * It selects the first valid input (with a true predicate) as the result. If no valid inputs exist, it falls back to the first input - * but marks its predicate as false. Additionally, it tracks whether the result has changed from its previous state (via `is_updated`) - * to support dataflow propagation of changes. - * - * Key dataflow-specific behaviors: - * - Prioritizes inputs with valid predicates (true) when merging paths. - * - Explicitly tracks updates to the result to trigger reprocessing of dependent operations. - * - Gracefully handles missing or invalid inputs by falling back to the first input with an invalid predicate. - * - * @param op The neura.phi operation to handle - * @param value_map Reference to the map storing input values and where the merged result will be stored - * @return bool Always returns true, even if no valid inputs are found (handles partial success gracefully) - */ -bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &value_map) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.phi(dataflow):\n"; - } - - auto inputs = op.getInputs(); - size_t input_count = inputs.size(); + // ControlFlow mode: Validate index and retrieve selected input + if (pred_index >= input_count) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << pred_index << ")\n"; + } + return false; + } + Value selected_input = inputs[pred_index]; + if (!value_to_predicated_data_map.count(selected_input)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Selected input not found in value map\n"; + } + return false; + } + selected_input_data = value_to_predicated_data_map[selected_input]; + selection_success = true; - if (input_count == 0) { + // ControlFlow mode: Log predecessor details if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; + llvm::outs() << "[neura-interpreter] ├─ Predecessor blocks (" << pred_count << ")\n"; + for (size_t i = 0; i < pred_count; ++i) { + if(i < pred_count - 1) { + llvm::outs() << "[neura-interpreter] │ ├─ [" << i << "]: " << "Block@" << predecessors[i]; + } else { + llvm::outs() << "[neura-interpreter] │ └─ [" << i << "]: " << "Block@" << predecessors[i]; + } + if (i == pred_index) { + llvm::outs() << " (→ current path)\n"; + } else { + llvm::outs() << "\n"; + } + } } - return false; - } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Input values (" << input_count << ")\n"; + } else { // DataFlow mode + // DataFlow mode: Select first valid input (with true predicate) + bool found_valid_input = false; for (size_t i = 0; i < input_count; ++i) { Value input = inputs[i]; - if (value_map.count(input)) { - auto input_data = value_map[input]; - const std::string prefix = (i < input_count - 1) ? "│ ├─" : "│ └─"; - llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]:" - << "value = " << input_data.value << "," - << "pred = " << input_data.predicate << "\n"; - } else { - const std::string prefix = (i < input_count - 1) ? "│ ├─" : "│ └─"; - llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]: \n"; + if (!value_to_predicated_data_map.count(input)) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Input [" << i << "] not found, skipping\n"; + } + continue; } - } - } - - // Initialize result with default values - PredicatedData result; - result.value = 0.0f; - result.predicate = false; - result.is_vector = false; - result.vector_data = {}; - result.is_reserve = false; - result.is_updated = false; - bool found_valid_input = false; - - // Select the first valid input (with true predicate) to use as the result - for (size_t i = 0; i < input_count; ++i) { - Value input = inputs[i]; - - if (!value_map.count(input)) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Input [" << i << "] not found, skipping\n"; + auto input_data = value_to_predicated_data_map[input]; + if (input_data.predicate) { + selected_input_data = input_data; + found_valid_input = true; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Selected input [" << i << "] (latest valid)\n"; + } + break; } - continue; } - auto input_data = value_map[input]; - - // Use the first valid input and stop searching - if (input_data.predicate && !found_valid_input) { - result.value = input_data.value; - result.predicate = input_data.predicate; - result.is_vector = input_data.is_vector; - result.vector_data = input_data.vector_data; - found_valid_input = true; - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Selected input [" << i << "] (latest valid)\n"; + // DataFlow mode: Fallback to first input with false predicate if no valid inputs + if (!found_valid_input) { + Value first_input = inputs[0]; + if (value_to_predicated_data_map.count(first_input)) { + auto first_data = value_to_predicated_data_map[first_input]; + selected_input_data.value = first_data.value; + selected_input_data.is_vector = first_data.is_vector; + selected_input_data.vector_data = first_data.vector_data; + selected_input_data.predicate = false; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ No valid input, using first input with pred=false\n"; + } + } else { + // Edge case: First input is undefined, initialize default values + selected_input_data.value = 0.0f; + selected_input_data.predicate = false; + selected_input_data.is_vector = false; + selected_input_data.vector_data = {}; } - break; } - } - - // Fallback: if no valid inputs, use the first input but mark predicate as false - if (!found_valid_input && input_count > 0) { - Value first_input = inputs[0]; - if (value_map.count(first_input)) { - auto first_data = value_map[first_input]; - result.value = first_data.value; - result.is_vector = first_data.is_vector; - result.vector_data = first_data.vector_data; - result.predicate = false; - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ No valid input, using first input with pred=false\n"; + selection_success = true; // DataFlow mode always considers selection successful + + // DataFlow mode: Log input values + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Input values (" << input_count << ")\n"; + for (size_t i = 0; i < input_count; ++i) { + Value input = inputs[i]; + if (value_to_predicated_data_map.count(input)) { + auto input_data = value_to_predicated_data_map[input]; + const std::string prefix = (i < input_count - 1) ? "│ ├─" : "│ └─"; + llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]:" + << "value = " << input_data.value << "," + << "pred = " << input_data.predicate << "\n"; + } else { + const std::string prefix = (i < input_count - 1) ? "│ ├─" : "│ └─"; + llvm::outs() << "[neura-interpreter] " << prefix << "[" << i << "]: \n"; + } } } } - // Check if the result has changed from its previous state (for dataflow propagation) - bool fields_changed = false; - if (value_map.count(op.getResult())) { - auto old_result = value_map[op.getResult()]; - fields_changed = (result.value != old_result.value) || - (result.predicate != old_result.predicate) || - (result.is_vector != old_result.is_vector) || - (result.vector_data != old_result.vector_data); + // Check for changes + bool is_updated = false; + if (value_to_predicated_data_map.count(op.getResult())) { + auto old_result = value_to_predicated_data_map[op.getResult()]; + is_updated = selected_input_data.isUpdatedComparedTo(old_result); } else { - fields_changed = true; + is_updated = true; } - result.is_updated = fields_changed; - value_map[op.getResult()] = result; + PredicatedData result = selected_input_data; + result.is_updated = is_updated; + result.is_reserve = false; + value_to_predicated_data_map[op.getResult()] = result; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Execution " << (found_valid_input ? "succeeded" : "partially succeeded") - << " | Result: value = " << result.value - << ", pred = " << result.predicate - << ", is_updated = " << result.is_updated << "\n"; + if (!isDataflowMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : " << op.getResult() << "\n"; + llvm::outs() << "[neura-interpreter] └─ Value : " << result.value + << ", [Pred = " << result.predicate << "]\n"; + } else { + llvm::outs() << "[neura-interpreter] └─ Execution " << (selection_success ? "succeeded" : "partially succeeded") + << " | Result: value = " << result.value + << ", pred = " << result.predicate + << ", is_updated = " << result.is_updated << "\n"; + } } - return true; + return selection_success; } /** @@ -2593,11 +2489,11 @@ bool handlePhiOpDataFlowMode(neura::PhiOp op, llvm::DenseMap &value_map) { +bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.reserve:\n"; } @@ -2608,7 +2504,7 @@ bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap placeholder.is_reserve = true; /* Flag to indicate this is a reserved placeholder */ Value result = op.getResult(); - value_map[result] = placeholder; + value_to_predicated_data_map[result] = placeholder; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Created placeholder : " << result << "\n"; @@ -2621,155 +2517,113 @@ bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap } /** - * @brief Handles the execution of a Neura control move operation (neura.ctrl_mov) by copying a source value into a reserved target placeholder. + * @brief Unified handler for Neura control move operations (neura.ctrl_mov) supporting both control-flow and data-flow modes. + * + * This function processes neura.ctrl_mov operations by copying data from a source value to a reserved target placeholder, + * with behavior adjusted via the CtrlMovMode parameter: + * - In ControlFlow mode: Unconditionally copies source data to the target after validating type matching, ensuring strict + * consistency between source and target types. Fails on validation errors (e.g., missing source/target, type mismatch). + * - In DataFlow mode: Conditionally copies data only if the source's predicate is valid (true). Tracks updates via the + * `is_updated` flag (computed by comparing with the target's previous state) to support propagation of changes to dependent operations. * - * This function processes Neura's ctrl_mov operations, which transfer data from a source value to a target placeholder that was previously reserved via a neura.reserve operation. - * It validates that the source exists in the value map, the target is a reserved placeholder, and both have matching types. If valid, it copies the source's value, predicate, vector flag, - * and vector data (if applicable) into the target, updating the reserved placeholder with the source's data. + * Both modes validate that the source exists in the value map and the target is a reserved placeholder (created via neura.reserve). + * Vector/scalar type consistency is preserved during data copying, with vector data copied only if the source is a vector. * - * @param op The neura.ctrl_mov operation to handle - * @param value_map Reference to the map storing both the source value and the target reserved placeholder - * @return bool True if the data is successfully moved to the target; false if validation fails (e.g., missing source, invalid target, type mismatch) + * @param op The neura.ctrl_mov operation to handle + * @param value_to_predicated_data_map Reference to the map storing source/target values and their metadata (value, predicate, type flags) + * @return bool True if processing succeeds; false only for critical errors (e.g., invalid target) in ControlFlow mode */ -bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap &value_map) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov:\n"; - } - - Value source = op.getValue(); - Value target = op.getTarget(); - - if (!value_map.count(source)) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Source value not found in value map\n"; - } - return false; - } - - if (!value_map.count(target) || !value_map[target].is_reserve) { +bool handleCtrlMovOp(neura::CtrlMovOp op, + llvm::DenseMap& value_to_predicated_data_map) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Target is not a reserve placeholder\n"; + llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov" + << (isDataflowMode() ? "(dataflow)" : "") << ":\n"; } - return false; - } - const auto &source_data = value_map[source]; - auto &target_data = value_map[target]; + Value source = op.getValue(); + Value target = op.getTarget(); - if (source.getType() != target.getType()) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Type mismatch (source =" - << source.getType() << ", target =" << target.getType() << ")\n"; + // Check source exists in map + if (!value_to_predicated_data_map.count(source)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map\n"; + } + return false; } - return false; - } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << source <<"\n"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << source_data.value - << ", [pred = " << source_data.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] ├─ Target: " << target << "\n"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << target_data.value - << ", [pred = " << target_data.predicate << "]\n"; - } - // Copy data from source to target: value, predicate, vector flag, and vector data (if vector) - target_data.value = source_data.value; - target_data.predicate = source_data.predicate; - target_data.is_vector = source_data.is_vector; - if (source_data.is_vector) { - target_data.vector_data = source_data.vector_data; - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n"; - llvm::outs() << "[neura-interpreter] └─ value = " << target_data.value - << ", [pred = " << target_data.predicate << "]\n"; - } - - return true; -} + // Check target is a reserved placeholder + if (!value_to_predicated_data_map.count(target) || !value_to_predicated_data_map[target].is_reserve) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder\n"; + } + return false; + } -/** - * @brief Handles the execution of a Neura control move operation (neura.ctrl_mov) in dataflow mode, with explicit tracking of value updates. - * - * This function processes neura.ctrl_mov operations under dataflow analysis, focusing on conditional updates to a reserved target placeholder. - * It only updates the target if the source value's predicate is valid (true). Additionally, it tracks whether the target was actually modified - * (via the `is_updated` flag) to support dataflow propagation (e.g., triggering reprocessing of dependent operations). - * - * Key differences from standard mode: - * - Explicitly checks if the source predicate is valid before updating. - * - Tracks detailed changes (value, predicate, vector status) to set `is_updated`. - * - Focuses on propagating state changes for dataflow analysis. - * - * @param op The neura.ctrl_mov operation to handle - * @param value_map Reference to the map storing the source value and target reserved placeholder - * @return bool True if the operation is processed successfully (including skipped updates); false for critical errors (e.g., missing source/target) - */ -bool handleCtrlMovOpDataFlowMode(neura::CtrlMovOp op, - llvm::DenseMap &value_map) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov(dataflow):\n"; - } + const auto& source_data = value_to_predicated_data_map[source]; + auto& target_data = value_to_predicated_data_map[target]; + bool success = true; - Value source = op.getValue(); - if (!value_map.count(source)) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Source value not found (execution failed)\n"; - } - return false; - } + if (!isDataflowMode()) { + // Control-flow mode: Validate type match and unconditionally copy data + if (source.getType() != target.getType()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Type mismatch (source=" + << source.getType() << ", target=" << target.getType() << ")\n"; + } + return false; + } - Value target = op.getTarget(); - auto &target_data = value_map[target]; - if (!value_map.count(target) || !target_data.is_reserve) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder (execution failed)\n"; - } - return false; - } + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Source: " << source << "\n" + << "[neura-interpreter] │ └─ value = " << source_data.value + << ", [pred = " << source_data.predicate << "]\n" + << "[neura-interpreter] ├─ Target: " << target << "\n" + << "[neura-interpreter] │ └─ value = " << target_data.value + << ", [pred = " << target_data.predicate << "]\n"; + } - // Capture source data and target's current state (for update checks) - const auto &source_data = value_map[source]; - const float old_value = target_data.value; - const bool old_predicate = target_data.predicate; - const bool old_is_vector = target_data.is_vector; - const std::vector oldvector_data = target_data.vector_data; + // Unconditional copy for control flow + target_data.value = source_data.value; + target_data.predicate = source_data.predicate; + target_data.is_vector = source_data.is_vector; + if (source_data.is_vector) { + target_data.vector_data = source_data.vector_data; + } - // Reset update flag; will be set to true only if actual changes occur - target_data.is_updated = false; - // Determine if update should proceed: only if source predicate is valid (true) - const bool should_update = (source_data.predicate == 1); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n" + << "[neura-interpreter] └─ value = " << target_data.value + << ", [pred = " << target_data.predicate << "]\n"; + } - if (should_update) { - // Copy source data to target - target_data.value = source_data.value; - target_data.predicate = source_data.predicate; - target_data.is_vector = source_data.is_vector; - target_data.vector_data = source_data.is_vector ? source_data.vector_data : std::vector(); + } else { // DataFlow mode + // Data-flow mode: Conditional update based on source predicate + const bool should_update = (source_data.predicate == 1); + const auto old_target_data = target_data; // Store current state for update check + + if (should_update) { + // Copy source data to target + target_data.value = source_data.value; + target_data.predicate = source_data.predicate; + target_data.is_vector = source_data.is_vector; + target_data.vector_data = source_data.is_vector ? source_data.vector_data : std::vector(); + } else if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate invalid (pred=" + << source_data.predicate << ")\n"; + } - // Check if scalar target was updated (value, predicate, or vector state changed) - const bool is_scalar_updated = !target_data.is_vector && - (target_data.value != old_value || - target_data.predicate != old_predicate || - !oldvector_data.empty()); - // Check if vector target was updated (predicate or vector elements changed) - const bool is_vector_updated = target_data.is_vector && - (target_data.predicate != old_predicate || - target_data.vector_data != oldvector_data); - target_data.is_updated = is_scalar_updated || is_vector_updated || (target_data.is_vector != old_is_vector); - } else if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate invalid (pred=" - << source_data.predicate << ")\n"; - } + // Calculate is_updated by comparing with old state + target_data.is_updated = target_data.isUpdatedComparedTo(old_target_data); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << source_data.value << " | " << source_data.predicate << "\n" - << "[neura-interpreter] ├─ Target (after): " << target_data.value << " | " << target_data.predicate - << " | is_updated=" << target_data.is_updated << "\n" - << "[neura-interpreter] └─ Execution succeeded\n"; - } + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Source: " << source_data.value << " | " << source_data.predicate << "\n" + << "[neura-interpreter] ├─ Target (after): " << target_data.value << " | " << target_data.predicate + << " | is_updated=" << target_data.is_updated << "\n" + << "[neura-interpreter] └─ Execution succeeded\n"; + } + } - return true; + return success; } /** @@ -2779,11 +2633,11 @@ bool handleCtrlMovOpDataFlowMode(neura::CtrlMovOp op, * validates their existence, and prints them in a human-readable format (scalar or vector) in verbose mode. For vector values, it formats elements as a * comma-separated list, using 0.0f for elements with an invalid predicate. If no return values are present, it indicates a void return. * - * @param op The neura.return operation to handle - * @param value_map Reference to the map storing the return values to be processed - * @return bool True if return values are successfully processed; false if any return value is missing from the value map + * @param op The neura.return operation to handle + * @param value_to_predicated_data_map Reference to the map storing the return values to be processed + * @return bool True if return values are successfully processed; false if any return value is missing from the value map */ -bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_map) { +bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.return:\n"; } @@ -2798,11 +2652,11 @@ bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap results; for (Value val : return_values) { - if (!value_map.count(val)) { + if (!value_to_predicated_data_map.count(val)) { llvm::errs() << "[neura-interpreter] └─ Return value not found in value map\n"; return false; } - results.push_back(value_map[val]); + results.push_back(value_to_predicated_data_map[val]); } if (isVerboseMode()) { @@ -2839,11 +2693,11 @@ bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_map) { +bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_predicate:\n"; } @@ -2855,15 +2709,15 @@ bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &value_map) { +bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &value_to_predicated_data_map) { if(isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_once:\n"; } @@ -2921,13 +2775,13 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &value_map) { +bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_always:\n"; } @@ -3007,7 +2861,7 @@ bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap& value_map, - Memory& mem, - llvm::SmallVector& next_work_list) { - llvm::DenseMap old_values; - for (Value result : op->getResults()) { - if (value_map.count(result)) { - old_values[result] = value_map[result]; - } - } +OperationHandleResult handleOperation( + Operation* op, + llvm::DenseMap& value_to_predicated_data_map, + Memory& mem, + Block**current_block = nullptr, + Block** last_visited_block = nullptr) { + + OperationHandleResult result{true, false, false}; - bool execution_success = true; if (auto const_op = dyn_cast(op)) { - execution_success = handleArithConstantOp(const_op, value_map); + result.success = handleArithConstantOp(const_op, value_to_predicated_data_map); } else if (auto const_op = dyn_cast(op)) { - execution_success = handleNeuraConstantOp(const_op, value_map); + result.success = handleNeuraConstantOp(const_op, value_to_predicated_data_map); } else if (auto mov_op = dyn_cast(op)) { - value_map[mov_op.getResult()] = value_map[mov_op.getOperand()]; + value_to_predicated_data_map[mov_op.getResult()] = value_to_predicated_data_map[mov_op.getOperand()]; } else if (auto add_op = dyn_cast(op)) { - execution_success = handleAddOp(add_op, value_map); + result.success = handleAddOp(add_op, value_to_predicated_data_map); } else if (auto sub_op = dyn_cast(op)) { - execution_success = handleSubOp(sub_op, value_map); + result.success = handleSubOp(sub_op, value_to_predicated_data_map); } else if (auto fadd_op = dyn_cast(op)) { - execution_success = handleFAddOp(fadd_op, value_map); + result.success = handleFAddOp(fadd_op, value_to_predicated_data_map); } else if (auto fsub_op = dyn_cast(op)) { - execution_success = handleFSubOp(fsub_op, value_map); + result.success = handleFSubOp(fsub_op, value_to_predicated_data_map); } else if (auto fmul_op = dyn_cast(op)) { - execution_success = handleFMulOp(fmul_op, value_map); + result.success = handleFMulOp(fmul_op, value_to_predicated_data_map); } else if (auto fdiv_op = dyn_cast(op)) { - execution_success = handleFDivOp(fdiv_op, value_map); + result.success = handleFDivOp(fdiv_op, value_to_predicated_data_map); } else if (auto vfmul_op = dyn_cast(op)) { - execution_success = handleVFMulOp(vfmul_op, value_map); + result.success = handleVFMulOp(vfmul_op, value_to_predicated_data_map); } else if (auto fadd_fadd_op = dyn_cast(op)) { - execution_success = handleFAddFAddOp(fadd_fadd_op, value_map); + result.success = handleFAddFAddOp(fadd_fadd_op, value_to_predicated_data_map); } else if (auto fmul_fadd_op = dyn_cast(op)) { - execution_success = handleFMulFAddOp(fmul_fadd_op, value_map); + result.success = handleFMulFAddOp(fmul_fadd_op, value_to_predicated_data_map); } else if (auto ret_op = dyn_cast(op)) { - execution_success = handleFuncReturnOp(ret_op, value_map); + result.success = handleFuncReturnOp(ret_op, value_to_predicated_data_map); + result.is_terminated = true; // Return operations terminate execution } else if (auto fcmp_op = dyn_cast(op)) { - execution_success = handleFCmpOp(fcmp_op, value_map); + result.success = handleFCmpOp(fcmp_op, value_to_predicated_data_map); } else if (auto icmp_op = dyn_cast(op)) { - execution_success = handleICmpOp(icmp_op, value_map); + result.success = handleICmpOp(icmp_op, value_to_predicated_data_map); } else if (auto or_op = dyn_cast(op)) { - execution_success = handleOrOp(or_op, value_map); + result.success = handleOrOp(or_op, value_to_predicated_data_map); } else if (auto not_op = dyn_cast(op)) { - execution_success = handleNotOp(not_op, value_map); + result.success = handleNotOp(not_op, value_to_predicated_data_map); } else if (auto sel_op = dyn_cast(op)) { - execution_success = handleSelOp(sel_op, value_map); + result.success = handleSelOp(sel_op, value_to_predicated_data_map); } else if (auto cast_op = dyn_cast(op)) { - execution_success = handleCastOp(cast_op, value_map); + result.success = handleCastOp(cast_op, value_to_predicated_data_map); } else if (auto load_op = dyn_cast(op)) { - execution_success = handleLoadOp(load_op, value_map, mem); + result.success = handleLoadOp(load_op, value_to_predicated_data_map, mem); } else if (auto store_op = dyn_cast(op)) { - execution_success = handleStoreOp(store_op, value_map, mem); + result.success = handleStoreOp(store_op, value_to_predicated_data_map, mem); } else if (auto gep_op = dyn_cast(op)) { - execution_success = handleGEPOp(gep_op, value_map); + result.success = handleGEPOp(gep_op, value_to_predicated_data_map); } else if (auto load_index_op = dyn_cast(op)) { - execution_success = handleLoadIndexedOp(load_index_op, value_map, mem); + result.success = handleLoadIndexedOp(load_index_op, value_to_predicated_data_map, mem); } else if (auto store_index_op = dyn_cast(op)) { - execution_success = handleStoreIndexedOp(store_index_op, value_map, mem); + result.success = handleStoreIndexedOp(store_index_op, value_to_predicated_data_map, mem); } else if (auto br_op = dyn_cast(op)) { - // execution_success = handleBrOp(br_op, value_map, current_block, last_visited_block); + // Branch operations only need block handling in control flow mode + if (current_block && last_visited_block) { + result.success = handleBrOp(br_op, value_to_predicated_data_map, *current_block, *last_visited_block); + result.is_branch = true; // Mark as branch to reset index + } else { + result.success = false; + } } else if (auto cond_br_op = dyn_cast(op)) { - // execution_success = handleCondBrOp(cond_br_op, value_map, current_block, last_visited_block); + if (current_block && last_visited_block) { + result.success = handleCondBrOp(cond_br_op, value_to_predicated_data_map, *current_block, *last_visited_block); + result.is_branch = true; + } else { + result.success = false; + } } else if (auto phi_op = dyn_cast(op)) { - execution_success = handlePhiOpDataFlowMode(phi_op, value_map); + // Phi operations need block information in control flow mode, but not in data flow + if (current_block && last_visited_block) { + result.success = handlePhiOp(phi_op, value_to_predicated_data_map, *current_block, *last_visited_block); + } else { + result.success = handlePhiOp(phi_op, value_to_predicated_data_map); + } } else if (auto reserve_op = dyn_cast(op)) { - execution_success = handleReserveOp(reserve_op, value_map); + result.success = handleReserveOp(reserve_op, value_to_predicated_data_map); } else if (auto ctrl_mov_op = dyn_cast(op)) { - execution_success = handleCtrlMovOpDataFlowMode(ctrl_mov_op, value_map); + result.success = handleCtrlMovOp(ctrl_mov_op, value_to_predicated_data_map); } else if (auto return_op = dyn_cast(op)) { - execution_success = handleNeuraReturnOp(return_op, value_map); + result.success = handleNeuraReturnOp(return_op, value_to_predicated_data_map); + result.is_terminated = true; } else if (auto grant_pred_op = dyn_cast(op)) { - execution_success = handleGrantPredicateOp(grant_pred_op, value_map); + result.success = handleGrantPredicateOp(grant_pred_op, value_to_predicated_data_map); } else if (auto grant_once_op = dyn_cast(op)) { - execution_success = handleGrantOnceOp(grant_once_op, value_map); + result.success = handleGrantOnceOp(grant_once_op, value_to_predicated_data_map); } else if (auto grant_always_op = dyn_cast(op)) { - execution_success = handleGrantAlwaysOp(grant_always_op, value_map); + result.success = handleGrantAlwaysOp(grant_always_op, value_to_predicated_data_map); } else { llvm::errs() << "[neura-interpreter] Unhandled op: "; op->print(llvm::errs()); llvm::errs() << "\n"; - execution_success = false; + result.success = false; } - // If execution failed, exit early without propagating updates - if (!execution_success) { + return result; +} + +/** + * @brief Executes a single operation in data flow mode with update propagation + * + * Processes the operation using the generic handler, then checks for value updates + * and propagates changes to dependent operations. This function contains data flow + * specific logic for dependency management and pending operation queue updates. + * + * @param op The operation to execute + * @param value_to_predicated_data_map Reference to map storing predicated data for values + * @param mem Reference to memory object for load/store operations + * @param next_pending_operation_queue Queue to receive dependent operations needing processing + * @return bool True if operation executed successfully, false otherwise + */ +bool executeOperation(Operation* op, + llvm::DenseMap& value_to_predicated_data_map, + Memory& mem, + llvm::SmallVector& next_pending_operation_queue) { + + // Save current values to detect updates after execution + llvm::DenseMap old_values; + for (Value result : op->getResults()) { + if (value_to_predicated_data_map.count(result)) { + old_values[result] = value_to_predicated_data_map[result]; + } + } + + // Process the operation using the generic handler (no block info needed for data flow) + auto handle_result = handleOperation(op, value_to_predicated_data_map, mem); + if (!handle_result.success) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation failed, no propagation\n"; } - return execution_success; + return false; + } + + // Check if any operands are invalid (data flow specific validation) + bool has_invalid_operands = false; + for (Value operand : op->getOperands()) { + if (value_to_predicated_data_map.count(operand) && + !value_to_predicated_data_map[operand].predicate) { + has_invalid_operands = true; + break; + } } - // Check if any of the operation's results were updated during execution + // Check if any results were updated during execution bool has_update = false; for (Value result : op->getResults()) { - if (!value_map.count(result)) continue; - PredicatedData new_data = value_map[result]; + if (!value_to_predicated_data_map.count(result)) continue; + PredicatedData new_data = value_to_predicated_data_map[result]; - // New result (not in old_values) is considered an update + // New results (not in old_values) are considered updates if (!old_values.count(result)) { new_data.is_updated = true; - value_map[result] = new_data; + value_to_predicated_data_map[result] = new_data; has_update = true; continue; } - // Compare new value with old value to detect updates + // Compare new value with old value to detect changes PredicatedData old_data = old_values[result]; - if (isDataUpdated(old_data, new_data)) { + if (old_data.isUpdatedComparedTo(new_data)) { new_data.is_updated = true; - value_map[result] = new_data; + value_to_predicated_data_map[result] = new_data; has_update = true; } else { new_data.is_updated = false; - value_map[result] = new_data; + value_to_predicated_data_map[result] = new_data; } } @@ -3171,7 +3073,9 @@ bool executeOperation(Operation* op, if (op->getNumResults() == 0) { if (auto ctrl_mov_op = dyn_cast(op)) { Value target = ctrl_mov_op.getTarget(); - if (value_map.count(target) && value_map[target].is_updated) { + if (value_to_predicated_data_map.count(target) && + value_to_predicated_data_map[target].is_updated && + value_to_predicated_data_map[target].predicate) { has_update = true; } else { llvm::outs() << "[neura-interpreter] No update for ctrl_mov target: " << target << "\n"; @@ -3179,8 +3083,8 @@ bool executeOperation(Operation* op, } } - // If updates occurred, propagate to dependent operations (users) - if (has_update) { + // Propagate updates to dependent operations if changes occurred + if (has_update && !has_invalid_operands) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; } @@ -3188,284 +3092,134 @@ bool executeOperation(Operation* op, // Collect all values affected by the update llvm::SmallVector affected_values; for (Value result : op->getResults()) { - if (value_map.count(result) && value_map[result].is_updated) { + if (value_to_predicated_data_map.count(result) && + value_to_predicated_data_map[result].is_updated && + value_to_predicated_data_map[result].predicate) { affected_values.push_back(result); } } - // Include targets from the operations without result (e.g., CtrlMovOp) + // Include targets from operations without results (e.g., CtrlMovOp) if (op->getNumResults() == 0) { if (auto ctrl_mov_op = dyn_cast(op)) { + Value source = ctrl_mov_op.getValue(); Value target = ctrl_mov_op.getTarget(); - if (value_map.count(target) && value_map[target].is_updated) { + if (value_to_predicated_data_map.count(source) && + value_to_predicated_data_map[source].is_updated && + value_to_predicated_data_map[source].predicate && + value_to_predicated_data_map.count(target) && + value_to_predicated_data_map[target].is_updated && + value_to_predicated_data_map[target].predicate) { affected_values.push_back(target); } } } - // Add all users of affected values to the next work list (if not already present) + // Add all users of affected values to the next pending operation queue (if not already present) for (Value val : affected_values) { - if (!value_users.count(val)) continue; - for (Operation* user_op : value_users[val]) { - if (!in_work_list[user_op]) { - next_work_list.push_back(user_op); - in_work_list[user_op] = true; + for (Operation* user_op : val.getUsers()) { + if (!is_operation_enqueued[user_op]) { + next_pending_operation_queue.push_back(user_op); + is_operation_enqueued[user_op] = true; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Added user to next work_list: "; + llvm::outs() << "[neura-interpreter] Added user to next pending_operation_queue: "; user_op->print(llvm::outs()); llvm::outs() << "\n"; } } - } + } } } - return execution_success; + return true; } /** - * @brief Executes a function in data flow mode, processing operations based on data availability and managing return operations separately. + * @brief Unified execution entry point supporting both data flow and control flow modes * - * This function processes operations in a work list, where each operation is executed once its dependencies are satisfied. - * Return operations are held in a delay queue until all their input values are valid, ensuring they execute only when all prerequisites are met. - * It iteratively processes the work list, propagating data through the graph until all operations (including returns) are completed. + * Executes a function using either data flow or control flow semantics based on the specified mode. + * Data flow mode processes operations when their dependencies are satisfied, while control flow + * mode follows traditional sequential execution with branch handling. * - * @param func The function to execute in data flow mode - * @param value_map Reference to a map storing predicated data for each value (tracks valid/invalid state) - * @param mem Reference to the memory object for handling load/store operations - * @return int 0 if execution completes successfully, 1 if an error occurs during operation execution + * @param func The function to execute + * @param value_to_predicated_data_map Reference to map storing predicated data for values + * @param mem Reference to memory object for load/store operations + * @return int 0 if execution completes successfully, 1 on error */ -int runDataFlowMode(func::FuncOp func, - llvm::DenseMap& value_map, - Memory& mem) { - // Queue to hold return operations until their dependencies are satisfied - llvm::SmallVector delay_queue; - - // Initialize work list with all operations except return ops (which go to delay queue) - for (auto& block : func.getBody()) { - for (auto& op : block.getOperations()) { - if (isa(op) || isa(op)) { - delay_queue.push_back(&op); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Return op added to delay queue: "; - op.print(llvm::outs()); - llvm::outs() << "\n"; +int run(func::FuncOp func, + llvm::DenseMap& value_to_predicated_data_map, + Memory& mem) { + if (isDataflowMode()) { + // Data flow mode execution logic + // Initialize pending operation queue with all operations except return operations + for (auto& block : func.getBody()) { + for (auto& op : block.getOperations()) { + if (isa(op) || isa(op)) { + continue; + } else { + pending_operation_queue.push_back(&op); + is_operation_enqueued[&op] = true; } - } else { - // Add non-return operations to the work list and mark them as present - work_list.push_back(&op); - in_work_list[&op] = true; } } - } - int iter_count = 0; - // Main loop: process work list and delay queue until both are empty - while (!work_list.empty() || !delay_queue.empty()) { - iter_count++; - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Iteration " << iter_count - << " | work_list: " << work_list.size() - << " | delay_queue: " << delay_queue.size() << "\n"; - } - - llvm::SmallVector next_work_list; - - // Process all operations in the current work list - for (Operation* op : work_list) { - in_work_list[op] = false; - if (!executeOperation(op, value_map, mem, next_work_list)) { - return 1; + int iter_count = 0; + // Main loop: process pending operation queue until empty + while (!pending_operation_queue.empty()) { + iter_count++; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Iteration " << iter_count + << " | pending_operation_queue: " << pending_operation_queue.size() << "\n"; } - } - // Check if return operations in the delay queue can be executed - if (!delay_queue.empty()) { - Operation* return_op = delay_queue[0]; - bool can_execute_return = true; - // Verify all input operands of the return op are valid (exist and have true predicate) - for (Value input : return_op->getOperands()) { - if (!value_map.count(input) || !value_map[input].predicate) { - can_execute_return = false; - break; + llvm::SmallVector next_pending_operation_queue; + for (Operation* op : pending_operation_queue) { + is_operation_enqueued[op] = false; + if (!executeOperation(op, value_to_predicated_data_map, mem, next_pending_operation_queue)) { + return 1; } } + pending_operation_queue = std::move(next_pending_operation_queue); + } - if (can_execute_return) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] All return inputs are valid, executing return\n"; - } - for (Operation* op : delay_queue) { - next_work_list.push_back(op); - in_work_list[op] = true; - } - delay_queue.clear(); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Total iterations: " << iter_count << "\n"; + } + } else { + // Control flow mode execution logic + Block* current_block = &func.getBody().front(); + Block* last_visited_block = nullptr; + size_t op_index = 0; + bool is_terminated = false; + + // Main loop: process operations sequentially through blocks + while (!is_terminated && current_block) { + auto& operations = current_block->getOperations(); + if (op_index >= operations.size()) break; + + Operation& op = *std::next(operations.begin(), op_index); + // Process operation with block information for control flow handling + auto handle_result = handleOperation(&op, value_to_predicated_data_map, mem, ¤t_block, &last_visited_block); + + if (!handle_result.success) return 1; + if (handle_result.is_terminated) { + is_terminated = true; + op_index++; + } else if (handle_result.is_branch) { + // Branch operations update current_block, reset index to start of new block + op_index = 0; } else { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Some return inputs are invalid, keeping return in delay queue\n"; - } + // Regular operations increment to next operation in block + op_index++; } } - - // Prepare work list for the next iteration - work_list = std::move(next_work_list); - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Total iterations: " << iter_count << "\n"; } return 0; } -/** - * @brief Executes the control flow of a function by processing operations in sequence and handling control transfers. - * - * This function iterates through the operations within the function's blocks, processing each operation - * according to its type using specialized handler functions. It manages control flow transfers (branches, - * conditional branches) and maintains the current execution state including the active block and operation index. - * - * @param func The function to execute in control flow mode - * @param value_map Reference to a map storing predicated data for each value - * @param mem Reference to the memory object for handling load/store operations - * @return int 0 if execution completes successfully, 1 if an error occurs (unhandled operation or handler failure) - */ -int runControlFlowMode(func::FuncOp func, - llvm::DenseMap& value_map, - Memory& mem) { - Block* current_block = &func.getBody().front(); /* Initialize execution state - start at the first block of the function */ - Block* last_visited_block = nullptr; /* Track the previous block for handling phi operations */ - size_t op_index = 0; /* Current position within the operations of the current block */ - bool is_terminated = false; /* Flag to indicate if execution has been terminated (e.g., by return operation) */ - - // Main execution loop - continues until termination or no more blocks to process - while (!is_terminated && current_block) { - auto& operations = current_block->getOperations(); - - if (op_index >= operations.size()) { - break; - } - - Operation& op = *std::next(operations.begin(), op_index); - - // Handle each operation type with specialized handlers - if (auto const_op = dyn_cast(op)) { - if (!handleArithConstantOp(const_op, value_map)) return 1; - ++op_index; - } else if (auto const_op = dyn_cast(op)) { - if (!handleNeuraConstantOp(const_op, value_map)) return 1; - ++op_index; - } else if (auto mov_op = dyn_cast(op)) { - value_map[mov_op.getResult()] = value_map[mov_op.getOperand()]; - ++op_index; - } else if (auto add_op = dyn_cast(op)) { - if (!handleAddOp(add_op, value_map)) return 1; - ++op_index; - } else if (auto sub_op = dyn_cast(op)) { - if (!handleSubOp(sub_op, value_map)) return 1; - ++op_index; - } else if (auto fadd_op = dyn_cast(op)) { - if (!handleFAddOp(fadd_op, value_map)) return 1; - ++op_index; - } else if (auto fsub_op = dyn_cast(op)) { - if (!handleFSubOp(fsub_op, value_map)) return 1; - ++op_index; - } else if (auto fmul_op = dyn_cast(op)) { - if (!handleFMulOp(fmul_op, value_map)) return 1; - ++op_index; - } else if (auto fdiv_op = dyn_cast(op)) { - if (!handleFDivOp(fdiv_op, value_map)) return 1; - ++op_index; - } else if (auto vfmul_op = dyn_cast(op)) { - if (!handleVFMulOp(vfmul_op, value_map)) return 1; - ++op_index; - } else if (auto fadd_fadd_op = dyn_cast(op)) { - if (!handleFAddFAddOp(fadd_fadd_op, value_map)) return 1; - ++op_index; - } else if (auto fmul_fadd_op = dyn_cast(op)) { - if (!handleFMulFAddOp(fmul_fadd_op, value_map)) return 1; - ++op_index; - } else if (auto ret_op = dyn_cast(op)) { - if (!handleFuncReturnOp(ret_op, value_map)) return 1; - is_terminated = true; - ++op_index; - } else if (auto fcmp_op = dyn_cast(op)) { - if (!handleFCmpOp(fcmp_op, value_map)) return 1; - ++op_index; - } else if (auto icmp_op = dyn_cast(op)) { - if (!handleICmpOp(icmp_op, value_map)) return 1; - ++op_index; - } else if (auto or_op = dyn_cast(op)) { - if (!handleOrOp(or_op, value_map)) return 1; - ++op_index; - } else if (auto not_op = dyn_cast(op)) { - if (!handleNotOp(not_op, value_map)) return 1; - ++op_index; - } else if (auto sel_op = dyn_cast(op)) { - if (!handleSelOp(sel_op, value_map)) return 1; - ++op_index; - } else if (auto cast_op = dyn_cast(op)) { - if (!handleCastOp(cast_op, value_map)) return 1; - ++op_index; - } else if (auto load_op = dyn_cast(op)) { - if (!handleLoadOp(load_op, value_map, mem)) return 1; - ++op_index; - } else if (auto store_op = dyn_cast(op)) { - if (!handleStoreOp(store_op, value_map, mem)) return 1; - ++op_index; - } else if (auto gep_op = dyn_cast(op)) { - if (!handleGEPOp(gep_op, value_map)) return 1; - ++op_index; - } else if (auto load_index_op = dyn_cast(op)) { - if (!handleLoadIndexedOp(load_index_op, value_map, mem)) return 1; - ++op_index; - } else if (auto store_index_op = dyn_cast(op)) { - if (!handleStoreIndexedOp(store_index_op, value_map, mem)) return 1; - ++op_index; - } else if (auto br_op = dyn_cast(op)) { - // Branch operations change the current block - reset index to start of new block - if (!handleBrOp(br_op, value_map, current_block, last_visited_block)) return 1; - op_index = 0; - } else if (auto cond_br_op = dyn_cast(op)) { - // Conditional branches change the current block - reset index to start of new block - if (!handleCondBrOp(cond_br_op, value_map, current_block, last_visited_block)) return 1; - op_index = 0; - } else if (auto phi_op = dyn_cast(op)) { - // Phi operations depend on previous block for value selection - if (!handlePhiOpControlFlowMode(phi_op, value_map, current_block, last_visited_block)) return 1; - ++op_index; - } else if (auto reserve_op = dyn_cast(op)) { - if (!handleReserveOp(reserve_op, value_map)) return 1; - ++op_index; - } else if (auto ctrl_mov_op = dyn_cast(op)) { - if (!handleCtrlMovOp(ctrl_mov_op, value_map)) return 1; - ++op_index; - } else if (auto return_op = dyn_cast(op)) { - if (!handleNeuraReturnOp(return_op, value_map)) return 1; - is_terminated = true; - ++op_index; - } else if (auto grant_pred_op = dyn_cast(op)) { - if (!handleGrantPredicateOp(grant_pred_op, value_map)) return 1; - ++op_index; - } else if (auto grant_once_op = dyn_cast(op)) { - if (!handleGrantOnceOp(grant_once_op, value_map)) return 1; - ++op_index; - } else if (auto grant_always_op = dyn_cast(op)) { - if (!handleGrantAlwaysOp(grant_always_op, value_map)) return 1; - ++op_index; - } else { - llvm::errs() << "[neura-interpreter] Unhandled op: "; - op.print(llvm::errs()); - llvm::errs() << "\n"; - return 1; - } - } - - return 0; -} - int main(int argc, char **argv) { - + // Parse command line arguments for (int i = 0; i < argc; ++i) { if (std::string(argv[i]) == "--verbose") { setVerboseMode(true); @@ -3475,16 +3229,18 @@ int main(int argc, char **argv) { } if (argc < 2) { - llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose]\n"; + llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose] [--dataflow]\n"; return 1; } + // Initialize MLIR context and dialects DialectRegistry registry; registry.insert(); MLIRContext context; context.appendDialectRegistry(registry); + // Load and parse input MLIR file llvm::SourceMgr source_mgr; auto file_or_err = mlir::openInputFile(argv[1]); if (!file_or_err) { @@ -3500,25 +3256,14 @@ int main(int argc, char **argv) { return 1; } - // Changes map to store PredicatedData instead of just float. - llvm::DenseMap value_map; - - Memory mem(1024); // 1MB + // Initialize data structures + llvm::DenseMap value_to_predicated_data_map; + Memory mem(1024); // 1MB memory allocation - if (isDataflowMode()) { - buildDependencyGraph(*module); - for (auto func : module->getOps()) { - if (runDataFlowMode(func, value_map, mem)) { - llvm::errs() << "[neura-interpreter] Data Flow execution failed\n"; - return 1; - } - } - } else { - for (auto func : module->getOps()) { - if (runControlFlowMode(func, value_map, mem)) { - llvm::errs() << "[neura-interpreter] Control Flow execution failed\n"; - return 1; - } + for (auto func : module->getOps()) { + if (run(func, value_to_predicated_data_map, mem)) { + llvm::errs() << "[neura-interpreter] Execution failed\n"; + return 1; } } From 2f3a9f3b07c1c71aab48dc2004b5cee8d4a38329 Mon Sep 17 00:00:00 2001 From: item Date: Sat, 16 Aug 2025 20:49:45 +0800 Subject: [PATCH 4/6] add interpreter dataflow mode --- .../interpreter/basic_operation/gep.mlir | 41 +- .../basic_operation/load_store.mlir | 19 - .../basic_operation/load_store_index.mlir | 63 -- .../loop_convert_controlflow_to_dataflow.mlir | 101 +++ test/neura/interpreter/loop_dataflow.mlir | 38 - tools/neura-interpreter/neura-interpreter.cpp | 705 ++++-------------- 6 files changed, 260 insertions(+), 707 deletions(-) delete mode 100644 test/neura/interpreter/basic_operation/load_store.mlir delete mode 100644 test/neura/interpreter/basic_operation/load_store_index.mlir create mode 100644 test/neura/interpreter/loop_convert_controlflow_to_dataflow.mlir delete mode 100644 test/neura/interpreter/loop_dataflow.mlir diff --git a/test/neura/interpreter/basic_operation/gep.mlir b/test/neura/interpreter/basic_operation/gep.mlir index c3c5611a..465b0a68 100644 --- a/test/neura/interpreter/basic_operation/gep.mlir +++ b/test/neura/interpreter/basic_operation/gep.mlir @@ -4,44 +4,7 @@ func.func @test_gep_simple() -> i32 { %base = arith.constant 0 : i32 %idx = arith.constant 2 : i32 %gep = "neura.gep"(%base, %idx) { strides = [4] } : (i32, i32) -> i32 - - %val = arith.constant 42 : i32 - "neura.store"(%val, %gep) : (i32, i32) -> () - - %loaded = "neura.load"(%gep) : (i32) -> i32 // CHECK: [neura-interpreter] └─ Final GEP result: base = 0, total offset = 8, final address = 8, [pred = 1] - // CHECK: [neura-interpreter] → Output: 42.000000 - return %loaded : i32 -} - -func.func @test_gep_2d() -> i32 { - %base = arith.constant 0 : i32 - %idx0 = arith.constant 1 : i32 - %idx1 = arith.constant 3 : i32 - %gep = "neura.gep"(%base, %idx0, %idx1) { strides = [16, 4] } : (i32, i32, i32) -> i32 - // CHECK: [neura-interpreter] └─ Final GEP result: base = 0, total offset = 28, final address = 28, [pred = 1] - // CHECK: [neura-interpreter] → Output: 99.000000 - - %val = arith.constant 99 : i32 - "neura.store"(%val, %gep) : (i32, i32) -> () - - %loaded = "neura.load"(%gep) : (i32) -> i32 - return %loaded : i32 -} - -func.func @test_gep_predicate() -> i32 { - %base = arith.constant 0 : i32 - %idx0 = arith.constant 1 : i32 - %idx1 = arith.constant 3 : i32 - %pred = arith.constant 0 : i1 - - %gep = "neura.gep"(%base, %idx0, %idx1, %pred) { strides = [16, 4] } : (i32, i32, i32, i1) -> i32 - // CHECK: [neura-interpreter] └─ Final GEP result: base = 0, total offset = 28, final address = 28, [pred = 0] - - %val = arith.constant 77 : i32 - "neura.store"(%val, %gep) : (i32, i32) -> () - - %loaded = "neura.load"(%gep) : (i32) -> i32 - // CHECK: [neura-interpreter] → Output: 0.000000 - return %loaded : i32 + + return %gep : i32 } \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/load_store.mlir b/test/neura/interpreter/basic_operation/load_store.mlir deleted file mode 100644 index 4b78c07b..00000000 --- a/test/neura/interpreter/basic_operation/load_store.mlir +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: neura-interpreter %s --verbose | FileCheck %s - -func.func @test_store_load_i32() -> i32 { - %addr = arith.constant 0 : i32 - %val = arith.constant 123 : i32 - "neura.store"(%val, %addr) : (i32, i32) -> () - %loaded = "neura.load"(%addr) : (i32) -> i32 - // CHECK: [neura-interpreter] → Output: 123.000000 - return %loaded : i32 -} - -func.func @test_store_load_f32() -> f32 { - %addr = arith.constant 4 : i32 - %val = arith.constant 3.14 : f32 - "neura.store"(%val, %addr) : (f32, i32) -> () - %loaded = "neura.load"(%addr) : (i32) -> f32 - // CHECK: [neura-interpreter] → Output: 3.140000 - return %loaded : f32 -} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/load_store_index.mlir b/test/neura/interpreter/basic_operation/load_store_index.mlir deleted file mode 100644 index 8804e0a9..00000000 --- a/test/neura/interpreter/basic_operation/load_store_index.mlir +++ /dev/null @@ -1,63 +0,0 @@ -// RUN: neura-interpreter %s --verbose | FileCheck %s - -module { - // Test case: float (f32) load/store with single index - func.func @test_load_store_indexed() -> f32 { - %val = "neura.constant"() { value = 42.0 } : () -> f32 - %base = "neura.constant"() { value = 100 } : () -> i32 - %offset = arith.constant 4 : i32 - - "neura.store_indexed"(%val, %base, %offset) { - operandSegmentSizes = array - } : (f32, i32, i32) -> () - - %load = "neura.load_indexed"(%base, %offset) { - operandSegmentSizes = array - } : (i32, i32) -> f32 - // CHECK: [neura-interpreter] → Output: 42.000000 - - return %load : f32 - } - - // Test case: 32-bit integer (i32) load/store with single index - func.func @test_i32() -> i32 { - %val = "neura.constant"() { value = 66 } : () -> i32 - %base = "neura.constant"() { value = 200 } : () -> i32 - %offset = arith.constant 4 : i32 - - "neura.store_indexed"(%val, %base, %offset) { - operandSegmentSizes = array - } : (i32, i32, i32) -> () - - - %load = "neura.load_indexed"(%base, %offset) { - operandSegmentSizes = array - } : (i32, i32) -> i32 - // CHECK: [neura-interpreter] → Output: 66.000000 - - return %load : i32 - } - - // Test case: float (f32) load/store with multi-dimensional indexing (2 indices) - func.func @test_multi_index() -> f32 { - %base = "neura.constant"() { value = 500 } : () -> i32 - %i = arith.constant 2 : i32 - %j = arith.constant 3 : i32 - %stride = arith.constant 10 : i32 - - %offset_i = "neura.fmul"(%i, %stride) : (i32, i32) -> i32 - %offset = "neura.add"(%offset_i, %j) : (i32, i32) -> i32 - %val = "neura.constant"() { value = 777.0 } : () -> f32 - - "neura.store_indexed"(%val, %base, %i, %j) { - operandSegmentSizes = array - } : (f32, i32, i32, i32) -> () - - %load = "neura.load_indexed"(%base, %i, %j) { - operandSegmentSizes = array - } : (i32, i32, i32) -> f32 - // CHECK: [neura-interpreter] → Output: 777.000000 - - return %load : f32 - } -} \ No newline at end of file diff --git a/test/neura/interpreter/loop_convert_controlflow_to_dataflow.mlir b/test/neura/interpreter/loop_convert_controlflow_to_dataflow.mlir new file mode 100644 index 00000000..c6337e62 --- /dev/null +++ b/test/neura/interpreter/loop_convert_controlflow_to_dataflow.mlir @@ -0,0 +1,101 @@ +// RUN: mlir-neura-opt \ +// RUN: --assign-accelerator \ +// RUN: --lower-llvm-to-neura \ +// RUN: --canonicalize-live-in \ +// RUN: --leverage-predicated-value \ +// RUN: --transform-ctrl-to-data-flow \ +// RUN: --fold-constant \ +// RUN: %s -o %t_dataflow.mlir + +// RUN: neura-interpreter %t_dataflow.mlir --verbose --dataflow > %t_output.txt 2>&1 + +// RUN: FileCheck %s --check-prefix=DATAFLOW_IR --input-file=%t_dataflow.mlir + +// RUN: FileCheck %s --check-prefix=INTERPRETER_OUTPUT --input-file=%t_output.txt + +func.func @loop_sum() -> f32 { + %init_i = arith.constant 0.0 : f32 + %init_sum = arith.constant 0.0 : f32 + %step = arith.constant 1.0 : f32 + %add_val = arith.constant 3.0 : f32 + %limit = arith.constant 5.0 : f32 + + %v_i = "neura.reserve"() : () -> (f32) + %v_sum = "neura.reserve"() : () -> (f32) + + "neura.ctrl_mov"(%init_i, %v_i) : (f32, f32) -> () + "neura.ctrl_mov"(%init_sum, %v_sum) : (f32, f32) -> () + + "neura.br"() [^loop_head] {operandSegmentSizes = array} : () -> () + +^loop_head: + %i = "neura.phi"(%v_i, %init_i) : (f32, f32) -> f32 + %sum = "neura.phi"(%v_sum, %init_sum) : (f32, f32) -> f32 + + %cond = "neura.fcmp"(%i, %limit) {cmpType = "lt"} : (f32, f32) -> i1 + + "neura.cond_br"(%cond) [^loop_body, ^loop_exit] {operandSegmentSizes = array} : (i1) -> () + +^loop_body: + %new_sum = "neura.fadd"(%sum, %add_val) : (f32, f32) -> f32 + + %new_i = "neura.fadd"(%i, %step) : (f32, f32) -> f32 + + "neura.ctrl_mov"(%new_i, %v_i) : (f32, f32) -> () + "neura.ctrl_mov"(%new_sum, %v_sum) : (f32, f32) -> () + + "neura.br"() [^loop_head] {operandSegmentSizes = array} : () -> () + +^loop_exit: + return %sum : f32 +} + +// DATAFLOW_IR: module { +// DATAFLOW_IR: func.func @loop_sum() -> f32 attributes {accelerator = "neura"} { +// DATAFLOW_IR: %cst = arith.constant 0.000000e+00 : f32 +// DATAFLOW_IR: %cst_0 = arith.constant 1.000000e+00 : f32 +// DATAFLOW_IR: %cst_1 = arith.constant 3.000000e+00 : f32 +// DATAFLOW_IR: %cst_2 = arith.constant 5.000000e+00 : f32 +// DATAFLOW_IR: %0 = neura.reserve : !neura.data +// DATAFLOW_IR: %1 = "neura.grant_once"(%0) : (!neura.data) -> !neura.data +// DATAFLOW_IR: %2 = neura.reserve : !neura.data +// DATAFLOW_IR: %3 = "neura.grant_once"(%2) : (!neura.data) -> !neura.data +// DATAFLOW_IR: neura.ctrl_mov %cst -> %0 : f32 !neura.data +// DATAFLOW_IR: neura.ctrl_mov %cst -> %2 : f32 !neura.data +// DATAFLOW_IR: %4 = neura.reserve : !neura.data +// DATAFLOW_IR: %5 = "neura.phi"(%4, %cst_2) : (!neura.data, f32) -> !neura.data +// DATAFLOW_IR: %6 = neura.reserve : !neura.data +// DATAFLOW_IR: %7 = "neura.phi"(%6, %3) : (!neura.data, !neura.data) -> !neura.data +// DATAFLOW_IR: %8 = neura.reserve : !neura.data +// DATAFLOW_IR: %9 = "neura.phi"(%8, %cst) : (!neura.data, f32) -> !neura.data +// DATAFLOW_IR: %10 = neura.reserve : !neura.data +// DATAFLOW_IR: %11 = "neura.phi"(%10, %1) : (!neura.data, !neura.data) -> !neura.data +// DATAFLOW_IR: %12 = "neura.phi"(%11, %9) : (!neura.data, !neura.data) -> !neura.data +// DATAFLOW_IR: %13 = "neura.phi"(%7, %9) : (!neura.data, !neura.data) -> !neura.data +// DATAFLOW_IR: %14 = "neura.fcmp"(%12, %5) <{cmpType = "lt"}> : (!neura.data, !neura.data) -> !neura.data +// DATAFLOW_IR: %15 = neura.grant_predicate %13, %14 : !neura.data, !neura.data -> !neura.data +// DATAFLOW_IR: %16 = neura.grant_predicate %cst_1, %14 : f32, !neura.data -> f32 +// DATAFLOW_IR: %17 = neura.grant_predicate %12, %14 : !neura.data, !neura.data -> !neura.data +// DATAFLOW_IR: %18 = neura.grant_predicate %cst_0, %14 : f32, !neura.data -> f32 +// DATAFLOW_IR: %19 = neura.grant_predicate %1, %14 : !neura.data, !neura.data -> !neura.data +// DATAFLOW_IR: %20 = neura.grant_predicate %3, %14 : !neura.data, !neura.data -> !neura.data +// DATAFLOW_IR: %21 = neura.grant_predicate %cst, %14 : f32, !neura.data -> f32 +// DATAFLOW_IR: %22 = neura.grant_predicate %cst_2, %14 : f32, !neura.data -> f32 +// DATAFLOW_IR: %23 = "neura.not"(%14) : (!neura.data) -> !neura.data +// DATAFLOW_IR: %24 = neura.grant_predicate %13, %23 : !neura.data, !neura.data -> !neura.data +// DATAFLOW_IR: %25 = "neura.fadd"(%15, %16) : (!neura.data, f32) -> !neura.data +// DATAFLOW_IR: %26 = "neura.fadd"(%17, %18) : (!neura.data, f32) -> !neura.data +// DATAFLOW_IR: neura.ctrl_mov %26 -> %19 : !neura.data !neura.data +// DATAFLOW_IR: neura.ctrl_mov %25 -> %20 : !neura.data !neura.data +// DATAFLOW_IR: neura.ctrl_mov %19 -> %10 : !neura.data !neura.data +// DATAFLOW_IR: neura.ctrl_mov %21 -> %8 : f32 !neura.data +// DATAFLOW_IR: neura.ctrl_mov %20 -> %6 : !neura.data !neura.data +// DATAFLOW_IR: neura.ctrl_mov %22 -> %4 : f32 !neura.data +// DATAFLOW_IR: "neura.return"(%24) : (!neura.data) -> () +// DATAFLOW_IR: } +// DATAFLOW_IR: } + +// INTERPRETER_OUTPUT: [neura-interpreter] Executing neura.return: +// INTERPRETER_OUTPUT: [neura-interpreter] ├─ Return values: +// INTERPRETER_OUTPUT: [neura-interpreter] │ └─15.000000, [pred = 1] +// INTERPRETER_OUTPUT: [neura-interpreter] └─ Execution terminated successfully \ No newline at end of file diff --git a/test/neura/interpreter/loop_dataflow.mlir b/test/neura/interpreter/loop_dataflow.mlir deleted file mode 100644 index 2bed1a60..00000000 --- a/test/neura/interpreter/loop_dataflow.mlir +++ /dev/null @@ -1,38 +0,0 @@ -// RUN: neura-interpreter %s --verbose --dataflow | FileCheck %s - -func.func @loop_test() -> f32 attributes {accelerator = "neura"} { - %0 = "neura.grant_once"() <{constant_value = 10 : i64}> : () -> !neura.data - %1 = "neura.grant_once"() <{constant_value = 0 : i64}> : () -> !neura.data - %2 = "neura.grant_once"() <{constant_value = 1 : i64}> : () -> !neura.data - %3 = "neura.grant_once"() <{constant_value = 3.000000e+00 : f32}> : () -> !neura.data - %4 = "neura.grant_once"() <{constant_value = 0.000000e+00 : f32}> : () -> !neura.data - %5 = "neura.reserve"() : () -> (!neura.data) - %6 = "neura.phi"(%5, %0) : (!neura.data, !neura.data) -> !neura.data - %7 = "neura.reserve"() : () -> (!neura.data) - %8 = "neura.phi"(%7, %2) : (!neura.data, !neura.data) -> !neura.data - %9 = "neura.reserve"() : () -> (!neura.data) - %10 = "neura.phi"(%9, %3) : (!neura.data, !neura.data) -> !neura.data - %11 = "neura.reserve"() : () -> (!neura.data) - %12 = "neura.phi"(%11, %4) : (!neura.data, !neura.data) -> !neura.data - %13 = "neura.reserve"() : () -> (!neura.data) - %14 = "neura.phi"(%13, %1) : (!neura.data, !neura.data) -> !neura.data - %15 = "neura.fadd"(%12, %10) : (!neura.data, !neura.data) -> !neura.data - %16 = "neura.add"(%14, %8) : (!neura.data, !neura.data) -> !neura.data - %17 = "neura.icmp"(%16, %6) <{cmpType = "slt"}> : (!neura.data, !neura.data) -> !neura.data - %18 = "neura.grant_predicate"(%16, %17) : (!neura.data, !neura.data) -> !neura.data - "neura.ctrl_mov"(%18, %13) : (!neura.data, !neura.data) -> () - %19 = "neura.grant_predicate"(%15, %17) : (!neura.data, !neura.data) -> !neura.data - "neura.ctrl_mov"(%19, %11) : (!neura.data, !neura.data) -> () - %20 = "neura.grant_predicate"(%3, %17) : (!neura.data, !neura.data) -> !neura.data - "neura.ctrl_mov"(%20, %9) : (!neura.data, !neura.data) -> () - %21 = "neura.grant_predicate"(%2, %17) : (!neura.data, !neura.data) -> !neura.data - "neura.ctrl_mov"(%21, %7) : (!neura.data, !neura.data) -> () - %22 = "neura.grant_predicate"(%0, %17) : (!neura.data, !neura.data) -> !neura.data - "neura.ctrl_mov"(%22, %5) : (!neura.data, !neura.data) -> () - %23 = "neura.not"(%17) : (!neura.data) -> !neura.data - %24 = "neura.grant_predicate"(%15, %23) : (!neura.data, !neura.data) -> !neura.data - - // CHECK: [neura-interpreter] │ └─30.000000, [pred = 1] - - "neura.return"(%24) : (!neura.data) -> () -} \ No newline at end of file diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index 95fb1b9b..04a0d776 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -1,5 +1,6 @@ #include "llvm/Support/Format.h" #include "llvm/Support/SourceMgr.h" +#include "llvm/Support/raw_ostream.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/BuiltinOps.h" @@ -24,166 +25,6 @@ using namespace mlir; -/** - * @brief Implements a memory management system with allocation and deallocation capabilities. - * - * This class provides a simulated memory space with malloc/free operations, load/store - * operations for data access, and memory visualization. It maintains internal bookkeeping - * of allocated and free memory blocks using allocation tables and free lists. - */ -class Memory { -public: - /** - * @brief Constructs a Memory object with specified size. - * @param size The total size of memory to allocate (in bytes) - */ - Memory(size_t size); - - /** - * @brief Loads a value of type T from the specified memory address. - * @tparam T The type of data to load (must be trivially copyable) - * @param addr The memory address to load from - * @return The value loaded from memory - * @throws std::runtime_error if address is out of bounds - */ - template - T load(size_t addr) const; - - /** - * @brief Stores a value of type T at the specified memory address. - * @tparam T The type of data to store (must be trivially copyable) - * @param addr The memory address to store at - * @param value The value to store - * @throws std::runtime_error if address is out of bounds - */ - template - void store(size_t addr, const T& value); - - /** - * @brief Allocates a contiguous block of memory. - * @param sizeBytes The size of memory to allocate (in bytes) - * @return The starting address of the allocated block - * @throws std::runtime_error if insufficient memory is available - */ - size_t malloc(size_t sizeBytes); - - /** - * @brief Deallocates a previously allocated memory block. - * @param addr The starting address of the block to free - * @note Silently ignores invalid free operations (prints warning) - */ - void free(size_t addr); - - /** - * @brief Dumps memory contents in hexadecimal format. - * @param start The starting address for the dump (default: 0) - * @param length The number of bytes to display (default: 64) - */ - void dump(size_t start = 0, size_t length = 64) const; - - /** - * @brief Gets the total size of the memory space. - * @return The total memory size in bytes - */ - size_t getSize() const; - -private: - std::vector mem; /* The actual memory storage */ - std::unordered_map alloc_table; /* Tracks allocated blocks (address -> size) */ - std::map free_list; /* Tracks free blocks (address -> size) */ - - /** - * @brief Validates if a memory access is within bounds. - * @param addr The starting address to check - * @param size The size of the memory region to check - * @return true if access is valid, false otherwise - */ - bool validAddr(size_t addr, size_t size) const; - - /** - * @brief Merges adjacent free blocks in the free list. - * - * This internal method coalesces contiguous free blocks to prevent fragmentation - * and maintain optimal allocation performance. - */ - void mergeFreeBlocks(); -}; - -Memory::Memory(size_t size) : mem(size, 0) { - free_list[0] = size; -} - -template -T Memory::load(size_t addr) const { - assert(validAddr(addr, sizeof(T)) && "Memory load out of bounds"); - T result; - std::memcpy(&result, &mem[addr], sizeof(T)); - return result; -} - -template -void Memory::store(size_t addr, const T& value) { - assert(validAddr(addr, sizeof(T)) && "Memory store out of bounds"); - std::memcpy(&mem[addr], &value, sizeof(T)); -} - -size_t Memory::malloc(size_t sizeBytes) { - for (auto it = free_list.begin(); it != free_list.end(); ++it) { - if (it->second >= sizeBytes) { - size_t addr = it->first; - size_t remain = it->second - sizeBytes; - free_list.erase(it); - if (remain > 0) { - free_list[addr + sizeBytes] = remain; - } - alloc_table[addr] = sizeBytes; - return addr; - } - } - throw std::runtime_error("Out of memory"); -} - -void Memory::free(size_t addr) { - auto it = alloc_table.find(addr); - if (it == alloc_table.end()) { - std::cerr << "Invalid free at addr " << addr << "\n"; - return; - } - size_t size = it->second; - alloc_table.erase(it); - free_list[addr] = size; - - mergeFreeBlocks(); -} - -void Memory::dump(size_t start, size_t length) const { - for (size_t i = start; i < start + length && i < mem.size(); ++i) { - printf("%02X ", mem[i]); - if ((i - start + 1) % 16 == 0) printf("\n"); - } - printf("\n"); -} - -size_t Memory::getSize() const { - return mem.size(); -} - -bool Memory::validAddr(size_t addr, size_t size) const { - return addr + size <= mem.size(); -} - -void Memory::mergeFreeBlocks() { - auto it = free_list.begin(); - while (it != free_list.end()) { - auto curr = it++; - if (it != free_list.end() && curr->first + curr->second == it->first) { - curr->second += it->second; - free_list.erase(it); - it = curr; - } - } -} - // Predicated data structure, used to store scalar/vector values and related metadata struct PredicatedData { float value; /* Scalar floating-point value (valid when is_vector is false) */ @@ -193,6 +34,10 @@ struct PredicatedData { bool is_reserve; /* Reserve flag (may be used for memory reservation or temporary storage marking) */ bool is_updated; /* Update flag (indicates whether the data has been modified) */ + + PredicatedData() : value{0.0f}, predicate{true}, is_vector{false}, + vector_data{}, is_reserve{false}, is_updated{false} {} + /** * @brief Compares this PredicatedData instance with another to check if the data has been updated. * @@ -214,13 +59,87 @@ bool PredicatedData::isUpdatedComparedTo(const PredicatedData& other) const { return false; } -// Define structure to hold operation handling results and control flow information +/** @brief Structure to hold operation handling results and control flow information */ struct OperationHandleResult { bool success; /* Indicates if the operation was processed successfully */ bool is_terminated; /* Indicates if execution should terminate (e.g., after return operations) */ bool is_branch; /* Indicates if the operation is a branch (requires special index handling) */ }; +/** @brief Structure to represent the dependency graph of operations */ +struct DependencyGraph { + llvm::DenseMap dep_count; /* Stores the current dependency count of each operation */ + llvm::DenseSet executed_ops; /* Records the operations that have been executed */ + + /** + * @brief Builds a dependency graph for operations, calculating the initial dependency count (in-degree) + * for each operation. The dependency count is defined as the number of preceding operations within the + * same sequence that the current operation depends on. + * + * @param op_sequence The sequence of operations for which to build the dependency graph. + */ + void buildDependencyGraph(const std::vector& op_sequence); + + /** + * @brief Checks if an operation can be executed based on its dependency count. + * An operation can be executed if its dependency count is zero and it has not been executed yet. + * + * @param op The operation to check for execution eligibility. + * @return true if the operation can be executed; false otherwise. + */ + bool canExecute(Operation* op); + + /** + * @brief Updates the dependency graph after an operation has been executed. + * This involves marking the operation as executed and updating the dependency counts of its users. + * + * @param executed_op The operation that has been executed. + */ + void updateAfterExecution(Operation* executed_op); +}; + +void DependencyGraph::buildDependencyGraph(const std::vector& op_sequence) { + for (Operation* op : op_sequence) { + int deps = 0; + // Traverse all operands (input values) of the current operation to analyze dependency sources + for (Value operand : op->getOperands()) { + if (Operation* def_op = operand.getDefiningOp()) { + if (std::find(op_sequence.begin(), op_sequence.end(), def_op) != op_sequence.end()) { + deps++; + } + } + } + dep_count[op] = deps; + } +} + +bool DependencyGraph::canExecute(Operation* op) { + auto it = dep_count.find(op); + // The operation is executable if: + // 1. It exists in our dependency graph (it != dep_count.end()) + // 2. It has no unmet dependencies (dependency count == 0) + return it != dep_count.end() && it->second == 0; +} + +void DependencyGraph::updateAfterExecution(Operation* executed_op) { + // Mark as executed + executed_ops.insert(executed_op); + + // Traverse all output results produced by the executed operation + for (Value result : executed_op->getResults()) { + // Get all operations that use this result (dependent operations) + for (Operation* user : result.getUsers()) { + // Find the dependent operation in our dependency count map + auto it = dep_count.find(user); + // If the dependent operation is in our graph and has remaining dependencies, + // reduce its dependency count by 1 (since this executed operation was one of its dependencies) + if (it != dep_count.end() && it->second > 0) { + it->second--; + } + } + } +} + static llvm::SmallVector pending_operation_queue; /* List of operations to execute in current iteration */ static llvm::DenseMap is_operation_enqueued; /* Marks whether an operation is already in pending_operation_queue */ @@ -243,30 +162,6 @@ inline bool isVerboseMode() { return verbose; } -// /** -// * @brief Adds an operation to the pending operation queue if it's not already present. -// * -// * This function checks if the given operation is valid and not already in the pending operation queue -// * before adding it. It also maintains a flag to track presence in the pending operation queue for efficiency. -// * Verbose mode will log the addition of operations. -// * -// * @param op The Operation to be added to the pending operation queue. -// * @return void -// */ -// void addToWorkList(Operation* op) { -// if (op == nullptr) -// return; -// if (is_operation_enqueued.lookup(op)) -// return; - -// pending_operation_queue.push_back(op); -// is_operation_enqueued[op] = true; - -// if (isVerboseMode()) { -// llvm::outs() << "[neura-interpreter] pending_operation_queue Added: " << op->getName() << "\n"; -// } -// } - /** * @brief Handles the execution of an arithmetic constant operation (arith.constant) by parsing its value and storing it in the value map. * @@ -284,7 +179,7 @@ bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap(attr)) { @@ -1586,146 +1481,6 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value return true; } -/** - * @brief Handles the execution of a Neura load operation (neura.load) by reading a value from memory at a specified address. - * - * This function processes Neura's memory load operations, which take 1-2 operands: a memory address (stored as a float) and an optional predicate operand. - * It reads the value from memory at the specified address, with support for 32-bit floats, 32-bit integers, and booleans (1-bit integers). The operation - * is skipped if the combined predicate (input address predicate + optional predicate operand) is false, returning a default value of 0.0f. Errors are - * returned for invalid operand counts or unsupported data types. - * - * @param op The neura.load operation to handle - * @param value_to_predicated_data_map Reference to the map where the loaded value will be stored, keyed by the operation's result value - * @param mem Reference to the memory object used to read the value - * @return bool True if the load is successfully executed (including skipped loads); false for invalid operands or unsupported types - */ -bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.load:\n"; - } - - if (op.getNumOperands() < 1 || op.getNumOperands() > 2) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.load expects 1 or 2 operands (address, [predicate])\n"; - } - return false; - } - // Convert address from float to size_t (memory address type) - auto addr_val = value_to_predicated_data_map[op.getOperand(0)]; - bool final_predicate = addr_val.predicate; - - if (op.getNumOperands() > 1) { - auto pred_val = value_to_predicated_data_map[op.getOperand(1)]; - final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_val.value - << ", [pred = " << pred_val.predicate << "]\n"; - } - } - - float val = 0.0f; - size_t addr = static_cast(addr_val.value); - // Perform load only if final predicate is true - if (final_predicate) { - auto result_type = op.getResult().getType(); - if (result_type.isF32()) { - val = mem.load(addr); - } else if (result_type.isInteger(32)) { - val = static_cast(mem.load(addr)); - } else if (result_type.isInteger(1)) { - val = static_cast(mem.load(addr)); - } else { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported load type\n"; - } - return false; - } - } else { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Load skipped due to [pred = 0]\n"; - } - val = 0.0f; // Default value when load is skipped - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Load [addr = " << addr << "] => val = " - << val << ", [pred = " << final_predicate << "]\n"; - } - - value_to_predicated_data_map[op.getResult()] = { val, final_predicate }; - return true; -} - -/** - * @brief Handles the execution of a Neura store operation (neura.store) by writing a value to memory at a specified address. - * - * This function processes Neura's memory store operations, which take 2-3 operands: a value to store, a memory address (both stored as floats), - * and an optional predicate operand. It writes the value to memory at the specified address if the combined predicate (address predicate + - * optional predicate operand) is true. Supported types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). The operation - * is skipped if the predicate is false. Errors are returned for insufficient operands or unsupported data types. - * - * @param op The neura.store operation to handle - * @param value_to_predicated_data_map Reference to the map storing the value and address to be used for the store - * @param mem Reference to the memory object used to write the value - * @return bool True if the store is successfully executed; false for invalid operands or unsupported types - */ -bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &value_to_predicated_data_map, Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.store:\n"; - } - - if (op.getNumOperands() < 2) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.store expects at least two operands (value, address)\n"; - } - return false; - } - - auto val_data = value_to_predicated_data_map[op.getOperand(0)]; /* Value to store */ - auto addr_val = value_to_predicated_data_map[op.getOperand(1)]; /* Target address */ - bool final_predicate = addr_val.predicate; /* Base predicate from address validity */ - - if (op.getNumOperands() > 2) { - auto pred_val = value_to_predicated_data_map[op.getOperand(2)]; - final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred_val.value - << ", [pred = " << pred_val.predicate << "]\n"; - } - } - // Convert address from float to size_t (memory address type) - size_t addr = static_cast(addr_val.value); - // Perform store only if final predicate is true - if(final_predicate) { - auto val_type = op.getOperand(0).getType(); - if (val_type.isF32()) { - mem.store(addr, val_data.value); - } else if (val_type.isInteger(32)) { - mem.store(addr, static_cast(val_data.value)); - } else if (val_type.isInteger(1)) { - mem.store(addr, (val_data.value != 0.0f)); - } else { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported store type\n"; - } - return false; - } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Store [addr = " << addr - << "] => val = " << val_data.value - << ", [pred = 1" << "]\n"; - } - } else { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Store skipped due to [pred = 0]\n"; - } - } - - return true; -} - /** * @brief Handles the execution of a Neura Get Element Pointer operation (neura.gep) by computing a memory address from a base address and indices. * @@ -1852,190 +1607,6 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_ return true; } -/** - * @brief Handles the execution of a Neura indexed load operation (neura.load_indexed) by loading a value from memory using a base address and summed indices. - * - * This function processes Neura's indexed load operations, which calculate a target memory address by adding a base address to the sum of multiple indices. - * It supports scalar values only (no vectors) for the base address, indices, and predicate. The operation loads data from the computed address if the combined - * predicate (validity of base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). - * - * @param op The neura.load_indexed operation to handle - * @param value_to_predicated_data_map Reference to the map storing values (base address, indices, predicate) and where the loaded result will be stored - * @param mem Reference to the memory object used to read the value - * @return bool True if the indexed load is successfully executed; false for vector inputs, unsupported types, or errors - */ -bool handleLoadIndexedOp(neura::LoadIndexedOp op, - llvm::DenseMap &value_to_predicated_data_map, - Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Executing neura.load_indexed:\n"; - } - - // Retrieve base address and validate it is not a vector - auto base_val = value_to_predicated_data_map[op.getBase()]; - if (base_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector base not supported in load_indexed\n"; - } - return false; - } - - float base_F = base_val.value; /* Base address (stored as float) */ - bool final_predicate = base_val.predicate; /* Initialize predicate with base's validity */ - - // Calculate total offset by summing all indices (validate indices are not vectors) - // Todo: multi-dimensional index will be supported in the future - float offset = 0.0f; - for (Value idx : op.getIndices()) { - auto idx_val = value_to_predicated_data_map[idx]; - if (idx_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector index not supported in load_indexed\n"; - } - return false; - } - // Accumulate index values into total offset - offset += idx_val.value; - final_predicate = final_predicate && idx_val.predicate; - } - - // Incorporate optional predicate operand (validate it is not a vector) - if (op.getPredicate()) { - Value pred_operand = op.getPredicate(); - auto pred_val = value_to_predicated_data_map[pred_operand]; - if (pred_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; - } - return false; - } - final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); - } - - // Compute target address (base + total offset) and initialize loaded value - size_t addr = static_cast(base_F + offset); - float val = 0.0f; - - // Perform load only if final predicate is true (valid address and conditions) - if (final_predicate) { - auto result_type = op.getResult().getType(); - if (result_type.isF32()) { - val = mem.load(addr); - } else if (result_type.isInteger(32)) { - val = static_cast(mem.load(addr)); - } else if (result_type.isInteger(1)) { - val = static_cast(mem.load(addr)); - } else { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported result type\n"; - } - return false; - } - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ LoadIndexed [addr = " << addr << "] => val = " - << val << ", [pred = " << final_predicate << "]\n"; - } - - value_to_predicated_data_map[op.getResult()] = { val, final_predicate, false, {}, false }; - return true; -} - -/** - * @brief Handles the execution of a Neura indexed store operation (neura.store_indexed) by storing a value to memory using a base address and summed indices. - * - * This function processes Neura's indexed store operations, which calculate a target memory address by adding a base address to the sum of multiple indices, then stores a value at that address. - * It supports scalar values only (no vectors) for the value to store, base address, indices, and predicate. The operation performs the store only if the combined predicate (validity of the value, - * base, indices, and optional predicate operand) is true. Supported data types include 32-bit floats, 32-bit integers, and booleans (1-bit integers). - * - * @param op The neura.store_indexed operation to handle - * @param value_to_predicated_data_map Reference to the map storing the value to store, base address, indices, and predicate - * @param mem Reference to the memory object used to write the value - * @return bool True if the indexed store is successfully executed; false for vector inputs, unsupported types, or errors - */ -bool handleStoreIndexedOp(neura::StoreIndexedOp op, - llvm::DenseMap &value_to_predicated_data_map, - Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.store_indexed:\n"; - } - - auto val_to_store = value_to_predicated_data_map[op.getValue()]; - if (val_to_store.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector value not supported in store_indexed\n"; - } - return false; - } - float value = val_to_store.value; - bool final_predicate = val_to_store.predicate; - - auto base_val = value_to_predicated_data_map[op.getBase()]; - if (base_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector base not supported in store_indexed\n"; - } - return false; - } - - // Retrieve base address and validate it is not a vector - float base_F = base_val.value; - final_predicate = final_predicate && base_val.predicate; - - // Calculate total offset by summing all indices (validate indices are not vectors) - // Todo: multi-dimensional index will be supported in the future - float offset = 0.0f; - for (Value idx : op.getIndices()) { - auto idx_val = value_to_predicated_data_map[idx]; - if (idx_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector index not supported in store_indexed\n"; - } - return false; - } - offset += idx_val.value; - final_predicate = final_predicate && idx_val.predicate; - } - - if (op.getPredicate()) { - Value pred_operand = op.getPredicate(); - auto pred_val = value_to_predicated_data_map[pred_operand]; - if (pred_val.is_vector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; - } - return false; - } - final_predicate = final_predicate && pred_val.predicate && (pred_val.value != 0.0f); - } - - size_t addr = static_cast(base_F + offset); - - if (final_predicate) { - auto val_type = op.getValue().getType(); - if (val_type.isF32()) { - mem.store(addr, value); - } else if (val_type.isInteger(32)) { - mem.store(addr, static_cast(value)); - } else if (val_type.isInteger(1)) { - mem.store(addr, value != 0.0f); - } else { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported value type in store_indexed\n"; - } - return false; - } - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ StoreIndexed [addr = " << addr << "] <= val = " - << value << ", [pred = " << final_predicate << "]\n"; - } - - return true; -} - /** * @brief Handles the execution of a Neura unconditional branch operation (neura.br) by transferring control to a target block. * @@ -2881,6 +2452,44 @@ bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap Vector of operations with no intra-sequence dependencies + */ +std::vector findIndependentInSequence( + const std::vector &op_sequence) { + std::vector independent_ops; + // Track all values defined by operations within the sequence for quick lookup + llvm::DenseSet values_defined_in_sequence; + + for (Operation *op : op_sequence) { + for (Value result : op->getResults()) { + values_defined_in_sequence.insert(result); + } + } + + for (Operation *op : op_sequence) { + bool has_intra_dependencies = false; + + for (Value operand : op->getOperands()) { + if (values_defined_in_sequence.count(operand)) { + has_intra_dependencies = true; + break; + } + } + + if (!has_intra_dependencies) { + independent_ops.push_back(op); + } + } + + return independent_ops; +} + /** * @brief Generic operation handling function that unifies type checking for both execution modes * @@ -2890,7 +2499,6 @@ bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap& value_to_predicated_data_map, - Memory& mem, Block**current_block = nullptr, Block** last_visited_block = nullptr) { @@ -2943,16 +2550,8 @@ OperationHandleResult handleOperation( result.success = handleSelOp(sel_op, value_to_predicated_data_map); } else if (auto cast_op = dyn_cast(op)) { result.success = handleCastOp(cast_op, value_to_predicated_data_map); - } else if (auto load_op = dyn_cast(op)) { - result.success = handleLoadOp(load_op, value_to_predicated_data_map, mem); - } else if (auto store_op = dyn_cast(op)) { - result.success = handleStoreOp(store_op, value_to_predicated_data_map, mem); } else if (auto gep_op = dyn_cast(op)) { result.success = handleGEPOp(gep_op, value_to_predicated_data_map); - } else if (auto load_index_op = dyn_cast(op)) { - result.success = handleLoadIndexedOp(load_index_op, value_to_predicated_data_map, mem); - } else if (auto store_index_op = dyn_cast(op)) { - result.success = handleStoreIndexedOp(store_index_op, value_to_predicated_data_map, mem); } else if (auto br_op = dyn_cast(op)) { // Branch operations only need block handling in control flow mode if (current_block && last_visited_block) { @@ -3006,14 +2605,12 @@ OperationHandleResult handleOperation( * specific logic for dependency management and pending operation queue updates. * * @param op The operation to execute - * @param value_to_predicated_data_map Reference to map storing predicated data for values - * @param mem Reference to memory object for load/store operations + * @param value_to_predicated_data_map Reference to map storing predicated data for valuess * @param next_pending_operation_queue Queue to receive dependent operations needing processing * @return bool True if operation executed successfully, false otherwise */ bool executeOperation(Operation* op, llvm::DenseMap& value_to_predicated_data_map, - Memory& mem, llvm::SmallVector& next_pending_operation_queue) { // Save current values to detect updates after execution @@ -3025,7 +2622,7 @@ bool executeOperation(Operation* op, } // Process the operation using the generic handler (no block info needed for data flow) - auto handle_result = handleOperation(op, value_to_predicated_data_map, mem); + auto handle_result = handleOperation(op, value_to_predicated_data_map); if (!handle_result.success) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation failed, no propagation\n"; @@ -3033,16 +2630,6 @@ bool executeOperation(Operation* op, return false; } - // Check if any operands are invalid (data flow specific validation) - bool has_invalid_operands = false; - for (Value operand : op->getOperands()) { - if (value_to_predicated_data_map.count(operand) && - !value_to_predicated_data_map[operand].predicate) { - has_invalid_operands = true; - break; - } - } - // Check if any results were updated during execution bool has_update = false; for (Value result : op->getResults()) { @@ -3084,7 +2671,7 @@ bool executeOperation(Operation* op, } // Propagate updates to dependent operations if changes occurred - if (has_update && !has_invalid_operands) { + if (has_update) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; } @@ -3147,19 +2734,30 @@ bool executeOperation(Operation* op, * @return int 0 if execution completes successfully, 1 on error */ int run(func::FuncOp func, - llvm::DenseMap& value_to_predicated_data_map, - Memory& mem) { + llvm::DenseMap& value_to_predicated_data_map) { if (isDataflowMode()) { // Data flow mode execution logic // Initialize pending operation queue with all operations except return operations + + std::vector op_seq; for (auto& block : func.getBody()) { for (auto& op : block.getOperations()) { - if (isa(op) || isa(op)) { - continue; - } else { - pending_operation_queue.push_back(&op); - is_operation_enqueued[&op] = true; - } + op_seq.emplace_back(&op); + } + } + + // Dependency graph to track operation dependencies + DependencyGraph dep_graph; + dep_graph.buildDependencyGraph(op_seq); + + std::vector independent_ops = findIndependentInSequence(op_seq); + + for (auto* op : independent_ops) { + pending_operation_queue.emplace_back(op); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Initial pending operation: "; + op->print(llvm::outs()); + llvm::outs() << "\n"; } } @@ -3168,6 +2766,7 @@ int run(func::FuncOp func, while (!pending_operation_queue.empty()) { iter_count++; if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ----------------------------------------\n"; llvm::outs() << "[neura-interpreter] Iteration " << iter_count << " | pending_operation_queue: " << pending_operation_queue.size() << "\n"; } @@ -3175,8 +2774,19 @@ int run(func::FuncOp func, llvm::SmallVector next_pending_operation_queue; for (Operation* op : pending_operation_queue) { is_operation_enqueued[op] = false; - if (!executeOperation(op, value_to_predicated_data_map, mem, next_pending_operation_queue)) { - return 1; + if (dep_graph.canExecute(op)) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ========================================\n"; + llvm::outs() << "[neura-interpreter] Executing operation: " << *op << "\n"; + } + if (!executeOperation(op, value_to_predicated_data_map, next_pending_operation_queue)) { + return 1; + } + dep_graph.updateAfterExecution(op); + } else { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Skipping operation (dependencies not satisfied): " << *op << "\n"; + } } } pending_operation_queue = std::move(next_pending_operation_queue); @@ -3199,7 +2809,7 @@ int run(func::FuncOp func, Operation& op = *std::next(operations.begin(), op_index); // Process operation with block information for control flow handling - auto handle_result = handleOperation(&op, value_to_predicated_data_map, mem, ¤t_block, &last_visited_block); + auto handle_result = handleOperation(&op, value_to_predicated_data_map, ¤t_block, &last_visited_block); if (!handle_result.success) return 1; if (handle_result.is_terminated) { @@ -3258,10 +2868,9 @@ int main(int argc, char **argv) { // Initialize data structures llvm::DenseMap value_to_predicated_data_map; - Memory mem(1024); // 1MB memory allocation for (auto func : module->getOps()) { - if (run(func, value_to_predicated_data_map, mem)) { + if (run(func, value_to_predicated_data_map)) { llvm::errs() << "[neura-interpreter] Execution failed\n"; return 1; } From 76272876d0de4bca63d40cacebe474149d0dbecc Mon Sep 17 00:00:00 2001 From: item Date: Sun, 17 Aug 2025 17:40:03 +0800 Subject: [PATCH 5/6] add interpreter dataflow mode --- tools/neura-interpreter/neura-interpreter.cpp | 222 ++++++++---------- 1 file changed, 96 insertions(+), 126 deletions(-) diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index 04a0d776..93df5881 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -15,13 +15,13 @@ #include "NeuraDialect/NeuraDialect.h" #include "NeuraDialect/NeuraOps.h" +#include #include #include #include -#include #include -#include #include +#include using namespace mlir; @@ -68,21 +68,24 @@ struct OperationHandleResult { /** @brief Structure to represent the dependency graph of operations */ struct DependencyGraph { - llvm::DenseMap dep_count; /* Stores the current dependency count of each operation */ - llvm::DenseSet executed_ops; /* Records the operations that have been executed */ + // Tracks the number of pending producer operations that each consumer operation is waiting for + llvm::DenseMap consumer_pending_producers; + // Records operations that have been executed + llvm::DenseSet executed_ops; /** - * @brief Builds a dependency graph for operations, calculating the initial dependency count (in-degree) - * for each operation. The dependency count is defined as the number of preceding operations within the - * same sequence that the current operation depends on. + * @brief Builds a dependency graph for operations, calculating the initial count of producer operations + * that each consumer operation depends on. This count represents how many preceding producer operations + * within the same sequence the current consumer operation relies on. * * @param op_sequence The sequence of operations for which to build the dependency graph. */ - void buildDependencyGraph(const std::vector& op_sequence); + void build(const std::vector& op_sequence); /** - * @brief Checks if an operation can be executed based on its dependency count. - * An operation can be executed if its dependency count is zero and it has not been executed yet. + * @brief Determines if an operation can be executed based on its count of pending producers. + * An operation is executable when it has zero pending producers (all dependencies have been satisfied) + * and it hasn't been executed yet. * * @param op The operation to check for execution eligibility. * @return true if the operation can be executed; false otherwise. @@ -91,49 +94,48 @@ struct DependencyGraph { /** * @brief Updates the dependency graph after an operation has been executed. - * This involves marking the operation as executed and updating the dependency counts of its users. + * This involves marking the operation as executed and decrementing the pending producer count + * for all consumer operations that depend on it. * - * @param executed_op The operation that has been executed. + * @param executed_op The operation that has been executed (acts as a producer). */ void updateAfterExecution(Operation* executed_op); }; -void DependencyGraph::buildDependencyGraph(const std::vector& op_sequence) { - for (Operation* op : op_sequence) { - int deps = 0; - // Traverse all operands (input values) of the current operation to analyze dependency sources - for (Value operand : op->getOperands()) { - if (Operation* def_op = operand.getDefiningOp()) { - if (std::find(op_sequence.begin(), op_sequence.end(), def_op) != op_sequence.end()) { - deps++; +void DependencyGraph::build(const std::vector& op_sequence) { + for (Operation* consumer_op : op_sequence) { + int required_producers = 0; + // Count how many producer operations this consumer depends on + for (Value operand : consumer_op->getOperands()) { + if (Operation* producer_op = operand.getDefiningOp()) { + // Only count producers within the same operation sequence + if (std::find(op_sequence.begin(), op_sequence.end(), producer_op) != op_sequence.end()) { + required_producers++; } } } - dep_count[op] = deps; + consumer_pending_producers[consumer_op] = required_producers; } } bool DependencyGraph::canExecute(Operation* op) { - auto it = dep_count.find(op); - // The operation is executable if: - // 1. It exists in our dependency graph (it != dep_count.end()) - // 2. It has no unmet dependencies (dependency count == 0) - return it != dep_count.end() && it->second == 0; + auto it = consumer_pending_producers.find(op); + // Operation is executable if: + // 1. It exists in the dependency graph + // 2. It has no pending producers (all dependencies satisfied) + return it != consumer_pending_producers.end() && it->second == 0; } void DependencyGraph::updateAfterExecution(Operation* executed_op) { - // Mark as executed + // Mark the completed operation as executed (it acts as a producer) executed_ops.insert(executed_op); - // Traverse all output results produced by the executed operation + // Update all consumer operations that depend on this producer for (Value result : executed_op->getResults()) { - // Get all operations that use this result (dependent operations) - for (Operation* user : result.getUsers()) { - // Find the dependent operation in our dependency count map - auto it = dep_count.find(user); - // If the dependent operation is in our graph and has remaining dependencies, - // reduce its dependency count by 1 (since this executed operation was one of its dependencies) - if (it != dep_count.end() && it->second > 0) { + for (Operation* consumer_op : result.getUsers()) { + auto it = consumer_pending_producers.find(consumer_op); + // Decrement pending producer count for valid consumers + if (it != consumer_pending_producers.end() && it->second > 0) { it->second--; } } @@ -2106,95 +2108,65 @@ bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap */ bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap& value_to_predicated_data_map) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov" + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov" << (isDataflowMode() ? "(dataflow)" : "") << ":\n"; - } + } - Value source = op.getValue(); - Value target = op.getTarget(); + Value source = op.getValue(); + Value target = op.getTarget(); - // Check source exists in map - if (!value_to_predicated_data_map.count(source)) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map\n"; - } - return false; + if (!value_to_predicated_data_map.count(source)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map\n"; } + return false; + } - // Check target is a reserved placeholder - if (!value_to_predicated_data_map.count(target) || !value_to_predicated_data_map[target].is_reserve) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder\n"; - } - return false; + if (!value_to_predicated_data_map.count(target) || !value_to_predicated_data_map[target].is_reserve) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder\n"; } + return false; + } - const auto& source_data = value_to_predicated_data_map[source]; - auto& target_data = value_to_predicated_data_map[target]; - bool success = true; - - if (!isDataflowMode()) { - // Control-flow mode: Validate type match and unconditionally copy data - if (source.getType() != target.getType()) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Error: Type mismatch (source=" - << source.getType() << ", target=" << target.getType() << ")\n"; - } - return false; - } - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << source << "\n" - << "[neura-interpreter] │ └─ value = " << source_data.value - << ", [pred = " << source_data.predicate << "]\n" - << "[neura-interpreter] ├─ Target: " << target << "\n" - << "[neura-interpreter] │ └─ value = " << target_data.value - << ", [pred = " << target_data.predicate << "]\n"; - } + const auto& source_data = value_to_predicated_data_map[source]; + auto& target_data = value_to_predicated_data_map[target]; - // Unconditional copy for control flow - target_data.value = source_data.value; - target_data.predicate = source_data.predicate; - target_data.is_vector = source_data.is_vector; - if (source_data.is_vector) { - target_data.vector_data = source_data.vector_data; - } + if (!isDataflowMode() && source.getType() != target.getType()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Error: Type mismatch (source=" + << source.getType() << ", target = " << target.getType() << ")\n"; + } + return false; + } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n" - << "[neura-interpreter] └─ value = " << target_data.value - << ", [pred = " << target_data.predicate << "]\n"; - } + const PredicatedData old_target_data = target_data; + const bool should_update = isDataflowMode() ? (source_data.predicate == 1) : true; - } else { // DataFlow mode - // Data-flow mode: Conditional update based on source predicate - const bool should_update = (source_data.predicate == 1); - const auto old_target_data = target_data; // Store current state for update check - - if (should_update) { - // Copy source data to target - target_data.value = source_data.value; - target_data.predicate = source_data.predicate; - target_data.is_vector = source_data.is_vector; - target_data.vector_data = source_data.is_vector ? source_data.vector_data : std::vector(); - } else if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate invalid (pred=" - << source_data.predicate << ")\n"; - } + if (should_update) { + target_data.value = source_data.value; + target_data.predicate = source_data.predicate; + target_data.is_vector = source_data.is_vector; + if (source_data.is_vector) { + target_data.vector_data = source_data.vector_data; + } + } else if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Skip update: Source predicate invalid (pred=" + << source_data.predicate << ")\n"; + } - // Calculate is_updated by comparing with old state - target_data.is_updated = target_data.isUpdatedComparedTo(old_target_data); + target_data.is_updated = target_data.isUpdatedComparedTo(old_target_data); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Source: " << source_data.value << " | " << source_data.predicate << "\n" - << "[neura-interpreter] ├─ Target (after): " << target_data.value << " | " << target_data.predicate - << " | is_updated=" << target_data.is_updated << "\n" - << "[neura-interpreter] └─ Execution succeeded\n"; - } - } + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Source: " << source << ", value = " << source_data.value + << ", [pred = " << source_data.predicate << "]\n" + << "[neura-interpreter] ├─ Target: " << target << ", value = " << target_data.value + << ", [pred = " << target_data.predicate << "]\n" + << "[neura-interpreter] └─ Updated (is_updated = " << target_data.is_updated << ")\n"; + } - return success; + return true; } /** @@ -2319,7 +2291,7 @@ bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap independent_ops = findIndependentInSequence(op_seq); for (auto* op : independent_ops) { @@ -2774,15 +2744,15 @@ int run(func::FuncOp func, llvm::SmallVector next_pending_operation_queue; for (Operation* op : pending_operation_queue) { is_operation_enqueued[op] = false; - if (dep_graph.canExecute(op)) { + if (consumer_dependent_on_producer_graph.canExecute(op)) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ========================================\n"; llvm::outs() << "[neura-interpreter] Executing operation: " << *op << "\n"; } if (!executeOperation(op, value_to_predicated_data_map, next_pending_operation_queue)) { - return 1; + return EXIT_FAILURE; } - dep_graph.updateAfterExecution(op); + consumer_dependent_on_producer_graph.updateAfterExecution(op); } else { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Skipping operation (dependencies not satisfied): " << *op << "\n"; @@ -2811,7 +2781,7 @@ int run(func::FuncOp func, // Process operation with block information for control flow handling auto handle_result = handleOperation(&op, value_to_predicated_data_map, ¤t_block, &last_visited_block); - if (!handle_result.success) return 1; + if (!handle_result.success) return EXIT_FAILURE; if (handle_result.is_terminated) { is_terminated = true; op_index++; @@ -2825,7 +2795,7 @@ int run(func::FuncOp func, } } - return 0; + return EXIT_SUCCESS; } int main(int argc, char **argv) { @@ -2840,7 +2810,7 @@ int main(int argc, char **argv) { if (argc < 2) { llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose] [--dataflow]\n"; - return 1; + return EXIT_FAILURE; } // Initialize MLIR context and dialects @@ -2855,7 +2825,7 @@ int main(int argc, char **argv) { auto file_or_err = mlir::openInputFile(argv[1]); if (!file_or_err) { llvm::errs() << "[neura-interpreter] Error opening file\n"; - return 1; + return EXIT_FAILURE; } source_mgr.AddNewSourceBuffer(std::move(file_or_err), llvm::SMLoc()); @@ -2863,7 +2833,7 @@ int main(int argc, char **argv) { OwningOpRef module = parseSourceFile(source_mgr, &context); if (!module) { llvm::errs() << "[neura-interpreter] Failed to parse MLIR input file\n"; - return 1; + return EXIT_FAILURE; } // Initialize data structures @@ -2872,9 +2842,9 @@ int main(int argc, char **argv) { for (auto func : module->getOps()) { if (run(func, value_to_predicated_data_map)) { llvm::errs() << "[neura-interpreter] Execution failed\n"; - return 1; + return EXIT_FAILURE; } } - return 0; + return EXIT_SUCCESS; } \ No newline at end of file From ca864d2104c27eee30d98c07a0a9276fd6ac5d6a Mon Sep 17 00:00:00 2001 From: item Date: Thu, 21 Aug 2025 22:16:25 +0800 Subject: [PATCH 6/6] add interpreter dataflow mode --- tools/neura-interpreter/neura-interpreter.cpp | 1036 +++++++++++------ 1 file changed, 655 insertions(+), 381 deletions(-) diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index 93df5881..96866621 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -25,29 +25,34 @@ using namespace mlir; -// Predicated data structure, used to store scalar/vector values and related metadata +// Predicated data structure that stores scalar/vector values and related metadata. struct PredicatedData { - float value; /* Scalar floating-point value (valid when is_vector is false) */ - bool predicate; /* Validity flag: true means the value is valid, false means it should be ignored */ - bool is_vector; /* Indicates if it's a vector: true for vector, false for scalar */ - std::vector vector_data; /* Vector data (valid when is_vector is true) */ - bool is_reserve; /* Reserve flag (may be used for memory reservation or temporary storage marking) */ - bool is_updated; /* Update flag (indicates whether the data has been modified) */ - + float value; /* Scalar floating-point value (valid when is_vector is false). */ + bool predicate; /* Validity flag: true means the value is valid, false means it should be ignored. */ + bool is_vector; /* Indicates if it's a vector: true for vector, false for scalar. */ + std::vector vector_data; /* Vector data (valid when is_vector is true). */ + bool is_reserve; /* Reserve flag (may be used for memory reservation or temporary storage marking). */ + bool is_updated; /* Update flag (indicates whether the data has been modified). */ + /** + * @brief Constructs a new PredicatedData object with default values. + * + * Initializes all member variables to their default states: scalar value 0.0f, + * valid predicate, scalar type, empty vector data, and flags set to false. + */ PredicatedData() : value{0.0f}, predicate{true}, is_vector{false}, vector_data{}, is_reserve{false}, is_updated{false} {} /** - * @brief Compares this PredicatedData instance with another to check if the data has been updated. - * - * This function determines whether any part of the current PredicatedData differs from another instance. - * It compares scalar values, predicate flags, vector flags, and vector contents (if applicable). - * This is useful in dataflow analysis to determine whether a value change should trigger downstream updates. - * - * @param other The PredicatedData instance to compare against. - * @return bool True if any field differs, indicating an update; false if all fields are equal. - */ + * @brief Compares this PredicatedData instance with another to check for updates. + * + * Determines whether any part of the current PredicatedData differs from another instance. + * Compares scalar values, predicate flags, vector flags, and vector contents (if applicable). + * Useful in dataflow analysis to determine if a value change should trigger downstream updates. + * + * @param other The PredicatedData instance to compare against. + * @return bool True if any field differs (indicating an update); false if all fields are equal. + */ bool isUpdatedComparedTo(const PredicatedData& other) const; }; @@ -59,18 +64,22 @@ bool PredicatedData::isUpdatedComparedTo(const PredicatedData& other) const { return false; } -/** @brief Structure to hold operation handling results and control flow information */ +/** + * @brief Structure that holds operation handling results and control flow information. + */ struct OperationHandleResult { - bool success; /* Indicates if the operation was processed successfully */ - bool is_terminated; /* Indicates if execution should terminate (e.g., after return operations) */ - bool is_branch; /* Indicates if the operation is a branch (requires special index handling) */ + bool success; /* Indicates if the operation was processed successfully. */ + bool is_terminated; /* Indicates if execution should terminate (e.g., after return operations). */ + bool is_branch; /* Indicates if the operation is a branch (requires special index handling). */ }; -/** @brief Structure to represent the dependency graph of operations */ +/** + * @brief Structure that represents the dependency graph of operations. + */ struct DependencyGraph { - // Tracks the number of pending producer operations that each consumer operation is waiting for + // Tracks the number of pending producer operations that each consumer operation is waiting for. llvm::DenseMap consumer_pending_producers; - // Records operations that have been executed + // Records operations that have been executed. llvm::DenseSet executed_ops; /** @@ -105,10 +114,10 @@ struct DependencyGraph { void DependencyGraph::build(const std::vector& op_sequence) { for (Operation* consumer_op : op_sequence) { int required_producers = 0; - // Count how many producer operations this consumer depends on + // Counts how many producer operations this consumer depends on. for (Value operand : consumer_op->getOperands()) { if (Operation* producer_op = operand.getDefiningOp()) { - // Only count producers within the same operation sequence + // Counts only producers within the same operation sequence. if (std::find(op_sequence.begin(), op_sequence.end(), producer_op) != op_sequence.end()) { required_producers++; } @@ -121,20 +130,20 @@ void DependencyGraph::build(const std::vector& op_sequence) { bool DependencyGraph::canExecute(Operation* op) { auto it = consumer_pending_producers.find(op); // Operation is executable if: - // 1. It exists in the dependency graph - // 2. It has no pending producers (all dependencies satisfied) + // 1. It exists in the dependency graph. + // 2. It has no pending producers (all dependencies satisfied). return it != consumer_pending_producers.end() && it->second == 0; } void DependencyGraph::updateAfterExecution(Operation* executed_op) { - // Mark the completed operation as executed (it acts as a producer) + // Marks the completed operation as executed (it acts as a producer). executed_ops.insert(executed_op); - // Update all consumer operations that depend on this producer + // Updates all consumer operations that depend on this producer. for (Value result : executed_op->getResults()) { for (Operation* consumer_op : result.getUsers()) { auto it = consumer_pending_producers.find(consumer_op); - // Decrement pending producer count for valid consumers + // Decrements pending producer count for valid consumers. if (it != consumer_pending_producers.end() && it->second > 0) { it->second--; } @@ -142,11 +151,11 @@ void DependencyGraph::updateAfterExecution(Operation* executed_op) { } } -static llvm::SmallVector pending_operation_queue; /* List of operations to execute in current iteration */ -static llvm::DenseMap is_operation_enqueued; /* Marks whether an operation is already in pending_operation_queue */ +static llvm::SmallVector pending_operation_queue; /* List of operations to execute in current iteration. */ +static llvm::DenseMap is_operation_enqueued; /* Marks whether an operation is already in pending_operation_queue. */ -static bool verbose = false; /* Verbose logging mode switch: outputs debug information when true */ -static bool dataflow = false; /* Dataflow analysis mode switch: enables dataflow-related analysis logic when true */ +static bool verbose = false; /* Verbose logging mode switch: outputs debug information when true. */ +static bool dataflow = false; /* Dataflow analysis mode switch: enables dataflow-related analysis logic when true. */ inline void setDataflowMode(bool v) { dataflow = v; @@ -165,17 +174,23 @@ inline bool isVerboseMode() { } /** - * @brief Handles the execution of an arithmetic constant operation (arith.constant) by parsing its value and storing it in the value map. + * @brief Handles the execution of an arithmetic constant operation (arith.constant) by parsing + * its value and storing it in the value map. * - * This function processes MLIR's arith.constant operations, which represent constant values. It extracts the constant value from the operation's - * attribute, converts it to a floating-point representation (supporting floats, integers, and booleans), and stores it in the value map with a - * predicate set to true (since constants are always valid). Unsupported constant types result in an error. + * This function processes MLIR's arith.constant operations, which represent constant values. It + * extracts the constant value from the operation's attribute, converts it to a floating-point + * representation (supporting floats, integers, and booleans), and stores it in the value map with + * a predicate set to true (since constants are always valid). Unsupported constant types result + * in an error. * * @param op The arith.constant operation to handle - * @param value_to_predicated_data_map Reference to the map where the parsed constant value will be stored, keyed by the operation's result value - * @return bool True if the constant is successfully parsed and stored; false if the constant type is unsupported + * @param value_to_predicated_data_map Reference to the map where the parsed constant value + * will be stored, keyed by the operation's result value + * @return bool True if the constant is successfully parsed and stored; + * false if the constant type is unsupported */ -bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_to_predicated_data_map) { +bool handleArithConstantOp(mlir::arith::ConstantOp op, + llvm::DenseMap& value_to_predicated_data_map) { auto attr = op.getValue(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing arith.constant:\n"; @@ -183,7 +198,7 @@ bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap(attr)) { val.value = float_attr.getValueAsDouble(); if (isVerboseMode()) { @@ -191,7 +206,7 @@ bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap(attr)) { if(int_attr.getType().isInteger(1)) { val.value = int_attr.getInt() != 0 ? 1.0f : 0.0f; @@ -207,7 +222,7 @@ bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& value_to_predicated_data_map) { +bool handleNeuraConstantOp(neura::ConstantOp op, + llvm::DenseMap& value_to_predicated_data_map) { auto attr = op.getValue(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.constant:\n"; } - // Handle floating-point scalar constants + // Handles floating-point scalar constants. if (auto float_attr = llvm::dyn_cast(attr)) { PredicatedData val; val.value = float_attr.getValueAsDouble(); @@ -251,7 +275,7 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap(attr)) { PredicatedData val; val.value = static_cast(int_attr.getInt()); @@ -265,7 +289,7 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap(attr)) { if (!dense_attr.getElementType().isF32()) { if (isVerboseMode()) { @@ -295,7 +319,7 @@ bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleAddOp(neura::AddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.add:\n"; } @@ -368,18 +397,26 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &value_t } /** - * @brief Handles the execution of a Neura subtraction operation (neura.sub) by computing the difference of integer operands. + * @brief Handles the execution of a Neura subtraction operation (neura.sub) by + * computing the difference of integer operands. * - * This function processes Neura's subtraction operations, which take 2-3 operands: two integer inputs (LHS and RHS) - * and an optional predicate operand. It computes the difference of the integer values (LHS - RHS), combines the - * predicates of all operands (including the optional predicate if present), and stores the result in the value map. - * The operation requires at least two operands; fewer will result in an error. + * This function processes Neura's subtraction operations, which take 2-3 operands: + * two integer inputs (LHS and RHS) and an optional predicate operand. It computes + * the difference of the integer values (LHS - RHS), combines the predicates of all + * operands (including the optional predicate if present), and stores the result in + * the value map. The operation requires at least two operands; fewer will result + * in an error. * * @param op The neura.sub operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the subtraction is successfully computed; false if there are fewer than 2 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the subtraction is successfully + * computed; false if there are fewer than + * 2 operands */ -bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleSubOp(neura::SubOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sub:\n"; } @@ -429,18 +466,26 @@ bool handleSubOp(neura::SubOp op, llvm::DenseMap &value_t } /** - * @brief Handles the execution of a Neura floating-point addition operation (neura.fadd) by computing the sum of floating-point operands. - * - * This function processes Neura's floating-point addition operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) - * and an optional predicate operand. It computes the sum of the floating-point values, combines the predicates of all operands - * (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; + * @brief Handles the execution of a Neura floating-point addition operation + * (neura.fadd) by computing the sum of floating-point operands. + * + * This function processes Neura's floating-point addition operations, which take + * 2-3 operands: two floating-point inputs (LHS and RHS) and an optional predicate + * operand. It computes the sum of the floating-point values, combines the + * predicates of all operands (including the optional predicate if present), and + * stores the result in the value map. The operation requires at least two operands; * fewer will result in an error. * * @param op The neura.fadd operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point addition is successfully computed; false if there are fewer than 2 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the floating-point addition is + * successfully computed; false if there are + * fewer than 2 operands */ -bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFAddOp(neura::FAddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd:\n"; } @@ -485,18 +530,26 @@ bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura floating-point subtraction operation (neura.fsub) by computing the difference of floating-point operands. + * @brief Handles the execution of a Neura floating-point subtraction operation + * (neura.fsub) by computing the difference of floating-point operands. * - * This function processes Neura's floating-point subtraction operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) - * and an optional predicate operand. It calculates the difference of the floating-point values (LHS - RHS), combines the predicates of all - * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; - * fewer will result in an error. + * This function processes Neura's floating-point subtraction operations, which take + * 2-3 operands: two floating-point inputs (LHS and RHS) and an optional predicate + * operand. It calculates the difference of the floating-point values (LHS - RHS), + * combines the predicates of all operands (including the optional predicate if + * present), and stores the result in the value map. The operation requires at least + * two operands; fewer will result in an error. * * @param op The neura.fsub operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point subtraction is successfully computed; false if there are fewer than 2 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the floating-point subtraction + * is successfully computed; false if there + * are fewer than 2 operands */ -bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFSubOp(neura::FSubOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fsub:\n"; } @@ -541,18 +594,26 @@ bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura floating-point multiplication operation (neura.fmul) by computing the product of floating-point operands. - * - * This function processes Neura's floating-point multiplication operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) - * and an optional predicate operand. It calculates the product of the floating-point values (LHS * RHS), combines the predicates of all - * operands (including the optional predicate if present), and stores the result in the value map. The operation requires at least two operands; + * @brief Handles the execution of a Neura floating-point multiplication operation + * (neura.fmul) by computing the product of floating-point operands. + * + * This function processes Neura's floating-point multiplication operations, which take + * 2-3 operands: two floating-point inputs (LHS and RHS) and an optional predicate + * operand. It calculates the product of the floating-point values (LHS * RHS), combines + * the predicates of all operands (including the optional predicate if present), and + * stores the result in the value map. The operation requires at least two operands; * fewer will result in an error. * * @param op The neura.fmul operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point multiplication is successfully computed; false if there are fewer than 2 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the floating-point multiplication + * is successfully computed; false if there are + * fewer than 2 operands */ -bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFMulOp(neura::FMulOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul:\n"; } @@ -602,18 +663,27 @@ bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura floating-point division operation (neura.fdiv) by computing the quotient of floating-point operands. - * - * This function processes Neura's floating-point division operations, which take 2-3 operands: two floating-point inputs (dividend/LHS and divisor/RHS) - * and an optional predicate operand. It calculates the quotient of the floating-point values (LHS / RHS), handles division by zero by returning NaN, - * combines the predicates of all operands (including the optional predicate if present), and stores the result in the value map. The operation requires - * at least two operands; fewer will result in an error. + * @brief Handles the execution of a Neura floating-point division operation + * (neura.fdiv) by computing the quotient of floating-point operands. + * + * This function processes Neura's floating-point division operations, which take + * 2-3 operands: two floating-point inputs (dividend/LHS and divisor/RHS) and an + * optional predicate operand. It calculates the quotient of the floating-point + * values (LHS / RHS), handles division by zero by returning NaN, combines the + * predicates of all operands (including the optional predicate if present), and + * stores the result in the value map. The operation requires at least two operands; + * fewer will result in an error. * * @param op The neura.fdiv operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the floating-point division is successfully computed; false if there are fewer than 2 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the floating-point division is + * successfully computed; false if there are + * fewer than 2 operands */ -bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFDivOp(neura::FDivOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fdiv:\n"; } @@ -649,7 +719,7 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value float rhs_float = static_cast(rhs.value); if (rhs_float == 0.0f) { - // Return quiet NaN for division by zero to avoid runtime errors + // Returns quiet NaN for division by zero to avoid runtime errors. result_float = std::numeric_limits::quiet_NaN(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Warning: Division by zero, result is NaN\n"; @@ -673,18 +743,27 @@ bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura vector floating-point multiplication operation (neura.vfmul) by computing element-wise products of vector operands. + * @brief Handles the execution of a Neura vector floating-point multiplication + * operation (neura.vfmul) by computing element-wise products of vector operands. * - * This function processes Neura's vector floating-point multiplication operations, which take 2-3 operands: two vector inputs (LHS and RHS) - * and an optional scalar predicate operand. It validates that both primary operands are vectors of equal size, computes element-wise products, - * combines the predicates of all operands (including the optional scalar predicate if present), and stores the resulting vector in the value map. - * Errors are returned for invalid operand types (non-vectors), size mismatches, or vector predicates. + * This function processes Neura's vector floating-point multiplication operations, + * which take 2-3 operands: two vector inputs (LHS and RHS) and an optional scalar + * predicate operand. It validates that both primary operands are vectors of equal + * size, computes element-wise products, combines the predicates of all operands + * (including the optional scalar predicate if present), and stores the resulting + * vector in the value map. Errors are returned for invalid operand types (non-vectors), + * size mismatches, or vector predicates. * * @param op The neura.vfmul operation to handle - * @param value_to_predicated_data_map Reference to the map where the resulting vector will be stored, keyed by the operation's result value - * @return bool True if the vector multiplication is successfully computed; false if there are invalid operands, size mismatches, or other errors + * @param value_to_predicated_data_map Reference to the map where the resulting + * vector will be stored, keyed by the operation's + * result value + * @return bool True if the vector multiplication is + * successfully computed; false if there are + * invalid operands, size mismatches, or other errors */ -bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleVFMulOp(neura::VFMulOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.vfmul:\n"; } @@ -754,7 +833,7 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val result.is_vector = true; result.predicate = final_predicate; result.vector_data.resize(lhs.vector_data.size()); - // Compute element-wise multiplication + // Computes element-wise multiplication. for (size_t i = 0; i < lhs.vector_data.size(); ++i) { result.vector_data[i] = lhs.vector_data[i] * rhs.vector_data[i]; } @@ -770,18 +849,26 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val } /** - * @brief Handles the execution of a Neura chained floating-point addition operation (neura.fadd_fadd) by computing a three-operand sum. + * @brief Handles the execution of a Neura chained floating-point addition + * operation (neura.fadd_fadd) by computing a three-operand sum. * - * This function processes Neura's chained floating-point addition operations, which take 3-4 operands: three floating-point inputs (A, B, C) - * and an optional predicate operand. It calculates the sum using the order ((A + B) + C), combines the predicates of all operands - * (including the optional predicate if present), and stores the result in the value map. The operation requires at least three operands; - * fewer will result in an error. + * This function processes Neura's chained floating-point addition operations, + * which take 3-4 operands: three floating-point inputs (A, B, C) and an optional + * predicate operand. It calculates the sum using the order ((A + B) + C), combines + * the predicates of all operands (including the optional predicate if present), + * and stores the result in the value map. The operation requires at least three + * operands; fewer will result in an error. * * @param op The neura.fadd_fadd operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool True if the chained floating-point addition is successfully computed; false if there are fewer than 3 operands + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool True if the chained floating-point + * addition is successfully computed; false + * if there are fewer than 3 operands */ -bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFAddFAddOp(neura::FAddFAddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd_fadd:\n"; } @@ -816,7 +903,7 @@ bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFMulFAddOp(neura::FMulFAddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul_fadd:\n"; } @@ -883,7 +978,7 @@ bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFuncReturnOp(func::ReturnOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing func.return:\n"; } @@ -934,7 +1036,7 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap } auto result = value_to_predicated_data_map[op.getOperand(0)]; - // Print vector return value if the result is a vector + // Prints vector return value if the result is a vector. if (result.is_vector) { llvm::outs() << "[neura-interpreter] → Output: ["; for (size_t i = 0; i < result.vector_data.size(); ++i) { @@ -952,18 +1054,27 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap } /** - * @brief Handles the execution of a Neura floating-point comparison operation (neura.fcmp) by evaluating a specified comparison between two operands. + * @brief Handles the execution of a Neura floating-point comparison operation + * (neura.fcmp) by evaluating a specified comparison between two operands. * - * This function processes Neura's floating-point comparison operations, which take 2-3 operands: two floating-point inputs (LHS and RHS) - * and an optional execution predicate. It evaluates the comparison based on the specified type (e.g., "eq" for equality, "lt" for less than), - * combines the predicates of all operands (including the optional predicate if present), and stores the result as a boolean scalar (1.0f for true, 0.0f for false) - * in the value map. Errors are returned for insufficient operands or unsupported comparison types. + * This function processes Neura's floating-point comparison operations, which take + * 2-3 operands: two floating-point inputs (LHS and RHS) and an optional execution + * predicate. It evaluates the comparison based on the specified type (e.g., "eq" for + * equality, "lt" for less than), combines the predicates of all operands (including + * the optional predicate if present), and stores the result as a boolean scalar + * (1.0f for true, 0.0f for false) in the value map. Errors are returned for + * insufficient operands or unsupported comparison types. * * @param op The neura.fcmp operation to handle - * @param value_to_predicated_data_map Reference to the map where the comparison result will be stored, keyed by the operation's result value - * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + * @param value_to_predicated_data_map Reference to the map where the comparison + * result will be stored, keyed by the operation's + * result value + * @return bool True if the comparison is successfully + * evaluated; false if there are insufficient + * operands or an unsupported comparison type */ -bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleFCmpOp(neura::FCmpOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fcmp:\n"; } @@ -998,7 +1109,7 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value bool fcmp_result = false; StringRef cmp_type = op.getCmpType(); - // Evaluate the comparison based on the specified type + // Evaluates the comparison based on the specified type. if (cmp_type == "eq") { fcmp_result = (lhs.value == rhs.value); } else if (cmp_type == "ne") { @@ -1040,19 +1151,31 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura integer comparison operation (neura.icmp) by evaluating signed/unsigned comparisons between integer operands. - * - * This function processes Neura's integer comparison operations, which take 2-3 operands: two integer inputs (LHS and RHS, stored as floats) - * and an optional execution predicate. It converts the floating-point stored values to integers, evaluates the comparison based on the specified - * type (e.g., "eq" for equality, "slt" for signed less than, "ult" for unsigned less than), combines the predicates of all operands, and stores - * the result as a boolean scalar (1.0f for true, 0.0f for false) in the value map. Errors are returned for insufficient operands or unsupported - * comparison types. + * @brief Handles the execution of a Neura integer comparison operation + * (neura.icmp) by evaluating signed or unsigned comparisons between + * integer operands. + * + * This function processes Neura's integer comparison operations, which take + * 2-3 operands: two integer inputs (LHS and RHS, stored as floats) and an + * optional execution predicate. It converts the floating-point stored values + * to integers, evaluates the comparison based on the specified type (e.g., + * "eq" for equality, "slt" for signed less than, "ult" for unsigned less + * than), combines the predicates of all operands (including the optional + * predicate if present), and stores the result as a boolean scalar (1.0f for + * true, 0.0f for false) in the value map. Errors are returned for insufficient + * operands or unsupported comparison types. * * @param op The neura.icmp operation to handle - * @param value_to_predicated_data_map Reference to the map where the comparison result will be stored, keyed by the operation's result value - * @return bool True if the comparison is successfully evaluated; false if there are insufficient operands or an unsupported comparison type + * @param value_to_predicated_data_map Reference to the map where the + * comparison result will be stored, + * keyed by the operation's result value + * @return bool True if the comparison is successfully + * evaluated; false if there are + * insufficient operands or an unsupported + * comparison type */ -bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleICmpOp(neura::ICmpOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.icmp:\n"; } @@ -1084,7 +1207,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value << ", [pred = " << pred_data.predicate << "]\n"; } } - // Convert stored floating-point values to signed integers (rounded to nearest integer) + // Converts stored floating-point values to signed integers (rounded to nearest integer). int64_t s_lhs = static_cast(std::round(lhs.value)); int64_t s_rhs = static_cast(std::round(rhs.value)); @@ -1093,7 +1216,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value static_cast(val) : static_cast(UINT64_MAX + val + 1); }; - // Convert signed integers to unsigned for unsigned comparisons + // Converts signed integers to unsigned for unsigned comparisons. uint64_t u_lhs = signed_to_unsigned(s_lhs); uint64_t u_rhs = signed_to_unsigned(s_rhs); @@ -1108,7 +1231,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value bool icmp_result = false; StringRef cmp_type = op.getCmpType(); - // Evaluate the comparison based on the specified type (signed, unsigned, or equality) + // Evaluates the comparison based on the specified type (signed, unsigned, or equality). if (cmp_type == "eq") { icmp_result = (s_lhs == s_rhs); } else if (cmp_type == "ne") { @@ -1125,7 +1248,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value return false; } } - // Handle unsigned comparisons + // Handles unsigned comparisons. else if (cmp_type.starts_with("u")) { if (cmp_type == "ult") icmp_result = (u_lhs < u_rhs); else if (cmp_type == "ule") icmp_result = (u_lhs <= u_rhs); @@ -1163,18 +1286,28 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura logical OR operation (neura.or) for scalar boolean values. - * - * This function processes Neura's logical OR operations, which compute the logical OR of two scalar boolean operands. - * Logical OR returns true if at least one of the operands is true (non-zero). It supports an optional third operand - * as a predicate to further validate the result. The operation requires scalar operands (no vectors) and returns - * a scalar result with a combined validity predicate. - * - * @param op The neura.or operation to handle (modified for logical OR) - * @param value_to_predicated_data_map Reference to the map storing operands and where the result will be stored - * @return bool True if the logical OR is successfully executed; false for invalid operands (e.g., vectors, insufficient count) + * @brief Handles the execution of a Neura logical OR operation (neura.or) + * for scalar boolean values. + * + * This function processes Neura's logical OR operations, which take 2-3 + * operands: two scalar boolean inputs (LHS and RHS, represented as floats) + * and an optional execution predicate. It computes the logical OR of the + * two operands (true if at least one is non-zero), combines the predicates + * of all operands (including the optional predicate if present), and stores + * the result as a boolean scalar (1.0f for true, 0.0f for false) in the + * value map. Errors are returned for insufficient operands or invalid + * operand types (e.g., vectors). + * + * @param op The neura.or operation to handle + * @param value_to_predicated_data_map Reference to the map where the + * logical OR result will be stored, + * keyed by the operation's result value + * @return bool True if the logical OR is successfully + * executed; false if there are invalid + * operands or insufficient inputs */ -bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleOrOp(neura::OrOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.or (logical OR):\n"; } @@ -1186,7 +1319,7 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_to_ return false; } - // Retrieve left and right operands + // Retrieves left and right operands. auto lhs = value_to_predicated_data_map[op.getOperand(0)]; auto rhs = value_to_predicated_data_map[op.getOperand(1)]; @@ -1205,13 +1338,13 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_to_ << " (boolean: " << (rhs.value != 0.0f ? "true" : "false") << "), [pred = " << rhs.predicate << "]\n"; } - // Convert operands to boolean (non-zero = true) + // Converts operands to boolean (non-zero = true). bool lhs_bool = (lhs.value != 0.0f); bool rhs_bool = (rhs.value != 0.0f); - // Logical OR result: true if either operand is true + // Logical OR result: true if either operand is true. bool result_bool = lhs_bool || rhs_bool; - // Compute final validity predicate (combines operand predicates and optional predicate) + // Computes final validity predicate (combines operand predicates and optional predicate). bool final_predicate = lhs.predicate && rhs.predicate; if (op.getNumOperands() > 2) { auto pred = value_to_predicated_data_map[op.getOperand(2)]; @@ -1244,18 +1377,25 @@ bool handleOrOp(neura::OrOp op, llvm::DenseMap &value_to_ } /** - * @brief Handles the execution of a Neura logical NOT operation (neura.not) by computing the inverse of a boolean input. + * @brief Handles the execution of a Neura logical NOT operation (neura.not) + * by computing the inverse of a boolean input. * - * This function processes Neura's logical NOT operations, which take a single boolean operand (represented as a floating-point value). - * It converts the input value to an integer (rounded to the nearest whole number), applies the logical NOT operation (inverting true/false), - * and stores the result as a floating-point value (1.0f for true, 0.0f for false) in the value map. The result's predicate is inherited - * from the input operand's predicate. + * This function processes Neura's logical NOT operations, which take a single + * boolean operand (represented as a floating-point value). It converts the + * input value to an integer (rounded to the nearest whole number), applies + * the logical NOT operation (inverting true/false), and stores the result as + * a floating-point value (1.0f for true, 0.0f for false) in the value map. + * The result's predicate is inherited from the input operand's predicate. * * @param op The neura.not operation to handle - * @param value_to_predicated_data_map Reference to the map where the result will be stored, keyed by the operation's result value - * @return bool Always returns true as the operation is guaranteed to execute successfully with valid input + * @param value_to_predicated_data_map Reference to the map where the result + * will be stored, keyed by the operation's + * result value + * @return bool Always returns true since the operation + * executes successfully with valid input */ -bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleNotOp(neura::NotOp op, + llvm::DenseMap &value_to_predicated_data_map) { auto input = value_to_predicated_data_map[op.getOperand()]; if (isVerboseMode()) { @@ -1265,9 +1405,9 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_t << ", [pred = " << input.predicate << "]\n"; } - // Convert the input floating-point value to an integer (rounded to nearest whole number) + // Converts the input floating-point value to an integer (rounded to nearest whole number). int64_t inputInt = static_cast(std::round(input.value)); - // Apply logical NOT: 0 (false) becomes 1 (true), non-zero (true) becomes 0 (false) + // Applies logical NOT: 0 (false) becomes 1 (true), non-zero (true) becomes 0 (false). int64_t result_int = !inputInt; if (isVerboseMode()) { @@ -1291,18 +1431,28 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &value_t } /** - * @brief Handles the execution of a Neura selection operation (neura.sel) by choosing between two values based on a condition. - * - * This function processes Neura's selection operations, which take exactly 3 operands: a condition, a value to use if the condition is true, - * and a value to use if the condition is false. It evaluates the condition (treating non-zero values with a true predicate as true), - * selects the corresponding value (either "if_true" or "if_false"), and combines the predicate of the condition with the predicate of the selected value. - * The result is marked as a vector only if both input values are vectors. Errors are returned if the operand count is not exactly 3. + * @brief Handles the execution of a Neura selection operation (neura.sel) + * by choosing between two values based on a condition. + * + * This function processes Neura's selection operations, which take exactly + * three operands: a condition, a value to use if the condition is true, and + * a value to use if the condition is false. It evaluates the condition + * (non-zero values with a true predicate are treated as true), selects the + * corresponding value ("if_true" or "if_false"), and combines the predicate + * of the condition with the predicate of the chosen value. The result is + * marked as a vector only if both input values are vectors. Errors are + * returned if the operand count is not exactly three. * * @param op The neura.sel operation to handle - * @param value_to_predicated_data_map Reference to the map where the selected result will be stored, keyed by the operation's result value - * @return bool True if the selection is successfully computed; false if the operand count is invalid + * @param value_to_predicated_data_map Reference to the map where the + * selection result will be stored, + * keyed by the operation's result value + * @return bool True if the selection is successfully + * computed; false if the operand count + * is invalid */ -bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleSelOp(neura::SelOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sel:\n"; } @@ -1317,7 +1467,7 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_t auto cond = value_to_predicated_data_map[op.getCond()]; /* Condition to evaluate */ auto if_true = value_to_predicated_data_map[op.getIfTrue()]; /* Value if condition is true */ auto if_false = value_to_predicated_data_map[op.getIfFalse()]; /* Value if condition is false */ - // Evaluate the condition: true if the value is non-zero and its predicate is true + // Evaluates the condition: true if the value is non-zero and its predicate is true. bool cond_value = (cond.value != 0.0f) && cond.predicate; if (isVerboseMode()) { @@ -1332,7 +1482,7 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_t } PredicatedData result; - // Prepare the result by selecting the appropriate value based on the condition + // Prepares the result by selecting the appropriate value based on the condition. if (cond_value) { result.value = if_true.value; result.predicate = if_true.predicate && cond.predicate; @@ -1359,18 +1509,29 @@ bool handleSelOp(neura::SelOp op, llvm::DenseMap &value_t } /** - * @brief Handles the execution of a Neura type conversion operation (neura.cast) by converting an input value between supported types. - * - * This function processes Neura's type conversion operations, which take 1-2 operands: an input value to convert, and an optional predicate operand. - * It supports multiple conversion types (e.g., float to integer, integer to boolean) and validates that the input type matches the conversion requirements. - * The result's predicate is combined with the optional predicate operand (if present), and the result inherits the input's vector flag. Errors are returned for - * invalid operand counts, unsupported conversion types, or mismatched input types for the specified conversion. + * @brief Handles the execution of a Neura type conversion operation + * (neura.cast) by converting an input value between supported types. + * + * This function processes Neura's type conversion operations, which take + * one or two operands: an input value to convert, and an optional predicate + * operand. It supports multiple conversion types (e.g., float to integer, + * integer to boolean) and validates that the input type matches the + * conversion requirements. The result's predicate is combined with the + * optional predicate (if present), and the result inherits the input's + * vector flag. Errors are returned for invalid operand counts, unsupported + * conversion types, or mismatched input types. * * @param op The neura.cast operation to handle - * @param value_to_predicated_data_map Reference to the map where the converted result will be stored, keyed by the operation's result value - * @return bool True if the conversion is successfully computed; false for invalid operands, unsupported types, or mismatched input types + * @param value_to_predicated_data_map Reference to the map where the + * converted result will be stored, + * keyed by the operation's result value + * @return bool True if the conversion is successfully + * computed; false if operands are invalid, + * the type is unsupported, or the input + * type does not match the conversion */ -bool handleCastOp(neura::CastOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleCastOp(neura::CastOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cast:\n"; } @@ -1406,7 +1567,7 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value float result_value = 0.0f; auto input_type = op.getOperand(0).getType(); - // Handle specific conversion types with input type validation + // Handles specific conversion types with input type validation. if (cast_type == "f2i") { if (!input_type.isF32()) { if (isVerboseMode()) { @@ -1484,26 +1645,36 @@ bool handleCastOp(neura::CastOp op, llvm::DenseMap &value } /** - * @brief Handles the execution of a Neura Get Element Pointer operation (neura.gep) by computing a memory address from a base address and indices. - * - * This function processes Neura's GEP operations, which calculate a target memory address by adding an offset to a base address. The offset is computed - * using multi-dimensional indices and corresponding strides (step sizes between elements in each dimension). The operation accepts 1 or more operands: - * a base address, optional indices (one per dimension), and an optional boolean predicate operand (last operand, if present). It requires a "strides" - * attribute specifying the stride for each dimension, which determines how much to multiply each index by when calculating the offset. - * - * Key behavior: - * - Validates operand count (minimum 1: base address) - * - Identifies optional predicate operand (last operand, if it's a 1-bit integer) - * - Uses "strides" attribute to determine step sizes for each index dimension - * - Computes total offset as the sum of (index * stride) for each dimension - * - Combines predicates from base address and optional predicate operand - * - Returns the final address (base + offset) with the combined predicate + * @brief Handles the execution of a Neura Get Element Pointer operation + * (neura.gep) by computing a memory address from a base address + * and one or more indices. + * + * This function processes Neura's GEP operations, which calculate a target + * memory address by adding an offset to a base address. The offset is + * computed using multi-dimensional indices and corresponding strides + * (step sizes between elements in each dimension). The operation takes + * one or more operands: a base address, optional indices (one per dimension), + * and an optional predicate operand (if present, it must be the last one). + * The "strides" attribute specifies the stride for each dimension, which + * determines how much to multiply each index by when calculating the offset. + * + * The operation validates operand counts, extracts and applies the optional + * predicate, computes the total offset as the sum of (index * stride) for + * each dimension, combines the predicates of the base address and the + * optional predicate, and produces the final address (base + offset) with + * the combined predicate. * * @param op The neura.gep operation to handle - * @param value_to_predicated_data_map Reference to the map storing predicated data for values (base address, indices, predicate) - * @return bool True if the address is successfully computed; false for invalid operands, missing strides, or mismatched indices/strides + * @param value_to_predicated_data_map Reference to the map storing + * predicated data for values (base + * address, indices, predicate) + * @return bool True if the address is successfully + * computed; false if operands are + * invalid, strides are missing, or + * indices do not match the strides */ -bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleGEPOp(neura::GEP op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.gep:\n"; } @@ -1543,7 +1714,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_ return false; } - // Convert strides attribute to a vector of size_t (scaling factors for indices) + // Converts strides attribute to a vector of size_t (scaling factors for indices). std::vector strides; for (auto s : strides_attr) { auto int_attr = mlir::dyn_cast(s); @@ -1564,7 +1735,7 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_ return false; } - // Calculate total offset by scaling each index with its stride and summing + // Calculates total offset by scaling each index with its stride and summing. size_t offset = 0; for (unsigned i = 0; i < index_count; ++i) { auto idx_val = value_to_predicated_data_map[op.getOperand(i + 1)]; @@ -1610,25 +1781,40 @@ bool handleGEPOp(neura::GEP op, llvm::DenseMap &value_to_ } /** - * @brief Handles the execution of a Neura unconditional branch operation (neura.br) by transferring control to a target block. - * - * This function processes Neura's unconditional branch operations, which unconditionally direct control flow to a specified target block. - * It validates the target block exists, ensures the number of branch arguments matches the target block's parameters, and copies argument - * values to the target's parameters. Finally, it updates the current and last visited blocks to reflect the control transfer. - * - * @param op The neura.br operation to handle - * @param value_to_predicated_data_map Reference to the map storing branch arguments and where target parameters will be updated - * @param current_block Reference to the current block; updated to the target block after branch - * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch - * @return bool True if the branch is successfully executed; false for invalid target block or argument/parameter mismatch + * @brief Handles the execution of a Neura unconditional branch operation + * (neura.br) by transferring control to a target block. + * + * This function processes Neura's unconditional branch operations, which + * always transfer control flow to a specified target block. It validates + * that the target block exists, checks that the number of branch arguments + * matches the target block's parameters, and copies the argument values to + * the target's parameters. Finally, it updates the current block and the + * last visited block to reflect the control transfer. + * + * @param op The neura.br operation to handle + * @param value_to_predicated_data_map Reference to the map storing branch + * arguments and where target parameters + * will be updated + * @param current_block Reference to the current block; + * updated to the target block after + * the branch + * @param last_visited_block Reference to the last visited block; + * updated to the previous current block + * after the branch + * @return bool True if the branch is successfully + * executed; false if the target block + * is invalid or argument/parameter + * counts mismatch */ -bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_predicated_data_map, - Block *¤t_block, Block *&last_visited_block) { +bool handleBrOp(neura::Br op, + llvm::DenseMap &value_to_predicated_data_map, + Block *¤t_block, + Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.br:\n"; } - // Get the target block of the unconditional branch + // Gets the target block of the unconditional branch. Block *dest_block = op.getDest(); if (!dest_block) { if (isVerboseMode()) { @@ -1637,7 +1823,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr return false; } - // Retrieve all successor blocks of the current block + // Retrieves all successor blocks of the current block. auto current_succs_range = current_block->getSuccessors(); std::vector succ_blocks(current_succs_range.begin(), current_succs_range.end()); @@ -1655,7 +1841,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; } - // Get branch arguments and target block parameters + // Gets branch arguments and target block parameters. const auto &args = op.getArgs(); const auto &dest_params = dest_block->getArguments(); @@ -1667,7 +1853,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr return false; } - // Copy argument values to target block parameters in the value map + // Copies argument values to target block parameters in the value map. for (size_t i = 0; i < args.size(); ++i) { Value dest_param = dest_params[i]; Value src_arg = args[i]; @@ -1680,7 +1866,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr return false; } - // Transfer argument value to target parameter + // Transfers argument value to target parameter. value_to_predicated_data_map[dest_param] = value_to_predicated_data_map[src_arg]; if (isVerboseMode() && i < dest_params.size() - 1) { llvm::outs() << "[neura-interpreter] │ ├─ Param[" << i << "]: value = " @@ -1691,7 +1877,7 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr } } - // Update control flow state: last visited = previous current block; current = target block + // Updates control flow state: last visited = previous current block; current = target block. last_visited_block = current_block; current_block = dest_block; if (isVerboseMode()) { @@ -1702,20 +1888,39 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &value_to_pr } /** - * @brief Handles the execution of a Neura conditional branch operation (neura.cond_br) by transferring control to one of two target blocks based on a boolean condition. - * - * This function processes Neura's conditional branch operations, which direct control flow to a "true" target block or "false" target block based on the value of a boolean condition. - * It validates the operation's operands (1 mandatory condition + 1 optional predicate), checks that the condition is a boolean (i1 type), and computes a final predicate to determine if the branch is valid. - * If valid, it selects the target block based on the condition's value, passes arguments to the target block's parameters, and updates the current and last visited blocks to reflect the control transfer. + * @brief Handles the execution of a Neura conditional branch operation + * (neura.cond_br) by transferring control to one of two target + * blocks based on a boolean condition. + * + * This function processes Neura's conditional branch operations, which + * direct control flow to either a "true" target block or a "false" target + * block depending on the value of a boolean condition. It validates the + * operands (one mandatory condition and one optional predicate), checks + * that the condition is a boolean (i1 type), and computes the final + * predicate to determine if the branch is valid. If valid, it selects the + * target block based on the condition’s value, copies the branch arguments + * to the target block’s parameters, and updates the current block and last + * visited block to reflect the control transfer. * * @param op The neura.cond_br operation to handle - * @param value_to_predicated_data_map Reference to the map storing values (including the condition and optional predicate) - * @param current_block Reference to the current block; updated to the target block after branch - * @param last_visited_block Reference to the last visited block; updated to the previous current block after branch - * @return bool True if the branch is successfully executed; false for invalid operands, missing values, type mismatches, or invalid predicates + * @param value_to_predicated_data_map Reference to the map storing values + * (including the condition and optional + * predicate) + * @param current_block Reference to the current block; + * updated to the target block after + * the branch + * @param last_visited_block Reference to the last visited block; + * updated to the previous current block + * after the branch + * @return bool True if the branch is successfully + * executed; false if operands are invalid, + * values are missing, types mismatch, + * or the predicate is invalid */ -bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &value_to_predicated_data_map, - Block *¤t_block, Block *&last_visited_block) { +bool handleCondBrOp(neura::CondBr op, + llvm::DenseMap &value_to_predicated_data_map, + Block *¤t_block, + Block *&last_visited_block) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cond_br:\n"; } @@ -1749,7 +1954,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val << ", [pred = " << cond_data.predicate << "]\n"; } - // Compute final predicate (combines condition's predicate and optional predicate operand) + // Computes final predicate (combines condition's predicate and optional predicate operand). bool final_predicate = cond_data.predicate; if (op.getNumOperands() > 1) { auto pred_data = value_to_predicated_data_map[op.getPredicate()]; @@ -1761,7 +1966,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } } - // Retrieve successor blocks (targets of the conditional branch) + // Retrieves successor blocks (targets of the conditional branch). auto current_succs_range = current_block->getSuccessors(); std::vector succ_blocks(current_succs_range.begin(), current_succs_range.end()); @@ -1782,7 +1987,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val return false; } - // Determine target block based on condition value (non-zero = true branch) + // Determines target block based on condition value (non-zero = true branch). bool is_true_branch = (cond_data.value != 0.0f); Block *target_block = is_true_branch ? op.getTrueDest() : op.getFalseDest(); const auto &branch_args = is_true_branch ? op.getTrueArgs() : op.getFalseArgs(); @@ -1804,7 +2009,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val return false; } - // Pass branch arguments to target block parameters (update value_to_predicated_data_map) + // Passes branch arguments to target block parameters (update value_to_predicated_data_map). if (!branch_args.empty()) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Pass Arguments\n"; @@ -1826,7 +2031,7 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } } - // Update control flow state: last visited block = previous current block; current block = target + // Updates control flow state: last visited block = previous current block; current block = target. last_visited_block = current_block; current_block = target_block; @@ -1839,19 +2044,33 @@ bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &val } /** - * @brief Unified handler for Neura phi operations, supporting both control flow and data flow modes. - * - * In ControlFlow mode: Selects the input based on the most recently visited predecessor block, - * requiring validation of block relationships and input-predecessor count matching. - * - * In DataFlow mode: Merges inputs by selecting the first valid (true predicate) input. Falls back to - * the first input with a false predicate if no valid inputs exist. Tracks result updates for propagation. - * - * @param op The neura.phi operation to process - * @param value_to_predicated_data_map Reference to the map storing input values and where the result will be stored - * @param current_block [ControlFlow only] Block containing the phi operation (nullptr for DataFlow) - * @param last_visited_block [ControlFlow only] Most recently visited predecessor block (nullptr for DataFlow) - * @return bool True if processing succeeds; false for validation failures in ControlFlow mode (always true for DataFlow) + * @brief Handles the execution of a Neura phi operation (neura.phi), + * supporting both control flow and data flow modes. + * + * In control flow mode, the phi operation selects its result based on the + * most recently visited predecessor block. It validates the block + * relationships and ensures that the number of incoming values matches + * the number of predecessors. + * + * In data flow mode, the phi operation merges inputs by selecting the first + * input with a true predicate. If no valid input exists, it falls back to + * the first input with a false predicate. The result is tracked to enable + * propagation through the data flow network. + * + * @param op The neura.phi operation to handle + * @param value_to_predicated_data_map Reference to the map storing input + * values and where the result will be + * stored + * @param current_block [ControlFlow only] The block + * containing the phi operation + * (nullptr in DataFlow mode) + * @param last_visited_block [ControlFlow only] The most recently + * visited predecessor block (nullptr + * in DataFlow mode) + * @return bool True if the phi is successfully + * executed; false if validation fails + * in control flow mode (always true in + * data flow mode) */ bool handlePhiOp(neura::PhiOp op, llvm::DenseMap &value_to_predicated_data_map, @@ -1871,18 +2090,18 @@ bool handlePhiOp(neura::PhiOp op, if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; } - return false; // No inputs is a failure in both modes + return false; // No inputs is a failure in both modes. } - // Store the finally selected input data + // Stores the finally selected input data. PredicatedData selected_input_data; bool selection_success = false; // -------------------------- - // Mode-specific logic: Input selection and validation + // Mode-specific logic: Input selection and validation. // -------------------------- if (!isDataflowMode()) { - // ControlFlow mode: Validate required block parameters + // ControlFlow mode: Validate required block parameters. if (!current_block || !last_visited_block) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ ControlFlow mode requires current_block and last_visited_block\n"; @@ -1890,7 +2109,7 @@ bool handlePhiOp(neura::PhiOp op, return false; } - // ControlFlow mode: Get predecessors and validate count + // ControlFlow mode: Get predecessors and validate count. auto predecessors_range = current_block->getPredecessors(); std::vector predecessors(predecessors_range.begin(), predecessors_range.end()); size_t pred_count = predecessors.size(); @@ -1901,7 +2120,7 @@ bool handlePhiOp(neura::PhiOp op, return false; } - // ControlFlow mode: Validate input count matches predecessor count + // ControlFlow mode: Validate input count matches predecessor count. if (input_count != pred_count) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.phi: Input count (" << input_count @@ -1910,7 +2129,7 @@ bool handlePhiOp(neura::PhiOp op, return false; } - // ControlFlow mode: Find index of last visited block among predecessors + // ControlFlow mode: Finds index of last visited block among predecessors. size_t pred_index = 0; bool found = false; for (auto pred : predecessors) { @@ -1927,7 +2146,7 @@ bool handlePhiOp(neura::PhiOp op, return false; } - // ControlFlow mode: Validate index and retrieve selected input + // ControlFlow mode: Validates index and retrieves selected input. if (pred_index >= input_count) { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << pred_index << ")\n"; @@ -1944,7 +2163,7 @@ bool handlePhiOp(neura::PhiOp op, selected_input_data = value_to_predicated_data_map[selected_input]; selection_success = true; - // ControlFlow mode: Log predecessor details + // ControlFlow mode: Log predecessor details. if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Predecessor blocks (" << pred_count << ")\n"; for (size_t i = 0; i < pred_count; ++i) { @@ -1962,7 +2181,7 @@ bool handlePhiOp(neura::PhiOp op, } } else { // DataFlow mode - // DataFlow mode: Select first valid input (with true predicate) + // DataFlow mode: Selects first valid input (with true predicate). bool found_valid_input = false; for (size_t i = 0; i < input_count; ++i) { Value input = inputs[i]; @@ -1983,7 +2202,7 @@ bool handlePhiOp(neura::PhiOp op, } } - // DataFlow mode: Fallback to first input with false predicate if no valid inputs + // DataFlow mode: Falls back to the first input with a false predicate if there are no valid inputs. if (!found_valid_input) { Value first_input = inputs[0]; if (value_to_predicated_data_map.count(first_input)) { @@ -1996,16 +2215,16 @@ bool handlePhiOp(neura::PhiOp op, llvm::outs() << "[neura-interpreter] ├─ No valid input, using first input with pred=false\n"; } } else { - // Edge case: First input is undefined, initialize default values + // Edge case: First input is undefined; initializes default values. selected_input_data.value = 0.0f; selected_input_data.predicate = false; selected_input_data.is_vector = false; selected_input_data.vector_data = {}; } } - selection_success = true; // DataFlow mode always considers selection successful + selection_success = true; // DataFlow mode always considers selection successful. - // DataFlow mode: Log input values + // DataFlow mode: Logs input values. if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Input values (" << input_count << ")\n"; for (size_t i = 0; i < input_count; ++i) { @@ -2024,7 +2243,7 @@ bool handlePhiOp(neura::PhiOp op, } } - // Check for changes + // Checks for changes. bool is_updated = false; if (value_to_predicated_data_map.count(op.getResult())) { auto old_result = value_to_predicated_data_map[op.getResult()]; @@ -2055,26 +2274,32 @@ bool handlePhiOp(neura::PhiOp op, } /** - * @brief Handles the execution of a Neura reservation operation (neura.reserve) by creating a placeholder value. + * @brief Handles the execution of a Neura reservation operation (neura.reserve) + * by creating a placeholder value for future use. * - * This function processes Neura's reserve operations, which create a placeholder value with predefined initial properties. - * The placeholder is initialized with a value of 0.0f, a predicate of false (initially invalid), and is marked as a reserved - * value via the is_reserve flag. This operation is typically used to allocate or reserve a value slot for future use, - * where the actual value and validity will be set later. + * The reserve operation allocates a placeholder value with predefined initial + * properties: a numeric value of 0.0f, a predicate set to false (initially + * invalid), and the is_reserve flag set to indicate that this value is reserved. + * It is typically used to allocate a value slot that will be updated later with + * actual data during execution. * * @param op The neura.reserve operation to handle - * @param value_to_predicated_data_map Reference to the map where the reserved placeholder will be stored, keyed by the operation's result value - * @return bool Always returns true as the reservation operation is guaranteed to succeed + * @param value_to_predicated_data_map Reference to the map where the reserved + * placeholder will be stored, keyed by + * the operation’s result value + * @return bool Always returns true, as reservation is + * guaranteed to succeed */ -bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleReserveOp(neura::ReserveOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.reserve:\n"; } PredicatedData placeholder; - placeholder.value = 0.0f; /* Initial value set to 0.0f */ - placeholder.predicate = false; /* Initially marked as invalid (predicate false) */ - placeholder.is_reserve = true; /* Flag to indicate this is a reserved placeholder */ + placeholder.value = 0.0f; /* Initial value sets to 0.0f. */ + placeholder.predicate = false; /* Initially marked as invalid (predicate false). */ + placeholder.is_reserve = true; /* Flag to indicate this is a reserved placeholder. */ Value result = op.getResult(); value_to_predicated_data_map[result] = placeholder; @@ -2090,21 +2315,32 @@ bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap } /** - * @brief Unified handler for Neura control move operations (neura.ctrl_mov) supporting both control-flow and data-flow modes. - * - * This function processes neura.ctrl_mov operations by copying data from a source value to a reserved target placeholder, - * with behavior adjusted via the CtrlMovMode parameter: - * - In ControlFlow mode: Unconditionally copies source data to the target after validating type matching, ensuring strict - * consistency between source and target types. Fails on validation errors (e.g., missing source/target, type mismatch). - * - In DataFlow mode: Conditionally copies data only if the source's predicate is valid (true). Tracks updates via the - * `is_updated` flag (computed by comparing with the target's previous state) to support propagation of changes to dependent operations. - * - * Both modes validate that the source exists in the value map and the target is a reserved placeholder (created via neura.reserve). - * Vector/scalar type consistency is preserved during data copying, with vector data copied only if the source is a vector. + * @brief Handles the execution of a Neura control move operation + * (neura.ctrl_mov), supporting both control flow and data flow modes. + * + * This function processes neura.ctrl_mov operations by copying data from a + * source value to a reserved target placeholder, with behavior determined + * by the CtrlMovMode parameter: + * - In control flow mode, the source is unconditionally copied to the target + * after validating type matching, ensuring strict consistency. The operation + * fails on critical validation errors (e.g., missing source/target, type mismatch). + * - In data flow mode, the copy occurs only if the source predicate is true. + * Updates are tracked via the `is_updated` flag, computed by comparing the + * new value with the target’s previous state, to propagate changes to + * dependent operations. + * + * Both modes validate that the source exists in the value map and that the + * target is a reserved placeholder (created via neura.reserve). Vector and + * scalar type consistency is preserved, with vector data copied only if + * the source is a vector. * * @param op The neura.ctrl_mov operation to handle - * @param value_to_predicated_data_map Reference to the map storing source/target values and their metadata (value, predicate, type flags) - * @return bool True if processing succeeds; false only for critical errors (e.g., invalid target) in ControlFlow mode + * @param value_to_predicated_data_map Reference to the map storing source + * and target values along with metadata + * (value, predicate, type flags) + * @return bool True if the operation succeeds; false + * only for critical errors (e.g., invalid + * target) in control flow mode */ bool handleCtrlMovOp(neura::CtrlMovOp op, llvm::DenseMap& value_to_predicated_data_map) { @@ -2170,29 +2406,38 @@ bool handleCtrlMovOp(neura::CtrlMovOp op, } /** - * @brief Handles the execution of a Neura return operation (neura.return) by processing and outputting return values. + * @brief Handles the execution of a Neura return operation (neura.return) + * by processing and outputting return values. * - * This function processes Neura's return operations, which return zero or more values from a function. It retrieves each return value from the value map, - * validates their existence, and prints them in a human-readable format (scalar or vector) in verbose mode. For vector values, it formats elements as a - * comma-separated list, using 0.0f for elements with an invalid predicate. If no return values are present, it indicates a void return. + * This function processes Neura's return operations, which return zero or + * more values from a function. It retrieves each return value from the + * value map, validates its existence, and prints it in a human-readable + * format in verbose mode. Scalar values are printed directly, while vector + * values are formatted as a comma-separated list, with elements having an + * invalid predicate displayed as 0.0f. If no return values are present, + * the operation indicates a void return. * * @param op The neura.return operation to handle - * @param value_to_predicated_data_map Reference to the map storing the return values to be processed - * @return bool True if return values are successfully processed; false if any return value is missing from the value map + * @param value_to_predicated_data_map Reference to the map storing the + * return values to be processed + * @return bool True if all return values are successfully + * processed; false if any value is missing + * from the value map */ -bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleNeuraReturnOp(neura::ReturnOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.return:\n"; } auto return_values = op.getValues(); - // Handle void return (no values) + // Handles void return (no values). if (return_values.empty()) { llvm::outs() << "[neura-interpreter] → Output: (void)\n"; return true; } - // Collect and validate return values from the value map + // Collects and validates return values from the value map. std::vector results; for (Value val : return_values) { if (!value_to_predicated_data_map.count(val)) { @@ -2206,7 +2451,7 @@ bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleGrantPredicateOp(neura::GrantPredicateOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_predicate:\n"; } @@ -2269,7 +2525,7 @@ bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleGrantOnceOp(neura::GrantOnceOp op, + llvm::DenseMap &value_to_predicated_data_map) { if(isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_once:\n"; } - // Check if either a value operand or constant attribute is provided + // Checks if either a value operand or constant attribute is provided. bool has_value = op.getValue() != nullptr; bool has_constant = op.getConstantValue().has_value(); @@ -2331,7 +2596,7 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap(constant_attr)) { source.value = int_attr.getInt(); @@ -2353,7 +2618,7 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap granted; bool has_granted = granted[op.getOperation()]; bool result_predicate = !has_granted; @@ -2383,16 +2648,25 @@ bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &value_to_predicated_data_map) { +bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_always:\n"; } @@ -2405,7 +2679,7 @@ bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap findIndependentInSequence( const std::vector &op_sequence) { std::vector independent_ops; - // Track all values defined by operations within the sequence for quick lookup + // Tracks all values defined by operations within the sequence for quick lookup. llvm::DenseSet values_defined_in_sequence; for (Operation *op : op_sequence) { @@ -2525,10 +2799,10 @@ OperationHandleResult handleOperation( } else if (auto gep_op = dyn_cast(op)) { result.success = handleGEPOp(gep_op, value_to_predicated_data_map); } else if (auto br_op = dyn_cast(op)) { - // Branch operations only need block handling in control flow mode + // Branch operations only need block handling in control flow mode. if (current_block && last_visited_block) { result.success = handleBrOp(br_op, value_to_predicated_data_map, *current_block, *last_visited_block); - result.is_branch = true; // Mark as branch to reset index + result.is_branch = true; // Marks as branch to reset index. } else { result.success = false; } @@ -2540,7 +2814,7 @@ OperationHandleResult handleOperation( result.success = false; } } else if (auto phi_op = dyn_cast(op)) { - // Phi operations need block information in control flow mode, but not in data flow + // Phi operations need block information in control flow mode, but not in data flow. if (current_block && last_visited_block) { result.success = handlePhiOp(phi_op, value_to_predicated_data_map, *current_block, *last_visited_block); } else { @@ -2585,7 +2859,7 @@ bool executeOperation(Operation* op, llvm::DenseMap& value_to_predicated_data_map, llvm::SmallVector& next_pending_operation_queue) { - // Save current values to detect updates after execution + // Saves current values to detect updates after execution. llvm::DenseMap old_values; for (Value result : op->getResults()) { if (value_to_predicated_data_map.count(result)) { @@ -2593,7 +2867,7 @@ bool executeOperation(Operation* op, } } - // Process the operation using the generic handler (no block info needed for data flow) + // Processes the operation using the generic handler (no block info needed for data flow). auto handle_result = handleOperation(op, value_to_predicated_data_map); if (!handle_result.success) { if (isVerboseMode()) { @@ -2602,13 +2876,13 @@ bool executeOperation(Operation* op, return false; } - // Check if any results were updated during execution + // Checks if any results were updated during execution. bool has_update = false; for (Value result : op->getResults()) { if (!value_to_predicated_data_map.count(result)) continue; PredicatedData new_data = value_to_predicated_data_map[result]; - // New results (not in old_values) are considered updates + // New results (not in old_values) are considered updates. if (!old_values.count(result)) { new_data.is_updated = true; value_to_predicated_data_map[result] = new_data; @@ -2616,7 +2890,7 @@ bool executeOperation(Operation* op, continue; } - // Compare new value with old value to detect changes + // Compares new value with old value to detect changes. PredicatedData old_data = old_values[result]; if (old_data.isUpdatedComparedTo(new_data)) { new_data.is_updated = true; @@ -2628,7 +2902,7 @@ bool executeOperation(Operation* op, } } - // Special case: check for updates in operations with no results (e.g., CtrlMovOp) + // Special case: checks for updates in operations with no results (e.g., CtrlMovOp). if (op->getNumResults() == 0) { if (auto ctrl_mov_op = dyn_cast(op)) { Value target = ctrl_mov_op.getTarget(); @@ -2642,13 +2916,13 @@ bool executeOperation(Operation* op, } } - // Propagate updates to dependent operations if changes occurred + // Propagates updates to dependent operations if changes occurred. if (has_update) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; } - // Collect all values affected by the update + // Collects all values affected by the update. llvm::SmallVector affected_values; for (Value result : op->getResults()) { if (value_to_predicated_data_map.count(result) && @@ -2658,7 +2932,7 @@ bool executeOperation(Operation* op, } } - // Include targets from operations without results (e.g., CtrlMovOp) + // Includes targets from operations without results (e.g., CtrlMovOp). if (op->getNumResults() == 0) { if (auto ctrl_mov_op = dyn_cast(op)) { Value source = ctrl_mov_op.getValue(); @@ -2674,7 +2948,7 @@ bool executeOperation(Operation* op, } } - // Add all users of affected values to the next pending operation queue (if not already present) + // Adds all users of affected values to the next pending operation queue (if not already present). for (Value val : affected_values) { for (Operation* user_op : val.getUsers()) { if (!is_operation_enqueued[user_op]) { @@ -2708,7 +2982,7 @@ int run(func::FuncOp func, llvm::DenseMap& value_to_predicated_data_map) { if (isDataflowMode()) { // Data flow mode execution logic - // Initialize pending operation queue with all operations except return operations + // Initializes pending operation queue with all operations except return operations. std::vector op_seq; for (auto& block : func.getBody()) { @@ -2717,7 +2991,7 @@ int run(func::FuncOp func, } } - // Dependency graph to track operation dependencies + // Tracks operation dependencies with dependency graph. DependencyGraph consumer_dependent_on_producer_graph; consumer_dependent_on_producer_graph.build(op_seq); std::vector independent_ops = findIndependentInSequence(op_seq); @@ -2732,7 +3006,7 @@ int run(func::FuncOp func, } int iter_count = 0; - // Main loop: process pending operation queue until empty + // Main loop: processes pending operation queue until empty. while (!pending_operation_queue.empty()) { iter_count++; if (isVerboseMode()) { @@ -2772,13 +3046,13 @@ int run(func::FuncOp func, size_t op_index = 0; bool is_terminated = false; - // Main loop: process operations sequentially through blocks + // Main loop: processes operations sequentially through blocks. while (!is_terminated && current_block) { auto& operations = current_block->getOperations(); if (op_index >= operations.size()) break; Operation& op = *std::next(operations.begin(), op_index); - // Process operation with block information for control flow handling + // Processes operation with block information for control flow handling. auto handle_result = handleOperation(&op, value_to_predicated_data_map, ¤t_block, &last_visited_block); if (!handle_result.success) return EXIT_FAILURE; @@ -2786,10 +3060,10 @@ int run(func::FuncOp func, is_terminated = true; op_index++; } else if (handle_result.is_branch) { - // Branch operations update current_block, reset index to start of new block + // Branch operations update current_block; reset index to start of new block. op_index = 0; } else { - // Regular operations increment to next operation in block + // Regular operations increment to next operation in block. op_index++; } } @@ -2799,7 +3073,7 @@ int run(func::FuncOp func, } int main(int argc, char **argv) { - // Parse command line arguments + // Parses command line arguments. for (int i = 0; i < argc; ++i) { if (std::string(argv[i]) == "--verbose") { setVerboseMode(true); @@ -2813,14 +3087,14 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - // Initialize MLIR context and dialects + // Initializes MLIR context and dialects. DialectRegistry registry; registry.insert(); MLIRContext context; context.appendDialectRegistry(registry); - // Load and parse input MLIR file + // Loads and parses input MLIR file. llvm::SourceMgr source_mgr; auto file_or_err = mlir::openInputFile(argv[1]); if (!file_or_err) { @@ -2836,7 +3110,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - // Initialize data structures + // Initializes data structures. llvm::DenseMap value_to_predicated_data_map; for (auto func : module->getOps()) {