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/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/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 deleted file mode 100644 index e60c8292..00000000 --- a/test/neura/interpreter/basic_operation/load_store.mlir +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: neura-interpreter %s | 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 b47c801b..00000000 --- a/test/neura/interpreter/basic_operation/load_store_index.mlir +++ /dev/null @@ -1,63 +0,0 @@ -// RUN: neura-interpreter %s | 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/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..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 | FileCheck %s +// 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/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_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/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index b4699ca1..96866621 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" @@ -14,240 +15,283 @@ #include "NeuraDialect/NeuraDialect.h" #include "NeuraDialect/NeuraOps.h" +#include #include #include #include -#include #include -#include #include +#include using namespace mlir; -static bool verbose = false; - -inline void setVerboseMode(bool v) { - verbose = v; -} +// 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). */ + + /** + * @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 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; +}; -inline bool isVerboseMode() { - return verbose; +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; } -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(); - } +/** + * @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). */ +}; - 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; - } - } - } +/** + * @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. + 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 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 build(const std::vector& op_sequence); + + /** + * @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. + */ + bool canExecute(Operation* op); + + /** + * @brief Updates the dependency graph after an operation has been executed. + * 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 (acts as a producer). + */ + void updateAfterExecution(Operation* executed_op); }; -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]; +void DependencyGraph::build(const std::vector& op_sequence) { + for (Operation* consumer_op : op_sequence) { + int required_producers = 0; + // Counts how many producer operations this consumer depends on. + for (Value operand : consumer_op->getOperands()) { + if (Operation* producer_op = operand.getDefiningOp()) { + // Counts only producers within the same operation sequence. + if (std::find(op_sequence.begin(), op_sequence.end(), producer_op) != op_sequence.end()) { + required_producers++; } - totalSize = stride; - baseAddr = memory.malloc(totalSize); + } } + consumer_pending_producers[consumer_op] = required_producers; + } +} - void set(const std::vector& indices, T value) { - size_t addr = calcAddr(indices); - memory.store(addr, value); - } +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). + return it != consumer_pending_producers.end() && it->second == 0; +} - T get(const std::vector& indices) const { - size_t addr = calcAddr(indices); - return memory.load(addr); +void DependencyGraph::updateAfterExecution(Operation* executed_op) { + // Marks the completed operation as executed (it acts as a producer). + executed_ops.insert(executed_op); + + // 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); + // Decrements pending producer count for valid consumers. + if (it != consumer_pending_producers.end() && it->second > 0) { + it->second--; + } } + } +} - void dumpRaw() const { - memory.dump(baseAddr, totalSize); - } +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. */ - void free() { - memory.free(baseAddr); - } +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. */ -private: - Memory& memory; - std::vector shape; - std::vector strides; - size_t totalSize; - size_t baseAddr; +inline void setDataflowMode(bool v) { + dataflow = v; +} - 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; - } -}; +inline bool isDataflowMode() { + return dataflow; +} -// Data structure to hold both value and predicate. -struct PredicatedData { - float value; - bool predicate; - bool isVector; - std::vector vectorData; - bool isReserve; -}; +inline void setVerboseMode(bool v) { + verbose = v; +} -bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& valueMap) { +inline bool isVerboseMode() { + return verbose; +} + +/** + * @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_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) { 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; + + // Handles 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; + } + // Handles 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 { + } + // Handles 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_to_predicated_data_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_to_predicated_data_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_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 or vector + * element type is unsupported + */ +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"; } - - if (auto floatAttr = llvm::dyn_cast(attr)) { + // Handles 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_to_predicated_data_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_to_predicated_data_map[op.getResult()] = val; + } + // Handles 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_to_predicated_data_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_to_predicated_data_map[op.getResult()] = val; + } + // Handles 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"; } @@ -255,26 +299,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_to_predicated_data_map.count(op.getResult()) == 0 && "Duplicate constant result?"); + value_to_predicated_data_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 { + } + // Handles unsupported constant types. + else { if (isVerboseMode()) { llvm::errs() << "[neura-interpreter] └─ Unsupported constant type in neura.constant\n"; } @@ -283,7 +329,24 @@ bool handleNeuraConstantOp(neura::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_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 handleAddOp(neura::AddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.add:\n"; } @@ -295,8 +358,8 @@ bool handleAddOp(neura::AddOp op, llvm::DenseMap &valueMa return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[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"; @@ -304,27 +367,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_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"; 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_to_predicated_data_map[op.getResult()] = result; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; @@ -333,7 +396,27 @@ 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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.sub:\n"; } @@ -345,8 +428,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_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"; @@ -354,35 +437,55 @@ 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_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"; 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd:\n"; } @@ -394,9 +497,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_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; + bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -405,8 +508,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_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"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; @@ -415,18 +518,38 @@ 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fsub:\n"; } @@ -438,9 +561,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_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_map[op.getRhs()]; + bool final_predicate = lhs.predicate && rhs.predicate; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operands \n"; @@ -449,8 +572,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_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"; llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << pred.value << " [pred = " << pred.predicate << "]\n"; @@ -459,18 +582,38 @@ 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul:\n"; } @@ -482,8 +625,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_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"; @@ -491,35 +634,56 @@ 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_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"; 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fdiv:\n"; } @@ -531,8 +695,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_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"; @@ -540,44 +704,66 @@ 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_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"; 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) { + // 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"; } } 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.vfmul:\n"; } @@ -589,17 +775,17 @@ bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &val return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[op.getRhs()]; + auto lhs = value_to_predicated_data_map[op.getLhs()]; + auto rhs = value_to_predicated_data_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]; @@ -611,32 +797,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_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"; } 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"; @@ -644,25 +830,45 @@ 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()); + // 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]; } 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_to_predicated_data_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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fadd_fadd:\n"; } @@ -674,9 +880,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_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"; - 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; + // Computes 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_to_predicated_data_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_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 fused multiply-add is + * successfully computed; false if there are + * fewer than 3 operands + */ +bool handleFMulFAddOp(neura::FMulFAddOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fmul_fadd:\n"; } @@ -729,9 +956,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_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"; - 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; + // Computes 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_to_predicated_data_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_to_predicated_data_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_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing func.return:\n"; } @@ -789,14 +1035,14 @@ bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap return true; } - auto result = valueMap[op.getOperand(0)]; - if (result.isVector) { - + auto result = value_to_predicated_data_map[op.getOperand(0)]; + // 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.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"; @@ -807,7 +1053,28 @@ 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_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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.fcmp:\n"; } @@ -818,8 +1085,8 @@ bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[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"; @@ -831,59 +1098,84 @@ 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_to_predicated_data_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(); + // Evaluates 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_to_predicated_data_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 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 + */ +bool handleICmpOp(neura::ICmpOp op, + llvm::DenseMap &value_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.icmp:\n"; } @@ -894,8 +1186,8 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value return false; } - auto lhs = valueMap[op.getLhs()]; - auto rhs = valueMap[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"; @@ -907,15 +1199,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_to_predicated_data_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"; } } - + // 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)); @@ -924,7 +1216,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value static_cast(val) : static_cast(UINT64_MAX + val + 1); }; - + // 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); @@ -939,7 +1231,7 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value bool icmp_result = false; StringRef cmp_type = op.getCmpType(); - + // 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") { @@ -955,7 +1247,9 @@ bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &value } return false; } - } else if (cmp_type.starts_with("u")) { + } + // 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); else if (cmp_type == "ugt") icmp_result = (u_lhs > u_rhs); @@ -973,98 +1267,136 @@ 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_to_predicated_data_map[op.getResult()] = result; return true; } -bool handleOrOp(neura::OrOp op, llvm::DenseMap &valueMap) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.or:\n"; +/** + * @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) { + 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)]; + // 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)]; - 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; + // 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. + bool result_bool = lhs_bool || rhs_bool; - bool finalPredicate = lhs.predicate && rhs.predicate; + // Computes 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_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"; + 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_to_predicated_data_map[op.getResult()] = result; return true; } -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()]; +/** + * @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_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) { + auto input = value_to_predicated_data_map[op.getOperand()]; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.not:\n"; @@ -1073,88 +1405,133 @@ bool handleNotOp(neura::NotOp op, llvm::DenseMap &valueMa << ", [pred = " << input.predicate << "]\n"; } + // Converts 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; + // Applies 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]] │ └─ Bitwise NOT : ~" << inputInt; - if (inputInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; - llvm::outs() << " = " << resultInt; - if (resultInt == -1) llvm::outs() << " (0xFFFFFFFFFFFFFFFF)"; + llvm::outs() << "[neura-interpreter] │ └─ Logical NOT : !" << inputInt; 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_to_predicated_data_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 + * 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 + * 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) { 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_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 */ + // 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()) { 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; + // 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; 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_to_predicated_data_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 + * 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 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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cast:\n"; } @@ -1165,8 +1542,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_to_predicated_data_map[op.getOperand(0)]; + std::string cast_type = op.getCastType().str(); if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ Operand\n"; @@ -1174,220 +1551,130 @@ 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_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"; - 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(); + // Handles 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; - - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue - << ", [pred = " << finalPredicate << "]\n"; - } - - valueMap[op.getResult()] = result; - return true; -} - -bool handleLoadOp(neura::LoadOp op, llvm::DenseMap &valueMap, 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; - } - - 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); - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; - llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predVal.value - << ", [pred = " << predVal.predicate << "]\n"; - } - } - - float val = 0.0f; - size_t addr = static_cast(addrVal.value); - - if (finalPredicate) { - auto resultType = op.getResult().getType(); - if (resultType.isF32()) { - val = mem.load(addr); - } else if (resultType.isInteger(32)) { - val = static_cast(mem.load(addr)); - } else if (resultType.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 - } + result.value = result_value; + result.predicate = final_predicate; + result.is_vector = input.is_vector; if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Load [addr = " << addr << "] => val = " - << val << ", [pred = " << finalPredicate << "]\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result_value + << ", [pred = " << final_predicate << "]\n"; } - valueMap[op.getResult()] = { val, finalPredicate }; + value_to_predicated_data_map[op.getResult()] = result; return true; } -bool handleStoreOp(neura::StoreOp op, llvm::DenseMap &valueMap, 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 valData = valueMap[op.getOperand(0)]; - auto addrVal = valueMap[op.getOperand(1)]; - bool finalPredicate = addrVal.predicate; - - if (op.getNumOperands() > 2) { - auto predVal = valueMap[op.getOperand(2)]; - finalPredicate = finalPredicate && predVal.predicate && (predVal.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)); - } else { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Unsupported store type\n"; - } - return false; - } - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Store [addr = " << addr - << "] => val = " << valData.value - << ", [pred = 1" << "]\n"; - } - } else { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Store skipped due to [pred = 0]\n"; - } - } - - 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 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 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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.gep:\n"; } @@ -1399,65 +1686,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_to_predicated_data_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; } + // Converts 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; } + // Calculates 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_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"; } 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] @@ -1465,235 +1754,111 @@ 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_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 = " << 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_to_predicated_data_map[op.getResult()] = result; return true; } -bool handleLoadIndexedOp(neura::LoadIndexedOp op, - llvm::DenseMap &valueMap, - Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Executing neura.load_indexed:\n"; - } - - auto baseVal = valueMap[op.getBase()]; - if (baseVal.isVector) { - 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 offset = 0.0f; - for (Value idx : op.getIndices()) { - auto idxVal = valueMap[idx]; - if (idxVal.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector index not supported in load_indexed\n"; - } - return false; - } - offset += idxVal.value; - finalPredicate = finalPredicate && idxVal.predicate; - } - - if (op.getPredicate()) { - Value predOperand = op.getPredicate(); - auto predVal = valueMap[predOperand]; - if (predVal.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; - } - return false; - } - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); - } - - size_t addr = static_cast(baseF + offset); - float val = 0.0f; - - if (finalPredicate) { - auto resultType = op.getResult().getType(); - if (resultType.isF32()) { - val = mem.load(addr); - } else if (resultType.isInteger(32)) { - val = static_cast(mem.load(addr)); - } else if (resultType.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 = " << finalPredicate << "]\n"; - } - - valueMap[op.getResult()] = { val, finalPredicate, false, {}, false }; - return true; -} - -bool handleStoreIndexedOp(neura::StoreIndexedOp op, - llvm::DenseMap &valueMap, - Memory &mem) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.store_indexed:\n"; - } - - auto valToStore = valueMap[op.getValue()]; - if (valToStore.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector value not supported in store_indexed\n"; - } - return false; - } - float value = valToStore.value; - bool finalPredicate = valToStore.predicate; - - auto baseVal = valueMap[op.getBase()]; - if (baseVal.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector base not supported in store_indexed\n"; - } - return false; - } - float baseF = baseVal.value; - finalPredicate = finalPredicate && baseVal.predicate; - - float offset = 0.0f; - for (Value idx : op.getIndices()) { - auto idxVal = valueMap[idx]; - if (idxVal.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector index not supported in store_indexed\n"; - } - return false; - } - offset += idxVal.value; - finalPredicate = finalPredicate && idxVal.predicate; - } - - if (op.getPredicate()) { - Value predOperand = op.getPredicate(); - auto predVal = valueMap[predOperand]; - if (predVal.isVector) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ Vector predicate not supported\n"; - } - return false; - } - finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); - } - - size_t addr = static_cast(baseF + offset); - - if (finalPredicate) { - auto valType = op.getValue().getType(); - if (valType.isF32()) { - mem.store(addr, value); - } else if (valType.isInteger(32)) { - mem.store(addr, static_cast(value)); - } else if (valType.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 = " << finalPredicate << "]\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 + * 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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.br:\n"; } - Block *destBlock = op.getDest(); - if (!destBlock) { + // Gets 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()); + // 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()); 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"; } - // auto parentFunc = destBlock->getParentOp(); - // unsigned blockIndex = 0; - // for (auto &block : parentFunc->getRegion(0)) { - // if (&block == destBlock) break; - // blockIndex++; - // } - + // Gets 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; } - + // Copies 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_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"; @@ -1701,27 +1866,61 @@ bool handleBrOp(neura::Br op, llvm::DenseMap &valueMap, return false; } - valueMap[destParam] = valueMap[srcArg]; - if (isVerboseMode() && i < destParams.size() - 1) { + // 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 = " - << valueMap[srcArg].value << "\n"; - } else if (isVerboseMode() && i == destParams.size() - 1) { + << value_to_predicated_data_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_to_predicated_data_map[src_arg].value << "\n"; } } - lastVisitedBlock = currentBlock; - currentBlock = destBlock; + // Updates 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 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 + * 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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.cond_br:\n"; } @@ -1733,14 +1932,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_to_predicated_data_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_to_predicated_data_map! (SSA name missing)\n"; } return false; } - auto condData = valueMap[op.getCondition()]; + auto cond_data = value_to_predicated_data_map[op.getCondition()]; if (!op.getCondition().getType().isInteger(1)) { if (isVerboseMode()) { @@ -1751,196 +1950,359 @@ 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; + // Computes 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_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"; - 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"; } } - // llvm::outs() << "[neura-interpreter] ├─ Current block: Block@" << currentBlock << "\n"; - - auto currentSuccsRange = currentBlock->getSuccessors(); - std::vector succBlocks(currentSuccsRange.begin(), currentSuccsRange.end()); + // 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()); 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(); - - // auto parentFunc = targetBlock->getParentOp(); - // unsigned blockIndex = 0; - // for (auto &block : parentFunc->getRegion(0)) { - // if (&block == targetBlock) break; - // blockIndex++; - // } + // 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(); + 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()) { + // 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"; } - 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_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 = " - << valueMap[branchArgs[i]].value << "\n"; - else { + << value_to_predicated_data_map[branch_args[i]].value << "\n"; + } + } else { + if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] │ └─ param[" << i << "]: value = " - << valueMap[branchArgs[i]].value << "\n"; + << value_to_predicated_data_map[branch_args[i]].value << "\n"; } } } - - - lastVisitedBlock = currentBlock; - currentBlock = targetBlock; + // Updates 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 handlePhiOp(neura::PhiOp op, llvm::DenseMap &valueMap, - Block *currentBlock, Block *lastVisitedBlock) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; +/** + * @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, + Block *current_block = nullptr, + Block *last_visited_block = nullptr) { + if (isVerboseMode()) { + if (!isDataflowMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.phi:\n"; + } else { + llvm::outs() << "[neura-interpreter] Executing neura.phi(dataflow):\n"; + } } - auto predecessorsRange = currentBlock->getPredecessors(); - std::vector predecessors(predecessorsRange.begin(), predecessorsRange.end()); - size_t predCount = predecessors.size(); - - if (predCount == 0) { + auto inputs = op.getInputs(); + size_t input_count = inputs.size(); + if (input_count == 0) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Current block has no predecessors\n"; + llvm::errs() << "[neura-interpreter] └─ Error: No inputs provided (execution failed)\n"; } - return false; + return false; // No inputs is a failure in both modes. } - size_t predIndex = 0; - bool found = false; - for (auto pred : predecessors) { - if (pred == lastVisitedBlock) { - found = true; - break; + // Stores 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; } - ++predIndex; - } - if (!found) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\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; - } - auto inputs = op.getInputs(); - size_t inputCount = inputs.size(); + // 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 (inputCount != predCount) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Input count (" << inputCount - << ") != predecessor count (" << predCount << ")\n"; + // ControlFlow mode: Finds 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; + } + ++pred_index; + } + if (!found) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\n"; + } + return false; } - return false; - } - if (predIndex >= inputCount) { - if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << predIndex << ")\n"; + // 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"; + } + return false; } - 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; - Value inputVal = inputs[predIndex]; - if (!valueMap.count(inputVal)) { + // ControlFlow mode: Log predecessor details. if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.phi: Input value not found in value map\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; - } - PredicatedData inputData = valueMap[inputVal]; - valueMap[op.getResult()] = inputData; + } else { // DataFlow mode + // 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]; + if (!value_to_predicated_data_map.count(input)) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Input [" << i << "] not found, skipping\n"; + } + continue; + } + 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; + } + } - 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] │ ├─ [" << i << "]: " << "Block@" << predecessors[i]; + // 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)) { + 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 { - llvm::outs() << "[neura-interpreter] │ └─ [" << i << "]: " << "Block@" << predecessors[i]; + // 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 = {}; } - if (i == predIndex) { - llvm::outs() << " (→ current path)\n"; - } else { - llvm::outs() << "\n"; + } + selection_success = true; // DataFlow mode always considers selection successful. + + // 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) { + 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"; + } } } - llvm::outs() << "[neura-interpreter] └─ Result : " << op.getResult() << "\n"; - llvm::outs() << "[neura-interpreter] └─ Value : " << inputData.value - << ", [Pred = " << inputData.predicate << "]\n"; } - return true; + + // 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()]; + is_updated = selected_input_data.isUpdatedComparedTo(old_result); + } else { + is_updated = true; + } + + PredicatedData result = selected_input_data; + result.is_updated = is_updated; + result.is_reserve = false; + value_to_predicated_data_map[op.getResult()] = result; + + if (isVerboseMode()) { + 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 selection_success; } -bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &valueMap) { +/** + * @brief Handles the execution of a Neura reservation operation (neura.reserve) + * by creating a placeholder value for future use. + * + * 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 reservation is + * guaranteed to succeed + */ +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; - placeholder.predicate = false; - placeholder.isReserve = true; + 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(); - valueMap[result] = placeholder; + value_to_predicated_data_map[result] = placeholder; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] └─ Created placeholder : " << result << "\n"; @@ -1949,131 +2311,192 @@ 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) { - if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov:\n"; +/** + * @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 + * 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) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.ctrl_mov" + << (isDataflowMode() ? "(dataflow)" : "") << ":\n"; } Value source = op.getValue(); Value target = op.getTarget(); - if (!valueMap.count(source)) { + if (!value_to_predicated_data_map.count(source)) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Source value not found in value map\n"; + llvm::errs() << "[neura-interpreter] └─ Error: Source value not found in value map\n"; } return false; } - if (!valueMap.count(target) || !valueMap[target].isReserve) { + if (!value_to_predicated_data_map.count(target) || !value_to_predicated_data_map[target].is_reserve) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Target is not a reserve placeholder\n"; + llvm::errs() << "[neura-interpreter] └─ Error: Target is not a reserve placeholder\n"; } return false; } - const auto &sourceData = valueMap[source]; - auto &targetData = valueMap[target]; + const auto& source_data = value_to_predicated_data_map[source]; + auto& target_data = value_to_predicated_data_map[target]; - if (source.getType() != target.getType()) { + if (!isDataflowMode() && source.getType() != target.getType()) { if (isVerboseMode()) { - llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Type mismatch (source =" - << source.getType() << ", target =" << target.getType() << ")\n"; + 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"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << sourceData.value - << ", [pred = " << sourceData.predicate << "]\n"; - llvm::outs() << "[neura-interpreter] ├─ Target: " << target << "\n"; - llvm::outs() << "[neura-interpreter] │ └─ value = " << targetData.value - << ", [pred = " << targetData.predicate << "]\n"; - } + const PredicatedData old_target_data = target_data; + const bool should_update = isDataflowMode() ? (source_data.predicate == 1) : true; - targetData.value = sourceData.value; - targetData.predicate = sourceData.predicate; - targetData.isVector = sourceData.isVector; - if (sourceData.isVector) { - targetData.vectorData = sourceData.vectorData; + 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"; } + target_data.is_updated = target_data.isUpdatedComparedTo(old_target_data); + if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n"; - llvm::outs() << "[neura-interpreter] └─ value = " << targetData.value - << ", [pred = " << targetData.predicate << "]\n"; - } + 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 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 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 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) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.return:\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"; + auto return_values = op.getValues(); + // Handles void return (no values). + if (return_values.empty()) { + llvm::outs() << "[neura-interpreter] → Output: (void)\n"; + return true; + } + + // 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)) { + llvm::errs() << "[neura-interpreter] └─ Return value not found in value map\n"; return false; } - returnValues.push_back(valueMap[val]); + results.push_back(value_to_predicated_data_map[val]); } if (isVerboseMode()) { - llvm::outs() << "[neura-interpreter] └─ Return values:"; - } - - if (returnValues.empty()) { - if (isVerboseMode()) { - llvm::outs() << " void\n"; - } - } else { - llvm::outs() << "\n"; - for (size_t i = 0; i < returnValues.size(); ++i) { - const auto &data = returnValues[i]; - if (isVerboseMode()) { - llvm::outs() << " [" << i << "]: "; - } - - if (data.isVector) { - if (isVerboseMode()) { - llvm::outs() << "vector=["; - } - - for (size_t j = 0; j < data.vectorData.size() && isVerboseMode(); ++j) { - float val = data.predicate ? data.vectorData[j] : 0.0f; + llvm::outs() << "[neura-interpreter] ├─ Return values:\n"; + for (size_t i = 0; i < results.size(); ++i) { + const auto &data = results[i]; + // Prints 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.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) llvm::outs() << ", "; - } - if (isVerboseMode()) { - llvm::outs() << "]"; + if (j != data.vector_data.size() - 1) + llvm::outs() << ", "; } + llvm::outs() << "]"; } else { + // Prints scalar value with predicate check (0.0f if predicate is false). 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; } -bool handleGrantPredicateOp(neura::GrantPredicateOp 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 two operands: a source value and a new predicate value. It updates + * the source's predicate to be the logical AND of the source's original + * predicate, the new predicate's validity, and whether the new predicate + * value is non-zero (true). The numeric value is retained, while the + * computed predicate is applied. 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_to_predicated_data_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 if operands are invalid + * or missing from the 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"; } @@ -2085,58 +2508,123 @@ 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 + * numeric value is retained, while the one-time predicate is applied. Errors + * are returned for invalid operand/attribute combinations or unsupported + * constant types. + * + * @param op The neura.grant_once 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 operation is successfully + * executed; false for invalid inputs + * or unsupported types + */ +bool handleGrantOnceOp(neura::GrantOnceOp op, + llvm::DenseMap &value_to_predicated_data_map) { if(isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_once:\n"; } - - if (op.getOperation()->getNumOperands() != 1) { + // Checks if either a value operand or constant attribute is provided. + bool has_value = op.getValue() != nullptr; + bool has_constant = op.getConstantValue().has_value(); + + if (has_value == has_constant) { 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 (has_value) { + Value input_value = op.getValue(); + if (!value_to_predicated_data_map.count(input_value)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Source value not found in value_to_predicated_data_map\n"; + } + return false; + } + source = value_to_predicated_data_map[input_value]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Source value: " << source.value << ", [pred = " << source.predicate << "]\n"; + } + } else { + // Extracts and converts 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.is_vector = false; + + } else if (auto float_attr = mlir::dyn_cast(constant_attr)) { + source.value = float_attr.getValueAsDouble(); + source.predicate = false; + source.is_vector = 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"; + } } - + // Tracks if this operation has already granted access (static to persist across calls). + static llvm::DenseMap granted; + bool has_granted = granted[op.getOperation()]; + bool result_predicate = !has_granted; - static llvm::DenseMap granted; - bool hasGranted = granted[op.getValue()]; - - bool resultPredicate = !hasGranted; - if (!hasGranted) { - granted[op.getValue()] = true; + if (!has_granted) { + granted[op.getOperation()] = true; if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] ├─ First access - granting predicate\n"; } @@ -2144,22 +2632,41 @@ 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 + * one operand (a source value) and return a copy of that value with its + * predicate set to true, regardless of the original predicate. It effectively + * "grants" validity unconditionally. Errors are returned if the operand count + * is not exactly one. + * + * @param op The neura.grant_always operation to handle + * @param value_to_predicated_data_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_to_predicated_data_map) { if (isVerboseMode()) { llvm::outs() << "[neura-interpreter] Executing neura.grant_always:\n"; } @@ -2171,190 +2678,447 @@ 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; + // Tracks 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 + * + * Processes core logic for all operation types and returns handling results. This function + * abstracts the common operation processing logic while accommodating mode-specific requirements + * through optional parameters for control flow information. + * + * @param op The operation to process + * @param value_to_predicated_data_map Reference to map storing predicated data for values + * @param current_block Pointer to current block (control flow mode only) + * @param last_visited_block Pointer to previously visited block (control flow mode only) + * @return OperationHandleResult Structure containing processing status and control flags + */ +OperationHandleResult handleOperation( + Operation* op, + llvm::DenseMap& value_to_predicated_data_map, + Block**current_block = nullptr, + Block** last_visited_block = nullptr) { + + OperationHandleResult result{true, false, false}; + + if (auto const_op = dyn_cast(op)) { + result.success = handleArithConstantOp(const_op, value_to_predicated_data_map); + } else if (auto const_op = dyn_cast(op)) { + result.success = handleNeuraConstantOp(const_op, value_to_predicated_data_map); + } else if (auto mov_op = dyn_cast(op)) { + value_to_predicated_data_map[mov_op.getResult()] = value_to_predicated_data_map[mov_op.getOperand()]; + } else if (auto add_op = dyn_cast(op)) { + result.success = handleAddOp(add_op, value_to_predicated_data_map); + } else if (auto sub_op = dyn_cast(op)) { + result.success = handleSubOp(sub_op, value_to_predicated_data_map); + } else if (auto fadd_op = dyn_cast(op)) { + result.success = handleFAddOp(fadd_op, value_to_predicated_data_map); + } else if (auto fsub_op = dyn_cast(op)) { + result.success = handleFSubOp(fsub_op, value_to_predicated_data_map); + } else if (auto fmul_op = dyn_cast(op)) { + result.success = handleFMulOp(fmul_op, value_to_predicated_data_map); + } else if (auto fdiv_op = dyn_cast(op)) { + result.success = handleFDivOp(fdiv_op, value_to_predicated_data_map); + } else if (auto vfmul_op = dyn_cast(op)) { + result.success = handleVFMulOp(vfmul_op, value_to_predicated_data_map); + } else if (auto fadd_fadd_op = dyn_cast(op)) { + result.success = handleFAddFAddOp(fadd_fadd_op, value_to_predicated_data_map); + } else if (auto fmul_fadd_op = dyn_cast(op)) { + result.success = handleFMulFAddOp(fmul_fadd_op, value_to_predicated_data_map); + } else if (auto ret_op = dyn_cast(op)) { + 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)) { + result.success = handleFCmpOp(fcmp_op, value_to_predicated_data_map); + } else if (auto icmp_op = dyn_cast(op)) { + result.success = handleICmpOp(icmp_op, value_to_predicated_data_map); + } else if (auto or_op = dyn_cast(op)) { + result.success = handleOrOp(or_op, value_to_predicated_data_map); + } else if (auto not_op = dyn_cast(op)) { + result.success = handleNotOp(not_op, value_to_predicated_data_map); + } else if (auto sel_op = dyn_cast(op)) { + 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 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. + 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; // Marks as branch to reset index. + } else { + result.success = false; + } + } else if (auto cond_br_op = dyn_cast(op)) { + 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)) { + // 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)) { + result.success = handleReserveOp(reserve_op, value_to_predicated_data_map); + } else if (auto ctrl_mov_op = dyn_cast(op)) { + result.success = handleCtrlMovOp(ctrl_mov_op, value_to_predicated_data_map); + } else if (auto return_op = dyn_cast(op)) { + result.success = handleNeuraReturnOp(return_op, value_to_predicated_data_map); + result.is_terminated = true; + } else if (auto grant_pred_op = dyn_cast(op)) { + result.success = handleGrantPredicateOp(grant_pred_op, value_to_predicated_data_map); + } else if (auto grant_once_op = dyn_cast(op)) { + result.success = handleGrantOnceOp(grant_once_op, value_to_predicated_data_map); + } else if (auto grant_always_op = dyn_cast(op)) { + 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"; + result.success = false; + } + + 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 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, + llvm::SmallVector& next_pending_operation_queue) { + + // 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)) { + old_values[result] = value_to_predicated_data_map[result]; + } + } + + // 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()) { + llvm::outs() << "[neura-interpreter] Operation failed, no propagation\n"; + } + return false; + } + + // 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. + if (!old_values.count(result)) { + new_data.is_updated = true; + value_to_predicated_data_map[result] = new_data; + has_update = true; + continue; + } + + // 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; + value_to_predicated_data_map[result] = new_data; + has_update = true; + } else { + new_data.is_updated = false; + value_to_predicated_data_map[result] = new_data; + } + } + // 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(); + 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"; + } + } + } + + // Propagates updates to dependent operations if changes occurred. + if (has_update) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Operation updated, propagating to users...\n"; + } + + // Collects all values affected by the update. + llvm::SmallVector affected_values; + for (Value result : op->getResults()) { + 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); + } + } + + // 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(); + Value target = ctrl_mov_op.getTarget(); + 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); + } + } + } + + // 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]) { + next_pending_operation_queue.push_back(user_op); + is_operation_enqueued[user_op] = true; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Added user to next pending_operation_queue: "; + user_op->print(llvm::outs()); + llvm::outs() << "\n"; + } + } + } + } + } + + return true; +} + +/** + * @brief Unified execution entry point supporting both data flow and control flow modes + * + * 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 + * @param value_to_predicated_data_map Reference to map storing predicated data for values + * @return int 0 if execution completes successfully, 1 on error + */ +int run(func::FuncOp func, + llvm::DenseMap& value_to_predicated_data_map) { + if (isDataflowMode()) { + // Data flow mode execution logic + // Initializes pending operation queue with all operations except return operations. + + std::vector op_seq; + for (auto& block : func.getBody()) { + for (auto& op : block.getOperations()) { + op_seq.emplace_back(&op); + } + } + + // 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); + + 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"; + } + } + + int iter_count = 0; + // Main loop: processes pending operation queue until empty. + 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"; + } + + llvm::SmallVector next_pending_operation_queue; + for (Operation* op : pending_operation_queue) { + is_operation_enqueued[op] = false; + 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 EXIT_FAILURE; + } + consumer_dependent_on_producer_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); + } + + 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: 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); + // 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; + 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 { + // Regular operations increment to next operation in block. + op_index++; + } + } + } + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) { + // Parses command line arguments. for (int i = 0; i < argc; ++i) { if (std::string(argv[i]) == "--verbose") { setVerboseMode(true); + } else if (std::string(argv[i]) == "--dataflow") { + setDataflowMode(true); } } if (argc < 2) { - llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose]\n"; - return 1; + llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose] [--dataflow]\n"; + return EXIT_FAILURE; } + // Initializes MLIR context and dialects. DialectRegistry registry; registry.insert(); MLIRContext context; context.appendDialectRegistry(registry); - llvm::SourceMgr sourceMgr; - auto fileOrErr = mlir::openInputFile(argv[1]); - if (!fileOrErr) { + // Loads and parses input MLIR file. + 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; + return EXIT_FAILURE; } - 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; + return EXIT_FAILURE; } - // Changes map to store PredicatedData instead of just float. - llvm::DenseMap valueMap; - - Memory mem(1024); // 1MB - - Block *currentBlock = nullptr; - Block *lastVisitedBlock = nullptr; - size_t opIndex = 0; - bool isTerminated = false; + // Initializes data structures. + llvm::DenseMap value_to_predicated_data_map; 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; - } - - 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"; - return 1; - } + if (run(func, value_to_predicated_data_map)) { + llvm::errs() << "[neura-interpreter] Execution failed\n"; + return EXIT_FAILURE; } } - return 0; -} + return EXIT_SUCCESS; +} \ No newline at end of file