diff --git a/include/NeuraDialect/NeuraOps.td b/include/NeuraDialect/NeuraOps.td index 25028b57..b7972aa1 100644 --- a/include/NeuraDialect/NeuraOps.td +++ b/include/NeuraDialect/NeuraOps.td @@ -39,7 +39,7 @@ def Neura_FAddOp : Op { let arguments = (ins AnyType:$lhs, AnyType:$rhs, Optional:$predicate); let results = (outs AnyType:$result); // let assemblyFormat = "$lhs `,` $rhs `,` $predicate attr-dict `:` type($result)"; - //let traits = [SameOperandsAndResultElementType]; + // let traits = [SameOperandsAndResultElementType]; } // Defines a floating-point substraction operation. diff --git a/test/neura/interpreter/add.mlir b/test/neura/interpreter/add.mlir deleted file mode 100644 index 836c625d..00000000 --- a/test/neura/interpreter/add.mlir +++ /dev/null @@ -1,13 +0,0 @@ -// RUN: neura-interpreter %s | FileCheck %s - -module { - func.func @test() -> f32 { - %arg0 = "neura.constant"() <{value = 9.0 : f32}> : () -> f32 - %cst = "neura.constant"() <{value = 2.0 : f32}> : () -> f32 - %0 = "neura.data_mov"(%arg0) : (f32) -> f32 - %1 = "neura.data_mov"(%cst) : (f32) -> f32 - %2 = "neura.fadd"(%0, %1) : (f32, f32) -> f32 - return %2 : f32 - // CHECK: 11.0 - } -} diff --git a/test/neura/interpreter/basic_operation/add.mlir b/test/neura/interpreter/basic_operation/add.mlir new file mode 100644 index 00000000..bd731a96 --- /dev/null +++ b/test/neura/interpreter/basic_operation/add.mlir @@ -0,0 +1,60 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ===----------------------------------------------------------------------===// +// Test 1: Add two float constants +// ===----------------------------------------------------------------------===// +func.func @test_add_f32() -> f32 { + %a = arith.constant 10.0 : f32 + %b = arith.constant 32.0 : f32 + %res = "neura.add"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 42.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 2: Add a negative and positive float +// ===----------------------------------------------------------------------===// +func.func @test_add_negative() -> f32 { + %a = arith.constant -5.0 : f32 + %b = arith.constant 3.0 : f32 + %res = "neura.add"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -2.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 3: Add two fractional values +// ===----------------------------------------------------------------------===// +func.func @test_add_fraction() -> f32 { + %a = arith.constant 2.5 : f32 + %b = arith.constant 1.25 : f32 + %res = "neura.add"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 4.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 4: Add zero and a number +// ===----------------------------------------------------------------------===// +func.func @test_add_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 7.0 : f32 + %res = "neura.add"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 7.000000 + return %res : f32 +} + +// RUN: neura-interpreter %s | FileCheck %s + +// ===----------------------------------------------------------------------===// +// Test 5: Add with operation predicate 0 +// ===----------------------------------------------------------------------===// +func.func @test_add_predicate_zero() -> f32 { + %a = arith.constant 10.0 : f32 + %b = arith.constant 32.0 : f32 + %pred = arith.constant 0 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.add"(%a, %b, %pred_f32) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/br.mlir b/test/neura/interpreter/basic_operation/br.mlir new file mode 100644 index 00000000..fe49b75e --- /dev/null +++ b/test/neura/interpreter/basic_operation/br.mlir @@ -0,0 +1,21 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_br_with_args() -> i32 { + %0 = "neura.constant"() {value = 42 : i32} : () -> i32 + "neura.br"(%0) [^bb1] {operandSegmentSizes = array} : (i32) -> () + + ^bb1(%a: i32): + // CHECK: [neura-interpreter] → Output: 42.000000 + return %a : i32 +} + +func.func @test_br_with_multi_args() { + %0 = "neura.constant"() {value = 42 : i32} : () -> i32 + %1 = "neura.constant"() {value = 1.0 : f32} : () -> f32 + "neura.br"(%0, %1) [^bb1] {operandSegmentSizes = array} : (i32, f32) -> () + + ^bb1(%a: i32, %b: f32): + "neura.add"(%a, %a) : (i32, i32) -> i32 + // CHECK-NEXT: [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 new file mode 100644 index 00000000..074d755c --- /dev/null +++ b/test/neura/interpreter/basic_operation/cast.mlir @@ -0,0 +1,59 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// int -> float +func.func @test_cast_i2f() -> f32 { + %a = arith.constant 42 : i32 + %res = "neura.cast"(%a) { cast_type = "i2f" } : (i32) -> f32 + // CHECK: [neura-interpreter] → Output: 42.000000 + return %res : f32 +} + +// float -> int +func.func @test_cast_f2i() -> i32 { + %a = arith.constant 3.14 : f32 + %res = "neura.cast"(%a) { cast_type = "f2i" } : (f32) -> i32 + // CHECK: [neura-interpreter] → Output: 3.000000 + return %res : i32 +} + +// bool -> int +func.func @test_cast_bool2i() -> i32 { + %b = arith.constant 1 : i1 + %res = "neura.cast"(%b) { cast_type = "bool2i" } : (i1) -> i32 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %res : i32 +} + +// bool -> float +func.func @test_cast_bool2f() -> f32 { + %b = arith.constant 0 : i1 + %res = "neura.cast"(%b) { cast_type = "bool2f" } : (i1) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +// int -> bool +func.func @test_cast_i2bool() -> i1 { + %a = arith.constant 100 : i32 + %res = "neura.cast"(%a) { cast_type = "i2bool" } : (i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %res : i1 +} + +// f2i with true predicate +func.func @test_cast_predicated() -> i32 { + %val = arith.constant 5.5 : f32 + %pred = arith.constant 1 : i1 + %res = "neura.cast"(%val, %pred) { cast_type = "f2i" } : (f32, i1) -> i32 + // CHECK: [neura-interpreter] → Output: 6.000000 + return %res : i32 +} + +// f2i with false predicate +func.func @test_cast_predicate_false() -> i32 { + %val = arith.constant 5.5 : f32 + %pred = arith.constant 0 : i1 + %res = "neura.cast"(%val, %pred) { cast_type = "f2i" } : (f32, i1) -> i32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : i32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/cond_br.mlir b/test/neura/interpreter/basic_operation/cond_br.mlir new file mode 100644 index 00000000..d23cab63 --- /dev/null +++ b/test/neura/interpreter/basic_operation/cond_br.mlir @@ -0,0 +1,51 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_cond_br_true() { + %cond = arith.constant 1 : i1 + "neura.cond_br"(%cond) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1) -> () + // CHECK: [neura-interpreter] → Output: (void) +^bb1: + return +^bb2: + return +} + +func.func @test_cond_br_false() { + %cond = arith.constant 0 : i1 + "neura.cond_br"(%cond) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1) -> () + // CHECK: [neura-interpreter] → Output: (void) +^bb1: + return +^bb2: + return +} + +func.func @test_cond_br_with_valid_predicate() { + %cond = arith.constant 1 : i1 + %pred = arith.constant 1 : i32 + "neura.cond_br"(%cond, %pred) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1, i32) -> () + // CHECK: [neura-interpreter] → Output: (void) +^bb1: + return +^bb2: + return +} + +func.func @test_nested_cond_br() { + %cond1 = arith.constant 1 : i1 + "neura.cond_br"(%cond1) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1) -> () + +^bb1: + %cond2 = arith.constant 0 : i1 + "neura.cond_br"(%cond2) [^bb3, ^bb4] {operandSegmentSizes = array} : (i1) -> () + // CHECK: [neura-interpreter] → Output: (void) + +^bb2: + return + +^bb3: + return + +^bb4: + return +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/ctrl_mov.mlir b/test/neura/interpreter/basic_operation/ctrl_mov.mlir new file mode 100644 index 00000000..a6574ac7 --- /dev/null +++ b/test/neura/interpreter/basic_operation/ctrl_mov.mlir @@ -0,0 +1,34 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_ctrl_mov_basic() { + %a = "neura.reserve"() : () -> (i32) + %const = arith.constant 42 : i32 + + "neura.ctrl_mov"(%const, %a) : (i32, i32) -> () + + // CHECK: [neura-interpreter] → Output: (void) + + return +} + +func.func @test_ctrl_mov_chained() { + %a = "neura.reserve"() : () -> (i32) + %b = "neura.reserve"() : () -> (i32) + %const = arith.constant 10 : i32 + + "neura.ctrl_mov"(%const, %a) : (i32, i32) -> () + + "neura.ctrl_mov"(%a, %b) : (i32, i32) -> () + // CHECK: [neura-interpreter] → Output: (void) + + return +} + +func.func @test_ctrl_mov_vector() { + %vec_reserve = "neura.reserve"() : () -> (vector<4xf32>) + %vec_const = "neura.constant"() {value = dense<[1.0, 2.0, 3.0, 4.0]> : vector<4xf32>} : () -> vector<4xf32> + + "neura.ctrl_mov"(%vec_const, %vec_reserve) : (vector<4xf32>, vector<4xf32>) -> () + // CHECK: [neura-interpreter] → Output: (void) + return +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fadd.mlir b/test/neura/interpreter/basic_operation/fadd.mlir new file mode 100644 index 00000000..f13717bc --- /dev/null +++ b/test/neura/interpreter/basic_operation/fadd.mlir @@ -0,0 +1,72 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ===----------------------------------------------------------------------===// +// Test 1: Valid neura.fadd with positive constants +// ===----------------------------------------------------------------------===// +func.func @test_fadd_positive() -> f32 { + %a = arith.constant 5.5 : f32 + %b = arith.constant 3.25 : f32 + %res = "neura.fadd"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 8.750000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 2: Valid neura.fadd with negative constants +// ===----------------------------------------------------------------------===// +func.func @test_fadd_negative() -> f32 { + %a = arith.constant -10.25 : f32 + %b = arith.constant -5.75 : f32 + %res = "neura.fadd"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -16.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 3: Valid neura.fadd with mixed signs +// ===----------------------------------------------------------------------===// +func.func @test_fadd_mixed_signs() -> f32 { + %a = arith.constant -7.5 : f32 + %b = arith.constant 12.25 : f32 + %res = "neura.fadd"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 4.750000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 4: Valid neura.fadd with zero +// ===----------------------------------------------------------------------===// +func.func @test_fadd_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 25.5 : f32 + %res = "neura.fadd"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 25.500000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 4: Predicate handling in neura.fadd +// ===----------------------------------------------------------------------===// +func.func @test_fadd_invalid_predicate() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 25.5 : f32 + %pred = arith.constant 0 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.fadd"(%a, %b, %pred_f32) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 5: Nested predicate handling in neura.fadd +// ===----------------------------------------------------------------------===// +func.func @test_nested_fadd_invalid_predicate() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 25.5 : f32 + %pred = arith.constant 0 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %tmp = "neura.fadd"(%a, %b, %pred_f32) : (f32, f32, f32) -> f32 + %res = "neura.fadd"(%tmp, %b, %pred_f32) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fadd_fadd.mlir b/test/neura/interpreter/basic_operation/fadd_fadd.mlir new file mode 100644 index 00000000..02664da2 --- /dev/null +++ b/test/neura/interpreter/basic_operation/fadd_fadd.mlir @@ -0,0 +1,65 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// Test basic fused fadd operation: (2.5 + 1.5) + 3.0 = 7.0 +func.func @test_fadd_fadd_basic() -> f32 { + %a = arith.constant 2.5 : f32 + %b = arith.constant 1.5 : f32 + %c = arith.constant 3.0 : f32 + %res = "neura.fadd_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 7.000000 + return %res : f32 +} + +// Test with negative numbers: (5.0 + (-2.0)) + (-1.0) = 2.0 +func.func @test_fadd_fadd_negative() -> f32 { + %a = arith.constant 5.0 : f32 + %b = arith.constant -2.0 : f32 + %c = arith.constant -1.0 : f32 + %res = "neura.fadd_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 2.000000 + return %res : f32 +} + +// Test with zero: (0.0 + 4.0) + 6.0 = 10.0 +func.func @test_fadd_fadd_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 4.0 : f32 + %c = arith.constant 6.0 : f32 + %res = "neura.fadd_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 10.000000 + return %res : f32 +} + +// Test with valid predicate: (3.0 + 1.0) + 2.0 = 6.0 +func.func @test_fadd_fadd_with_valid_predicate() -> f32 { + %a = arith.constant 3.0 : f32 + %b = arith.constant 1.0 : f32 + %c = arith.constant 2.0 : f32 + %pred = arith.constant 1 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.fadd_fadd"(%a, %b, %c, %pred_f32) : (f32, f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 6.000000 + return %res : f32 +} + +// Test with false predicate (should return 0) +func.func @test_fadd_fadd_with_invalid_predicate() -> f32 { + %a = arith.constant 10.0 : f32 + %b = arith.constant 20.0 : f32 + %c = arith.constant 30.0 : f32 + %pred = arith.constant 0 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.fadd_fadd"(%a, %b, %c, %pred_f32) : (f32, f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +// Test with invalid input predicate +func.func @test_fadd_fadd_with_invalid_input_predicate() -> f32 { + %a = "neura.constant"() {value = 5.0 : f32, predicate = false} : () -> f32 + %b = arith.constant 3.0 : f32 + %c = arith.constant 2.0 : f32 + %res = "neura.fadd_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fcmp.mlir b/test/neura/interpreter/basic_operation/fcmp.mlir new file mode 100644 index 00000000..7888d7e5 --- /dev/null +++ b/test/neura/interpreter/basic_operation/fcmp.mlir @@ -0,0 +1,96 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ====== Equal comparison (eq) ====== +func.func @test_fcmp_eq_true() -> i1 { + %a = arith.constant 3.14 : f32 + %eq = "neura.fcmp"(%a, %a) {cmpType = "eq"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %eq : i1 +} + +func.func @test_fcmp_eq_false() -> i1 { + %a = arith.constant 3.14 : f32 + %b = arith.constant 2.71 : f32 + %eq = "neura.fcmp"(%a, %b) {cmpType = "eq"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %eq : i1 +} + +func.func @test_fcmp_ne_true() -> i1 { + %a = arith.constant 3.14 : f32 + %b = arith.constant 2.71 : f32 + %ne = "neura.fcmp"(%a, %b) {cmpType = "ne"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ne : i1 +} + +func.func @test_fcmp_ne_false() -> i1 { + %a = arith.constant 3.14 : f32 + %ne = "neura.fcmp"(%a, %a) {cmpType = "ne"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ne : i1 +} + +func.func @test_fcmp_lt_true() -> i1 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 3.0 : f32 + %lt = "neura.fcmp"(%a, %b) {cmpType = "lt"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %lt : i1 +} + +func.func @test_fcmp_lt_false() -> i1 { + %a = arith.constant 3.0 : f32 + %b = arith.constant 2.0 : f32 + %lt = "neura.fcmp"(%a, %b) {cmpType = "lt"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %lt : i1 +} + +func.func @test_fcmp_le_true() -> i1 { + %a = arith.constant 2.5 : f32 + %b = arith.constant 2.5 : f32 + %le = "neura.fcmp"(%a, %b) {cmpType = "le"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %le : i1 +} + +func.func @test_fcmp_le_false() -> i1 { + %a = arith.constant 3.5 : f32 + %b = arith.constant 2.5 : f32 + %le = "neura.fcmp"(%a, %b) {cmpType = "le"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %le : i1 +} + +func.func @test_fcmp_gt_true() -> i1 { + %a = arith.constant 5.0 : f32 + %b = arith.constant 3.0 : f32 + %gt = "neura.fcmp"(%a, %b) {cmpType = "gt"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %gt : i1 +} + +func.func @test_fcmp_gt_false() -> i1 { + %a = arith.constant 3.0 : f32 + %b = arith.constant 5.0 : f32 + %gt = "neura.fcmp"(%a, %b) {cmpType = "gt"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %gt : i1 +} + +func.func @test_fcmp_ge_true() -> i1 { + %a = arith.constant 5.0 : f32 + %b = arith.constant 5.0 : f32 + %ge = "neura.fcmp"(%a, %b) {cmpType = "ge"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ge : i1 +} + +func.func @test_fcmp_ge_false() -> i1 { + %a = arith.constant 3.0 : f32 + %b = arith.constant 5.0 : f32 + %ge = "neura.fcmp"(%a, %b) {cmpType = "ge"} : (f32, f32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ge : i1 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fdiv.mlir b/test/neura/interpreter/basic_operation/fdiv.mlir new file mode 100644 index 00000000..492d6c00 --- /dev/null +++ b/test/neura/interpreter/basic_operation/fdiv.mlir @@ -0,0 +1,99 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_fdiv_positive() -> f32 { + %a = arith.constant 10.0 : f32 + %b = arith.constant 2.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 5.000000 + return %res : f32 +} + +func.func @test_fdiv_negative_dividend() -> f32 { + %a = arith.constant -10.0 : f32 + %b = arith.constant 2.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -5.000000 + return %res : f32 +} + +func.func @test_fdiv_negative_divisor() -> f32 { + %a = arith.constant 10.0 : f32 + %b = arith.constant -2.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -5.000000 + return %res : f32 +} + +func.func @test_fdiv_two_negatives() -> f32 { + %a = arith.constant -10.0 : f32 + %b = arith.constant -2.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 5.000000 + return %res : f32 +} + +func.func @test_fdiv_by_zero() -> f32 { + %a = arith.constant 5.0 : f32 + %b = arith.constant 0.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: nan + return %res : f32 +} + +func.func @test_fdiv_zero_dividend() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 5.0 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +func.func @test_fdiv_with_predicate_true() -> f32 { + %a = arith.constant 15.0 : f32 + %b = arith.constant 3.0 : f32 + %pred = arith.constant 1 : i32 + %res = "neura.fdiv"(%a, %b, %pred) : (f32, f32, i32) -> f32 + // CHECK: [neura-interpreter] → Output: 5.000000 + return %res : f32 +} + +func.func @test_fdiv_with_predicate_false() -> f32 { + %a = arith.constant 20.0 : f32 + %b = arith.constant 4.0 : f32 + %pred = arith.constant 0 : i32 + %res = "neura.fdiv"(%a, %b, %pred) : (f32, f32, i32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +func.func @test_fdiv_f64() -> f64 { + %a = arith.constant 10.5 : f64 + %b = arith.constant 2.5 : f64 + %res = "neura.fdiv"(%a, %b) : (f64, f64) -> f64 + // CHECK: [neura-interpreter] → Output: 4.200000 + return %res : f64 +} + +func.func @test_fdiv_decimal() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 0.5 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 4.000000 + return %res : f32 +} + +func.func @test_fdiv_large_numbers() -> f32 { + %a = arith.constant 1.0e20 : f32 + %b = arith.constant 1.0e10 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 10000000000.000000 + return %res : f32 +} + +func.func @test_fdiv_near_zero() -> f32 { + %a = arith.constant 1.0e-20 : f32 + %b = arith.constant 1.0e-10 : f32 + %res = "neura.fdiv"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fmul.mlir b/test/neura/interpreter/basic_operation/fmul.mlir new file mode 100644 index 00000000..93fdf302 --- /dev/null +++ b/test/neura/interpreter/basic_operation/fmul.mlir @@ -0,0 +1,58 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ===----------------------------------------------------------------------===// +// Test 1: Valid neura.fmul with positive constants +// ===----------------------------------------------------------------------===// +func.func @test_fmul_positive() -> f32 { + %a = arith.constant 2.5 : f32 + %b = arith.constant 3.0 : f32 + %res = "neura.fmul"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 7.500000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 2: Valid neura.fmul with negative result +// ===----------------------------------------------------------------------===// +func.func @test_fmul_negative_result() -> f32 { + %a = arith.constant -2.5 : f32 + %b = arith.constant 4.0 : f32 + %res = "neura.fmul"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -10.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 3: Valid neura.fmul with zero +// ===----------------------------------------------------------------------===// +func.func @test_fmul_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 10.5 : f32 + %res = "neura.fmul"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 4: Valid neura.fmul with three operands (including predicate) +// ===----------------------------------------------------------------------===// +func.func @test_fmul_with_predicate() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 5.0 : f32 + %p = arith.constant 1.0 : f32 // Non-zero predicate + %res = "neura.fmul"(%a, %b, %p) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 10.000000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 5: Neura.fmul with zero predicate (Negative case) +// ===----------------------------------------------------------------------===// +func.func @test_fmul_zero_predicate() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 5.0 : f32 + %p = arith.constant 0.0 : f32 // Zero predicate + %res = "neura.fmul"(%a, %b, %p) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fmul_add.mlir b/test/neura/interpreter/basic_operation/fmul_add.mlir new file mode 100644 index 00000000..85b10e75 --- /dev/null +++ b/test/neura/interpreter/basic_operation/fmul_add.mlir @@ -0,0 +1,73 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// (2.0 * 3.0) + 4.0 = 10.0 +func.func @test_fmul_fadd_basic() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 3.0 : f32 + %c = arith.constant 4.0 : f32 + %res = "neura.fmul_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 10.000000 + return %res : f32 +} + +// (5.0 * (-2.0)) + 12.0 = 2.0 +func.func @test_fmul_fadd_negative() -> f32 { + %a = arith.constant 5.0 : f32 + %b = arith.constant -2.0 : f32 + %c = arith.constant 12.0 : f32 + %res = "neura.fmul_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 2.000000 + return %res : f32 +} + +// (0.0 * 5.0) + 6.0 = 6.0 +func.func @test_fmul_fadd_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 5.0 : f32 + %c = arith.constant 6.0 : f32 + %res = "neura.fmul_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 6.000000 + return %res : f32 +} + +// (1.5 * 2.0) + 3.5 = 6.5 +func.func @test_fmul_fadd_decimal() -> f32 { + %a = arith.constant 1.5 : f32 + %b = arith.constant 2.0 : f32 + %c = arith.constant 3.5 : f32 + %res = "neura.fmul_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 6.500000 + return %res : f32 +} + +// (4.0 * 2.0) + 1.0 = 9.0 +func.func @test_fmul_fadd_with_valid_predicate() -> f32 { + %a = arith.constant 4.0 : f32 + %b = arith.constant 2.0 : f32 + %c = arith.constant 1.0 : f32 + %pred = arith.constant 1 : i1 // predicate 为 true + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.fmul_fadd"(%a, %b, %c, %pred_f32) : (f32, f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 9.000000 + return %res : f32 +} + +func.func @test_fmul_fadd_with_invalid_predicate() -> f32 { + %a = arith.constant 3.0 : f32 + %b = arith.constant 3.0 : f32 + %c = arith.constant 3.0 : f32 + %pred = arith.constant 0 : i1 + %pred_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.fmul_fadd"(%a, %b, %c, %pred_f32) : (f32, f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} + +func.func @test_fmul_fadd_with_invalid_input_predicate() -> f32 { + %a = arith.constant 2.0 : f32 + %b = "neura.constant"() {value = 5.0 : f32, predicate = false} : () -> f32 + %c = arith.constant 3.0 : f32 + %res = "neura.fmul_fadd"(%a, %b, %c) : (f32, f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/fsub.mlir b/test/neura/interpreter/basic_operation/fsub.mlir new file mode 100644 index 00000000..cd12aebf --- /dev/null +++ b/test/neura/interpreter/basic_operation/fsub.mlir @@ -0,0 +1,45 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ===----------------------------------------------------------------------===// +// Test 1: Valid neura.fsub with positive constants +// ===----------------------------------------------------------------------===// +func.func @test_fsub_positive() -> f32 { + %a = arith.constant 10.5 : f32 + %b = arith.constant 3.25 : f32 + %res = "neura.fsub"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 7.250000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 2: Valid neura.fsub with negative result +// ===----------------------------------------------------------------------===// +func.func @test_fsub_negative_result() -> f32 { + %a = arith.constant 5.0 : f32 + %b = arith.constant 8.75 : f32 + %res = "neura.fsub"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -3.750000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 3: Valid neura.fsub with negative operands +// ===----------------------------------------------------------------------===// +func.func @test_fsub_negative_operands() -> f32 { + %a = arith.constant -5.25 : f32 + %b = arith.constant -3.75 : f32 + %res = "neura.fsub"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -1.500000 + return %res : f32 +} + +// ===----------------------------------------------------------------------===// +// Test 4: Valid neura.fsub with zero +// ===----------------------------------------------------------------------===// +func.func @test_fsub_zero() -> f32 { + %a = arith.constant 0.0 : f32 + %b = arith.constant 10.5 : f32 + %res = "neura.fsub"(%a, %b) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: -10.500000 + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/gep.mlir b/test/neura/interpreter/basic_operation/gep.mlir new file mode 100644 index 00000000..c3c5611a --- /dev/null +++ b/test/neura/interpreter/basic_operation/gep.mlir @@ -0,0 +1,47 @@ +// RUN: neura-interpreter %s --verbose | FileCheck %s + +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 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/grant.mlir b/test/neura/interpreter/basic_operation/grant.mlir new file mode 100644 index 00000000..e0e2a0f3 --- /dev/null +++ b/test/neura/interpreter/basic_operation/grant.mlir @@ -0,0 +1,68 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_grant_predicate() -> vector<4xf32> { + %val = "neura.constant"() { + value = dense<[1.0, 2.0, 3.0, 4.0]> : vector<4xf32>, + predicate = true + } : () -> vector<4xf32> + + %pred = arith.constant 1 : i1 + + %res = "neura.grant_predicate"(%val, %pred) : + (vector<4xf32>, i1) -> vector<4xf32> + // CHECK: [neura-interpreter] → Output: [1.000000, 2.000000, 3.000000, 4.000000] + return %res : vector<4xf32> +} + +func.func @test_grant_once() -> vector<2xf32> { + %val = "neura.constant"() { + value = dense<[5.5, 6.5]> : vector<2xf32> + } : () -> vector<2xf32> + + %res = "neura.grant_once"(%val) : (vector<2xf32>) -> vector<2xf32> + // CHECK: [neura-interpreter] → Output: [5.500000, 6.500000] + return %res : vector<2xf32> +} + +func.func @test_grant_always() -> vector<3xf32> { + %val = "neura.constant"() { + value = dense<[10.0, 20.0, 30.0]> : vector<3xf32>, + predicate = false + } : () -> vector<3xf32> + + %res = "neura.grant_always"(%val) : (vector<3xf32>) -> vector<3xf32> + + // CHECK: [neura-interpreter] → Output: [10.000000, 20.000000, 30.000000] + return %res : vector<3xf32> +} + +func.func @test_combined_grants() -> vector<4xf32> { + %v = "neura.constant"() { + value = dense<[1.0, 2.0, 3.0, 4.0]> : vector<4xf32>, + predicate = true + } : () -> vector<4xf32> + + %p0 = arith.constant 0 : i1 + %p1 = arith.constant 1 : i1 + + %v_once = "neura.grant_once"(%v) : (vector<4xf32>) -> vector<4xf32> + %v_pred_true = "neura.grant_predicate"(%v_once, %p1) : (vector<4xf32>, i1) -> vector<4xf32> + %v_final = "neura.grant_always"(%v_pred_true) : (vector<4xf32>) -> vector<4xf32> + + // CHECK: [neura-interpreter] → Output: [1.000000, 2.000000, 3.000000, 4.000000] + return %v_final : vector<4xf32> +} + +func.func @test_combined_grants_blocked() -> vector<4xf32> { + %v = "neura.constant"() { + value = dense<[5.0, 6.0, 7.0, 8.0]> : vector<4xf32>, + predicate = true + } : () -> vector<4xf32> + + %p0 = arith.constant 0 : i1 + %v_once = "neura.grant_once"(%v) : (vector<4xf32>) -> vector<4xf32> + %v_pred = "neura.grant_predicate"(%v_once, %p0) : (vector<4xf32>, i1) -> vector<4xf32> + %v_final = "neura.grant_always"(%v_pred) : (vector<4xf32>) -> vector<4xf32> + // CHECK: [neura-interpreter] → Output: [5.000000, 6.000000, 7.000000, 8.000000] + return %v_final : vector<4xf32> +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/icmp.mlir b/test/neura/interpreter/basic_operation/icmp.mlir new file mode 100644 index 00000000..dbc804ee --- /dev/null +++ b/test/neura/interpreter/basic_operation/icmp.mlir @@ -0,0 +1,189 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ====== Equal comparison (eq) ====== +// Positive case: Equal numbers +func.func @test_icmp_eq_true() -> i1 { + %a = arith.constant 42 : i32 + %eq = "neura.icmp"(%a, %a) {cmpType = "eq"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %eq : i1 +} + +// Negative case: Unequal numbers +func.func @test_icmp_eq_false() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %eq = "neura.icmp"(%a, %b) {cmpType = "eq"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %eq : i1 +} + +// ====== Not equal comparison (ne) ====== +// Positive case: Unequal numbers +func.func @test_icmp_ne_true() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %ne = "neura.icmp"(%a, %b) {cmpType = "ne"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ne : i1 +} + +// Negative case: Equal numbers +func.func @test_icmp_ne_false() -> i1 { + %a = arith.constant 42 : i32 + %ne = "neura.icmp"(%a, %a) {cmpType = "ne"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ne : i1 +} + +// ====== Signed less than (slt) ====== +// Positive case: lhs < rhs +func.func @test_icmp_slt_true() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %slt = "neura.icmp"(%a, %b) {cmpType = "slt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %slt : i1 +} + +// Negative case: lhs >= rhs +func.func @test_icmp_slt_false() -> i1 { + %a = arith.constant 43 : i32 + %b = arith.constant 42 : i32 + %slt = "neura.icmp"(%a, %b) {cmpType = "slt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %slt : i1 +} + +// ====== Signed less than or equal (sle) ====== +// Positive case: lhs <= rhs +func.func @test_icmp_sle_true() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %sle = "neura.icmp"(%a, %b) {cmpType = "sle"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %sle : i1 +} + +// Negative case: lhs > rhs +func.func @test_icmp_sle_false() -> i1 { + %a = arith.constant 43 : i32 + %b = arith.constant 42 : i32 + %sle = "neura.icmp"(%a, %b) {cmpType = "sle"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %sle : i1 +} + +// ====== Signed greater than (sgt) ====== +// Positive case: lhs > rhs +func.func @test_icmp_sgt_true() -> i1 { + %a = arith.constant 43 : i32 + %b = arith.constant 42 : i32 + %sgt = "neura.icmp"(%a, %b) {cmpType = "sgt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %sgt : i1 +} + +// Negative case: lhs <= rhs +func.func @test_icmp_sgt_false() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %sgt = "neura.icmp"(%a, %b) {cmpType = "sgt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %sgt : i1 +} + +// ====== Signed greater than or equal (sge) ====== +// Positive case: lhs >= rhs +func.func @test_icmp_sge_true() -> i1 { + %a = arith.constant 43 : i32 + %b = arith.constant 42 : i32 + %sge = "neura.icmp"(%a, %b) {cmpType = "sge"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %sge : i1 +} + +// Negative case: lhs < rhs +func.func @test_icmp_sge_false() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %sge = "neura.icmp"(%a, %b) {cmpType = "sge"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %sge : i1 +} + +// ====== Unsigned less than (ult) ====== +// Positive case: lhs < rhs (unsigned) +func.func @test_icmp_ult_true() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %ult = "neura.icmp"(%a, %b) {cmpType = "ult"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ult : i1 +} + +// Negative case: lhs >= rhs (unsigned) +func.func @test_icmp_ult_false() -> i1 { + %a = arith.constant -1 : i32 // Unsigned: 4294967295 + %b = arith.constant 42 : i32 + %ult = "neura.icmp"(%a, %b) {cmpType = "ult"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ult : i1 +} + +// ====== Unsigned less than or equal (ule) ====== +// Positive case: lhs <= rhs (unsigned) +func.func @test_icmp_ule_true() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant 43 : i32 + %ule = "neura.icmp"(%a, %b) {cmpType = "ule"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ule : i1 +} + +// Negative case: lhs > rhs (unsigned) +func.func @test_icmp_ule_false() -> i1 { + %a = arith.constant -1 : i32 // Unsigned: 4294967295 + %b = arith.constant 42 : i32 + %ule = "neura.icmp"(%a, %b) {cmpType = "ule"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ule : i1 +} + +// ====== Unsigned greater than (ugt) ====== +// Positive case: lhs > rhs (unsigned) +func.func @test_icmp_ugt_true() -> i1 { + %a = arith.constant -1 : i32 // Unsigned: 4294967295 + %b = arith.constant 42 : i32 + %ugt = "neura.icmp"(%a, %b) {cmpType = "ugt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %ugt : i1 +} + +// Negative case: lhs <= rhs (unsigned) +func.func @test_icmp_ugt_false() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant -1 : i32 // Unsigned: 4294967295 + %ugt = "neura.icmp"(%a, %b) {cmpType = "ugt"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %ugt : i1 +} + +// ====== Unsigned greater than or equal (uge) ====== +// Positive case: lhs >= rhs (unsigned) +func.func @test_icmp_uge_true() -> i1 { + %a = arith.constant -1 : i32 // Unsigned: 4294967295 + %b = arith.constant 42 : i32 + %uge = "neura.icmp"(%a, %b) {cmpType = "uge"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %uge : i1 +} + +// Negative case: lhs < rhs (unsigned) +func.func @test_icmp_uge_false() -> i1 { + %a = arith.constant 42 : i32 + %b = arith.constant -1 : i32 // Unsigned: 4294967295 + %uge = "neura.icmp"(%a, %b) {cmpType = "uge"} : (i32, i32) -> i1 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %uge : i1 +} \ No newline at end of file diff --git a/test/neura/interpreter/interpreter.mlir b/test/neura/interpreter/basic_operation/interpreter.mlir similarity index 100% rename from test/neura/interpreter/interpreter.mlir rename to test/neura/interpreter/basic_operation/interpreter.mlir diff --git a/test/neura/interpreter/basic_operation/load_store.mlir b/test/neura/interpreter/basic_operation/load_store.mlir new file mode 100644 index 00000000..e60c8292 --- /dev/null +++ b/test/neura/interpreter/basic_operation/load_store.mlir @@ -0,0 +1,19 @@ +// 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 new file mode 100644 index 00000000..b47c801b --- /dev/null +++ b/test/neura/interpreter/basic_operation/load_store_index.mlir @@ -0,0 +1,63 @@ +// 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 new file mode 100644 index 00000000..a350326c --- /dev/null +++ b/test/neura/interpreter/basic_operation/not.mlir @@ -0,0 +1,25 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// Test 1: Bitwise NOT of 42 (result should be -43) +func.func @test_not_basic() -> i32 { + %a = arith.constant 42 : i32 + %res = "neura.not"(%a) : (i32) -> i32 + // CHECK: [neura-interpreter] → Output: -43.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 + 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 + return %res : i32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/or.mlir b/test/neura/interpreter/basic_operation/or.mlir new file mode 100644 index 00000000..13e9dede --- /dev/null +++ b/test/neura/interpreter/basic_operation/or.mlir @@ -0,0 +1,38 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// ====== Bitwise OR Operation Tests ====== + +// Case 1: 42 | 5 = 47 +func.func @test_or_basic() -> i32 { + %a = arith.constant 42 : i32 + %b = arith.constant 5 : i32 + %res = "neura.or"(%a, %b) : (i32, i32) -> i32 + // CHECK: [neura-interpreter] → Output: 47.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 + %res = "neura.or"(%a, %b) : (i32, i32) -> i32 + // CHECK: [neura-interpreter] → Output: 123.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 + 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 + %res = "neura.or"(%a, %b) : (i32, i32) -> i32 + // CHECK: [neura-interpreter] → Output: -1.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 new file mode 100644 index 00000000..b1ca96be --- /dev/null +++ b/test/neura/interpreter/basic_operation/phi.mlir @@ -0,0 +1,138 @@ +// RUN: neura-interpreter %s | FileCheck %s + +//===----------------------------------------------------------------------===// +// Test 1: Basic Phi node with control flow +//===----------------------------------------------------------------------===// +func.func @test_phi_ctrlflow() -> f32 { + %init = arith.constant 0.0 : f32 + %one = arith.constant 1.0 : f32 + + %v = "neura.reserve"() : () -> (f32) + "neura.ctrl_mov"(%one, %v) : (f32, f32) -> () + + %cond = arith.constant 0 : i1 + "neura.cond_br"(%cond) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1) -> () + +^bb1: + "neura.ctrl_mov"(%init, %v) : (f32, f32) -> () + "neura.br"() [^merge] {operandSegmentSizes = array} : () -> () + +^bb2: + "neura.br"() [^merge] {operandSegmentSizes = array} : () -> () + +^merge: + %phi = "neura.phi"(%init, %v) : (f32, f32) -> f32 + // CHECK: [neura-interpreter] → Output: 0.000000 + return %phi : f32 +} + +//===----------------------------------------------------------------------===// +// Test 2: Phi node in loop structure +//===----------------------------------------------------------------------===// +func.func @test_loop_phi() -> f32 { + %init = arith.constant 0.0 : f32 + %one = arith.constant 1.0 : f32 + %limit = arith.constant 3.0 : f32 + + %v = "neura.reserve"() : () -> (f32) + "neura.ctrl_mov"(%init, %v) : (f32, f32) -> () + + "neura.br"() [^loop_head] {operandSegmentSizes = array} : () -> () + +^loop_head: + %i = "neura.phi"(%v, %init) : (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: + %i_next = "neura.fadd"(%i, %one) : (f32, f32) -> f32 + "neura.ctrl_mov"(%i_next, %v) : (f32, f32) -> () + "neura.br"() [^loop_head] {operandSegmentSizes = array} : () -> () + +^loop_exit: + // CHECK: [neura-interpreter] → Output: 3.000000 + return %i : f32 +} + +//===----------------------------------------------------------------------===// +// Test 3: Phi node with multiple predecessors +//===----------------------------------------------------------------------===// +func.func @test_phi_multi_preds() -> i32 { + %c0 = arith.constant 0 : i32 + %c1 = arith.constant 1 : i32 + %c2 = arith.constant 2 : i32 + %cond = arith.constant 0 : i1 + "neura.cond_br"(%cond) [^bb1, ^bb2] {operandSegmentSizes = array} : (i1) -> () + +^bb1: + "neura.br"() [^merge] : () -> () + +^bb2: + "neura.br"() [^bb3] : () -> () + +^bb3: + "neura.br"() [^merge] : () -> () + +^extra: + "neura.br"() [^merge] : () -> () + +^merge: + %val = "neura.phi"(%c0, %c1, %c2) : (i32, i32, i32) -> i32 + // CHECK: [neura-interpreter] → Output: 1.000000 + return %val : i32 +} + +//===----------------------------------------------------------------------===// +// Test 4: Nested loops with phi nodes +//===----------------------------------------------------------------------===// +func.func @test_small_nested_loops() -> f32 { + %zero = arith.constant 0.0 : f32 + %one = arith.constant 1.0 : f32 + %two = arith.constant 2.0 : f32 + + %outer_i = "neura.reserve"() : () -> f32 + "neura.ctrl_mov"(%zero, %outer_i) : (f32, f32) -> () + + %outer_sum = "neura.reserve"() : () -> f32 + "neura.ctrl_mov"(%zero, %outer_sum) : (f32, f32) -> () + "neura.br"() [^outer_head] {operandSegmentSizes = array} : () -> () + +^outer_head: + %i = "neura.phi"(%outer_i, %zero) : (f32, f32) -> f32 + %outer_cond = "neura.fcmp"(%i, %two) {cmpType = "lt"} : (f32, f32) -> i1 + "neura.cond_br"(%outer_cond) [^outer_body, ^outer_exit] {operandSegmentSizes = array} : (i1) -> () + +^outer_body: + %inner_j = "neura.reserve"() : () -> f32 + "neura.ctrl_mov"(%zero, %inner_j) : (f32, f32) -> () + %inner_sum = "neura.reserve"() : () -> f32 + "neura.ctrl_mov"(%zero, %inner_sum) : (f32, f32) -> () + "neura.br"() [^inner_head] {operandSegmentSizes = array} : () -> () + +^inner_head: + %j = "neura.phi"(%inner_j, %zero) : (f32, f32) -> f32 + %inner_cond = "neura.fcmp"(%j, %two) {cmpType = "lt"} : (f32, f32) -> i1 + "neura.cond_br"(%inner_cond) [^inner_body, ^inner_exit] {operandSegmentSizes = array} : (i1) -> () + +^inner_body: + %i_mul_two = "neura.fmul"(%i, %two) : (f32, f32) -> f32 + %current_val = "neura.fadd"(%i_mul_two, %j) : (f32, f32) -> f32 + %new_inner_sum = "neura.fadd"(%inner_sum, %current_val) : (f32, f32) -> f32 + "neura.ctrl_mov"(%new_inner_sum, %inner_sum) : (f32, f32) -> () + %j_next = "neura.fadd"(%j, %one) : (f32, f32) -> f32 + "neura.ctrl_mov"(%j_next, %inner_j) : (f32, f32) -> () + "neura.br"() [^inner_head] {operandSegmentSizes = array} : () -> () + +^inner_exit: + %new_outer_sum = "neura.fadd"(%outer_sum, %inner_sum) : (f32, f32) -> f32 + "neura.ctrl_mov"(%new_outer_sum, %outer_sum) : (f32, f32) -> () + %i_next = "neura.fadd"(%i, %one) : (f32, f32) -> f32 + "neura.ctrl_mov"(%i_next, %outer_i) : (f32, f32) -> () + "neura.br"() [^outer_head] {operandSegmentSizes = array} : () -> () + +^outer_exit: + // CHECK: [neura-interpreter] → Output: 6.000000 + return %outer_sum : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/predicated_data.mlir b/test/neura/interpreter/basic_operation/predicated_data.mlir similarity index 88% rename from test/neura/interpreter/predicated_data.mlir rename to test/neura/interpreter/basic_operation/predicated_data.mlir index 42e635cf..f72670a1 100644 --- a/test/neura/interpreter/predicated_data.mlir +++ b/test/neura/interpreter/basic_operation/predicated_data.mlir @@ -6,6 +6,6 @@ module { %cst = "neura.constant"() <{value = 2.0 : f32, predicate = false}> : () -> !neura.data %res = "neura.fadd"(%arg0, %cst) : (!neura.data, !neura.data) -> !neura.data return %res : !neura.data - // CHECK: Output: 11.000000 (predicate=false) + // CHECK: [neura-interpreter] → Output: 0.000000 } } diff --git a/test/neura/interpreter/basic_operation/reserve.mlir b/test/neura/interpreter/basic_operation/reserve.mlir new file mode 100644 index 00000000..c02ff505 --- /dev/null +++ b/test/neura/interpreter/basic_operation/reserve.mlir @@ -0,0 +1,15 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_reserve_basic() { + %a = "neura.reserve"() : () -> (i32) + // CHECK: [neura-interpreter] → Output: (void) + return +} + +func.func @test_reserve_multiple_types() { + %a = "neura.reserve"() : () -> (i32) + %b = "neura.reserve"() : () -> (f32) + %c = "neura.reserve"() : () -> (tensor<4xf32>) + // CHECK: [neura-interpreter] → Output: (void) + return +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/sel.mlir b/test/neura/interpreter/basic_operation/sel.mlir new file mode 100644 index 00000000..e917288b --- /dev/null +++ b/test/neura/interpreter/basic_operation/sel.mlir @@ -0,0 +1,47 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_sel_with_comparison() -> f32 { + %a = arith.constant 5.0 : f32 + %b = arith.constant 3.0 : f32 + %cond = "neura.fcmp"(%a, %b) {cmpType = "gt"} : (f32, f32) -> i1 + + %true_val = arith.constant 100.0 : f32 + %false_val = arith.constant 200.0 : f32 + %res = "neura.sel"(%true_val, %false_val, %cond) : (f32, f32, i1) -> f32 + // CHECK: [neura-interpreter] → Output: 100.000000 + + return %res : f32 +} + +func.func @test_sel_with_comparison_false() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 3.0 : f32 + %cond = "neura.fcmp"(%a, %b) {cmpType = "gt"} : (f32, f32) -> i1 + + %true_val = arith.constant 100.0 : f32 + %false_val = arith.constant 200.0 : f32 + %res = "neura.sel"(%true_val, %false_val, %cond) : (f32, f32, i1) -> f32 + // CHECK: [neura-interpreter] → Output: 200.000000 + + return %res : f32 +} + +func.func @test_sel_nested_with_comparison() -> f32 { + %a = arith.constant 2.0 : f32 + %b = arith.constant 3.0 : f32 + %cond1 = "neura.fcmp"(%a, %b) {cmpType = "gt"} : (f32, f32) -> i1 + + %true_val1 = arith.constant 100.0 : f32 + %false_val1 = arith.constant 200.0 : f32 + %sel1 = "neura.sel"(%true_val1, %false_val1, %cond1) : (f32, f32, i1) -> f32 + + %c = arith.constant 5.0 : f32 + %d = arith.constant 1.0 : f32 + %cond2 = "neura.fcmp"(%c, %d) {cmpType = "gt"} : (f32, f32) -> i1 + + %true_val2 = arith.constant 300.0 : f32 + %res = "neura.sel"(%true_val2, %sel1, %cond2) : (f32, f32, i1) -> f32 + // CHECK: [neura-interpreter] → Output: 300.000000 + + return %res : f32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/sub.mlir b/test/neura/interpreter/basic_operation/sub.mlir new file mode 100644 index 00000000..7a6a89c1 --- /dev/null +++ b/test/neura/interpreter/basic_operation/sub.mlir @@ -0,0 +1,43 @@ +// RUN: neura-interpreter %s | FileCheck %s + +// Test basic subtraction with positive result +func.func @test_sub_positive() -> i32 { + %a = arith.constant 200 : i32 + %b = arith.constant 50 : i32 + %res = "neura.sub"(%a, %b) : (i32, i32) -> i32 + + // CHECK: [neura-interpreter] → Output: 150.000000 + return %res : i32 +} + +// Test subtraction with negative result +func.func @test_sub_negative() -> i32 { + %a = arith.constant 50 : i32 + %b = arith.constant 200 : i32 + %res = "neura.sub"(%a, %b) : (i32, i32) -> i32 + + // CHECK: [neura-interpreter] → Output: -150.000000 + return %res : i32 +} + +// Test subtraction with predicate=true +func.func @test_sub_with_predicate_true() -> i32 { + %a = arith.constant 300 : i32 + %b = arith.constant 100 : i32 + %pred = arith.constant 1 : i32 + %res = "neura.sub"(%a, %b, %pred) : (i32, i32, i32) -> i32 + + // CHECK: [neura-interpreter] → Output: 200.000000 + return %res : i32 +} + +// Test subtraction with predicate=false +func.func @test_sub_with_predicate_false() -> i32 { + %a = arith.constant 500 : i32 + %b = arith.constant 200 : i32 + %pred = arith.constant 0 : i32 + %res = "neura.sub"(%a, %b, %pred) : (i32, i32, i32) -> i32 + + // CHECK: [neura-interpreter] → Output: 0.000000 + return %res : i32 +} \ No newline at end of file diff --git a/test/neura/interpreter/basic_operation/vfmul.mlir b/test/neura/interpreter/basic_operation/vfmul.mlir new file mode 100644 index 00000000..d3e63605 --- /dev/null +++ b/test/neura/interpreter/basic_operation/vfmul.mlir @@ -0,0 +1,23 @@ +// RUN: neura-interpreter %s | FileCheck %s + +func.func @test_vfmul_basic() -> vector<2xf32> { + %a = "neura.constant"() {value = dense<[2.0, 3.0]> : vector<2xf32>} : () -> vector<2xf32> + %b = "neura.constant"() {value = dense<[4.0, 5.0]> : vector<2xf32>} : () -> vector<2xf32> + %res = "neura.vfmul"(%a, %b) : (vector<2xf32>, vector<2xf32>) -> vector<2xf32> + + // CHECK: [neura-interpreter] → Output: [8.000000, 15.000000] + + return %res : vector<2xf32> +} + +func.func @test_vfmul_with_valid_predicate() -> vector<3xf32> { + %a = "neura.constant"() {value = dense<[6.0, 7.0, 8.0]> : vector<3xf32>} : () -> vector<3xf32> + %b = "neura.constant"() {value = dense<[0.5, 2.0, 0.1]> : vector<3xf32>} : () -> vector<3xf32> + %pred = arith.constant 0 : i1 + %pred_as_f32 = "neura.cast"(%pred) {cast_type = "bool2f"} : (i1) -> f32 + %res = "neura.vfmul"(%a, %b, %pred_as_f32) : (vector<3xf32>, vector<3xf32>, f32) -> vector<3xf32> + + // CHECK: [neura-interpreter] → Output: [0.000000, 0.000000, 0.000000] + + return %res : vector<3xf32> +} \ No newline at end of file diff --git a/test/neura/interpreter/lower_and_interpret.mlir b/test/neura/interpreter/lower_and_interpret.mlir index 4f9c0371..9d50c317 100644 --- a/test/neura/interpreter/lower_and_interpret.mlir +++ b/test/neura/interpreter/lower_and_interpret.mlir @@ -32,7 +32,7 @@ module { %cst = arith.constant 2.0 : f32 %0 = arith.addf %arg0, %cst : f32 // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] - // CHECK: [neura-interpreter] Output: [[OUTPUT]] + // CHECK: [neura-interpreter] → Output: [[OUTPUT]] return %0 : f32 } } diff --git a/test/neura/interpreter/lower_and_interpret_subf.mlir b/test/neura/interpreter/lower_and_interpret_subf.mlir index 4c7d7d01..a91bed9d 100644 --- a/test/neura/interpreter/lower_and_interpret_subf.mlir +++ b/test/neura/interpreter/lower_and_interpret_subf.mlir @@ -34,7 +34,7 @@ module { %cst = arith.constant 2.0 : f32 %0 = arith.subf %arg0, %cst : f32 // CHECK: Golden output: [[OUTPUT:[0-9]+\.[0-9]+]] - // CHECK: [neura-interpreter] Output: [[OUTPUT]] + // CHECK: [neura-interpreter] → Output: [[OUTPUT]] return %0 : f32 } } diff --git a/tools/neura-interpreter/neura-interpreter.cpp b/tools/neura-interpreter/neura-interpreter.cpp index 53cf0eb8..b4699ca1 100644 --- a/tools/neura-interpreter/neura-interpreter.cpp +++ b/tools/neura-interpreter/neura-interpreter.cpp @@ -16,18 +16,2190 @@ #include #include +#include +#include +#include +#include +#include using namespace mlir; +static bool verbose = false; + +inline void setVerboseMode(bool v) { + verbose = v; +} + +inline bool isVerboseMode() { + return verbose; +} + +class Memory { +public: + Memory(size_t size) : mem(size, 0) { + freeList[0] = size; + } + + template + T load(size_t addr) const { + assert(validAddr(addr, sizeof(T)) && "Memory load out of bounds"); + T result; + std::memcpy(&result, &mem[addr], sizeof(T)); + return result; + } + + template + void store(size_t addr, const T& value) { + assert(validAddr(addr, sizeof(T)) && "Memory store out of bounds"); + std::memcpy(&mem[addr], &value, sizeof(T)); + } + + size_t malloc(size_t sizeBytes) { + for (auto it = freeList.begin(); it != freeList.end(); ++it) { + if (it->second >= sizeBytes) { + size_t addr = it->first; + size_t remain = it->second - sizeBytes; + freeList.erase(it); + if (remain > 0) { + freeList[addr + sizeBytes] = remain; + } + allocTable[addr] = sizeBytes; + return addr; + } + } + throw std::runtime_error("Out of memory"); + } + + void free(size_t addr) { + auto it = allocTable.find(addr); + if (it == allocTable.end()) { + std::cerr << "Invalid free at addr " << addr << "\n"; + return; + } + size_t size = it->second; + allocTable.erase(it); + freeList[addr] = size; + + mergeFreeBlocks(); + } + + void dump(size_t start = 0, size_t length = 64) const { + for (size_t i = start; i < start + length && i < mem.size(); ++i) { + printf("%02X ", mem[i]); + if ((i - start + 1) % 16 == 0) printf("\n"); + } + printf("\n"); + } + + size_t getSize() const { return mem.size(); } + +private: + std::vector mem; + std::unordered_map allocTable; + std::map freeList; + + bool validAddr(size_t addr, size_t size) const { + return addr + size <= mem.size(); + } + + void mergeFreeBlocks() { + auto it = freeList.begin(); + while (it != freeList.end()) { + auto curr = it++; + if (it != freeList.end() && curr->first + curr->second == it->first) { + curr->second += it->second; + freeList.erase(it); + it = curr; + } + } + } +}; + +template +class NdArray { +public: + NdArray(Memory& mem, const std::vector& dims) + : memory(mem), shape(dims) { + strides.resize(dims.size()); + size_t stride = sizeof(T); + for (int i = dims.size() - 1; i >= 0; --i) { + strides[i] = stride; + stride *= dims[i]; + } + totalSize = stride; + baseAddr = memory.malloc(totalSize); + } + + void set(const std::vector& indices, T value) { + size_t addr = calcAddr(indices); + memory.store(addr, value); + } + + T get(const std::vector& indices) const { + size_t addr = calcAddr(indices); + return memory.load(addr); + } + + void dumpRaw() const { + memory.dump(baseAddr, totalSize); + } + + void free() { + memory.free(baseAddr); + } + +private: + Memory& memory; + std::vector shape; + std::vector strides; + size_t totalSize; + size_t baseAddr; + + size_t calcAddr(const std::vector& indices) const { + assert(indices.size() == shape.size()); + size_t offset = baseAddr; + for (size_t i = 0; i < indices.size(); ++i) { + assert(indices[i] < shape[i]); + offset += indices[i] * strides[i]; + } + return offset; + } +}; + // Data structure to hold both value and predicate. struct PredicatedData { float value; bool predicate; + bool isVector; + std::vector vectorData; + bool isReserve; }; +bool handleArithConstantOp(mlir::arith::ConstantOp op, llvm::DenseMap& valueMap) { + 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(); + 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; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Parsed boolean constant : " + << (val.value ? "true" : "false") << "\n"; + } + } else { + val.value = static_cast(intAttr.getInt()); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Parsed integer constant : " + << llvm::format("%.6f", val.value) << "\n"; + } + } + } 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; + return true; +} + +bool handleNeuraConstantOp(neura::ConstantOp op, llvm::DenseMap& valueMap) { + auto attr = op.getValue(); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.constant:\n"; + } + + if (auto floatAttr = llvm::dyn_cast(attr)) { + PredicatedData val; + val.value = floatAttr.getValueAsDouble(); + val.predicate = true; + val.isVector = false; + + if (auto predAttr = op->getAttrOfType("predicate")) { + val.predicate = predAttr.getValue(); + } + + assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); + valueMap[op.getResult()] = val; + } else if (auto intAttr = llvm::dyn_cast(attr)) { + PredicatedData val; + val.value = static_cast(intAttr.getInt()); + val.predicate = true; + val.isVector = false; + + if (auto predAttr = op->getAttrOfType("predicate")) { + val.predicate = predAttr.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()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported vector element type in neura.constant\n"; + } + return false; + } + + PredicatedData val; + val.isVector = true; + val.predicate = true; + + size_t vectorSize = denseAttr.getNumElements(); + val.vectorData.resize(vectorSize); + + auto floatValues = denseAttr.getValues(); + std::copy(floatValues.begin(), floatValues.end(), val.vectorData.begin()); + + if (auto predAttr = op->getAttrOfType("predicate")) { + val.predicate = predAttr.getValue(); + } + + assert(valueMap.count(op.getResult()) == 0 && "Duplicate constant result?"); + valueMap[op.getResult()] = val; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Parsed vector constant of size: " << vectorSize << "\n"; + } + } else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported constant type in neura.constant\n"; + } + return false; + } + return true; +} + +bool handleAddOp(neura::AddOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.add:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.add expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operands \n"; + llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value << " [pred = " << lhs.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << " [pred = " << rhs.predicate << "]\n"; + } + + int64_t lhsInt = static_cast(std::round(lhs.value)); + int64_t rhsInt = static_cast(std::round(rhs.value)); + bool finalPredicate = lhs.predicate && rhs.predicate; + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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; + + PredicatedData result; + result.value = static_cast(sum); + result.predicate = finalPredicate; + result.isVector = false; + + valueMap[op.getResult()] = result; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + return true; +} + +bool handleSubOp(neura::SubOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.sub:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.sub expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getOperand(0)]; + auto rhs = valueMap[op.getOperand(1)]; + + 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"; + } + + bool finalPredicate = lhs.predicate && rhs.predicate; + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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; + + PredicatedData result; + result.value = static_cast(resultInt); + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFAddOp(neura::FAddOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fadd:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fadd expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + bool finalPredicate = lhs.predicate && rhs.predicate; + + 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"; + } + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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"; + } + } + + PredicatedData result; + result.value = lhs.value + rhs.value; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFSubOp(neura::FSubOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fsub:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fsub expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + bool finalPredicate = lhs.predicate && rhs.predicate; + + 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"; + } + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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"; + } + } + + PredicatedData result; + result.value = lhs.value - rhs.value; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFMulOp(neura::FMulOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fmul:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fmul expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getOperand(0)]; + auto rhs = valueMap[op.getOperand(1)]; + + 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"; + } + + bool finalPredicate = lhs.predicate && rhs.predicate; + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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; + + PredicatedData result; + result.value = resultFloat; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFDivOp(neura::FDivOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fdiv:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fdiv expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getOperand(0)]; + auto rhs = valueMap[op.getOperand(1)]; + + 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"; + } + + bool finalPredicate = lhs.predicate && rhs.predicate; + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + finalPredicate = finalPredicate && 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); + + if (rhsFloat == 0.0f) { + resultFloat = 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; + } + + PredicatedData result; + result.value = resultFloat; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value << " [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleVFMulOp(neura::VFMulOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.vfmul:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.vfmul expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + + if (!lhs.isVector || !rhs.isVector) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.vfmul requires both operands to be vectors\n"; + } + return false; + } + + auto printVector = [](ArrayRef vec) { + llvm::outs() << "["; + for (size_t i = 0; i < vec.size(); ++i) { + llvm::outs() << vec[i]; + if (i != vec.size() - 1) + llvm::outs() << ", "; + } + llvm::outs() << "]"; + }; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operands \n"; + llvm::outs() << "[neura-interpreter] │ ├─ LHS : vector size = " << lhs.vectorData.size() << ", "; + printVector(lhs.vectorData); + llvm::outs() << ", [pred = " << lhs.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ RHS : vector size = " << rhs.vectorData.size() << ", "; + printVector(rhs.vectorData); + llvm::outs() << ", [pred = " << rhs.predicate << "]\n"; + } + + if (lhs.vectorData.size() != rhs.vectorData.size()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Vector size mismatch in neura.vfmul\n"; + } + return false; + } + + bool finalPredicate = lhs.predicate && rhs.predicate; + + if (op.getNumOperands() > 2) { + auto pred = valueMap[op.getOperand(2)]; + if (pred.isVector) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Predicate operand must be a scalar in neura.vfmul\n"; + } + return false; + } + finalPredicate = finalPredicate && (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"; + } + } + + 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]; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : " << "vector size = " << result.vectorData.size() << ", "; + printVector(result.vectorData); + llvm::outs() << ", [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFAddFAddOp(neura::FAddFAddOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fadd_fadd:\n"; + } + + if (op.getNumOperands() < 3) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fadd_fadd expects at least three operands\n"; + } + return false; + } + + auto a = valueMap[op.getA()]; + auto b = valueMap[op.getB()]; + auto c = valueMap[op.getC()]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operands \n"; + llvm::outs() << "[neura-interpreter] │ ├─ Operand A : value = " << a.value << ", [pred = " << a.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Operand B : value = " << b.value << ", [pred = " << b.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Operand C : value = " << c.value << ", [pred = " << c.predicate << "]\n"; + } + + bool finalPredicate = a.predicate && b.predicate && c.predicate; + + if (op.getNumOperands() > 3) { + auto predOperand = valueMap[op.getOperand(3)]; + finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value + << " [pred = " << predOperand.predicate << "]\n"; + } + } + + float resultValue = (a.value + b.value) + c.value; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Calculation : (" << a.value << " + " << b.value << ") + " << c.value + << " = " << resultValue << "\n"; + } + + PredicatedData result; + result.value = resultValue; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue + << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFMulFAddOp(neura::FMulFAddOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fmul_fadd:\n"; + } + if (op.getNumOperands() < 3) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fmul_fadd expects at least three operands\n"; + } + return false; + } + + auto a = valueMap[op.getA()]; + auto b = valueMap[op.getB()]; + auto c = valueMap[op.getC()]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operands \n"; + llvm::outs() << "[neura-interpreter] │ ├─ Operand A : value = " << a.value << ", [pred = " << a.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Operand B : value = " << b.value << ", [pred = " << b.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ Operand C : value = " << c.value << ", [pred = " << c.predicate << "]\n"; + } + + bool finalPredicate = a.predicate && b.predicate && c.predicate; + + if (op.getNumOperands() > 3) { + auto predOperand = valueMap[op.getOperand(3)]; + finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value + << ", [pred = " << predOperand.predicate << "]\n"; + } + } + + float resultValue = 0.0f; + float mulResult = a.value * b.value; + resultValue = mulResult + c.value; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Calculation : (" << a.value << " * " << b.value << ") + " << c.value + << " = " << mulResult << " + " << c.value << " = " << resultValue << "\n"; + } + + PredicatedData result; + result.value = resultValue; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue + << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleFuncReturnOp(func::ReturnOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing func.return:\n"; + } + if (!op && isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Expected func.return but got something else\n"; + return false; + } + + if (op.getNumOperands() == 0) { + llvm::outs() << "[neura-interpreter] → Output: (void)\n"; + return true; + } + + auto result = valueMap[op.getOperand(0)]; + if (result.isVector) { + + llvm::outs() << "[neura-interpreter] → Output: ["; + for (size_t i = 0; i < result.vectorData.size(); ++i) { + float val = result.predicate ? result.vectorData[i] : 0.0f; + llvm::outs() << llvm::format("%.6f", val); + if (i != result.vectorData.size() - 1) + llvm::outs() << ", "; + } + llvm::outs() << "]\n"; + } else { + float val = result.predicate ? result.value : 0.0f; + llvm::outs() << "[neura-interpreter] → Output: " << llvm::format("%.6f", val) << "\n"; + } + return true; +} + +bool handleFCmpOp(neura::FCmpOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.fcmp:\n"; + } + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.fcmp expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + + 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"; + } + + bool pred = true; + if (op.getNumOperands() > 2) { + auto predData = valueMap[op.getPredicate()]; + pred = predData.predicate && (predData.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); + } else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported comparison type: " << cmpType << "\n"; + } + return false; + } + + bool finalPredicate = lhs.predicate && rhs.predicate && pred; + float resultValue = fcmpResult ? 1.0f : 0.0f; + + PredicatedData result; + result.value = resultValue; + result.predicate = finalPredicate; + result.isVector = 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"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " + << resultValue << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleICmpOp(neura::ICmpOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.icmp:\n"; + } + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.icmp expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getLhs()]; + auto rhs = valueMap[op.getRhs()]; + + 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"; + } + + bool pred = true; + if (op.getNumOperands() > 2) { + auto predData = valueMap[op.getPredicate()]; + pred = predData.predicate && (predData.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predData.value + << ", [pred = " << predData.predicate << "]\n"; + } + } + + int64_t s_lhs = static_cast(std::round(lhs.value)); + int64_t s_rhs = static_cast(std::round(rhs.value)); + + auto signed_to_unsigned = [](int64_t val) { + return val >= 0 ? + static_cast(val) : + static_cast(UINT64_MAX + val + 1); + }; + + uint64_t u_lhs = signed_to_unsigned(s_lhs); + uint64_t u_rhs = signed_to_unsigned(s_rhs); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Evaluation\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Signed values : LHS = " << s_lhs + << ", RHS = " << s_rhs << "\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Unsigned values : LHS = " << u_lhs + << ", RHS = " << u_rhs << "\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Comparison type : " << op.getCmpType() << "\n"; + } + + bool icmp_result = false; + StringRef cmp_type = op.getCmpType(); + + if (cmp_type == "eq") { + icmp_result = (s_lhs == s_rhs); + } else if (cmp_type == "ne") { + icmp_result = (s_lhs != s_rhs); + } else if (cmp_type.starts_with("s")) { + if (cmp_type == "slt") icmp_result = (s_lhs < s_rhs); + else if (cmp_type == "sle") icmp_result = (s_lhs <= s_rhs); + else if (cmp_type == "sgt") icmp_result = (s_lhs > s_rhs); + else if (cmp_type == "sge") icmp_result = (s_lhs >= s_rhs); + else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported signed comparison type: " << cmp_type << "\n"; + } + return false; + } + } 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); + else if (cmp_type == "uge") icmp_result = (u_lhs >= u_rhs); + else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported unsigned comparison type: " << cmp_type << "\n"; + } + return false; + } + } else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported comparison type: " << cmp_type << "\n"; + } + return false; + } + + bool finalPredicate = lhs.predicate && rhs.predicate && pred; + float resultValue = icmp_result ? 1.0f : 0.0f; + + PredicatedData result; + result.value = resultValue; + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Comparison result : " << (icmp_result ? "true" : "false") << "\n"; + llvm::outs() << "[neura-interpreter] └─ Result : value = " << resultValue + << ", [pred = " << finalPredicate << "]\n"; + } + + + valueMap[op.getResult()] = result; + return true; +} + +bool handleOrOp(neura::OrOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.or:\n"; + } + + if (op.getNumOperands() < 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.or expects at least two operands\n"; + } + return false; + } + + auto lhs = valueMap[op.getOperand(0)]; + auto rhs = valueMap[op.getOperand(1)]; + + if (lhs.isVector || rhs.isVector) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.or requires scalar operands\n"; + } + return false; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operands \n"; + llvm::outs() << "[neura-interpreter] │ ├─ LHS : value = " << lhs.value << ", [pred = " << lhs.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ RHS : value = " << rhs.value << ", [pred = " << rhs.predicate << "]\n"; + } + + int64_t lhsInt = static_cast(std::round(lhs.value)); + int64_t rhsInt = static_cast(std::round(rhs.value)); + int64_t resultInt = lhsInt | rhsInt; + + bool finalPredicate = 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"; + } + + 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"; + } + + PredicatedData result; + result.value = static_cast(resultInt); + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value + << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[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()]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.not:\n"; + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Input : value = " << input.value + << ", [pred = " << input.predicate << "]\n"; + } + + int64_t inputInt = static_cast(std::round(input.value)); + int64_t resultInt = ~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() << "\n"; + } + + + PredicatedData result; + result.value = static_cast(resultInt); + result.predicate = input.predicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value + << ", [pred = " << result.predicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleSelOp(neura::SelOp op, llvm::DenseMap &valueMap) { + 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"; + } + 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; + + 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] ├─ Evaluation \n"; + } + + PredicatedData result; + if (condValue) { + result.value = ifTrue.value; + result.predicate = ifTrue.predicate && cond.predicate; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Condition is true, selecting 'ifTrue' branch\n"; + } + } else { + result.value = ifFalse.value; + result.predicate = ifFalse.predicate && cond.predicate; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Condition is false, selecting 'ifFalse' branch\n"; + } + } + + result.isVector = ifTrue.isVector && ifFalse.isVector; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result : value = " << result.value + << ", predicate = " << result.predicate << "\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleCastOp(neura::CastOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.cast:\n"; + } + if (op.getNumOperands() < 1 || op.getNumOperands() > 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.cast expects 1 or 2 operands\n"; + } + return false; + } + + auto input = valueMap[op.getOperand(0)]; + std::string castType = op.getCastType().str(); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Input : value = " + << input.value << ", [pred = " << input.predicate << "]\n"; + } + + bool finalPredicate = input.predicate; + if (op.getOperation()->getNumOperands() > 1) { + auto predOperand = valueMap[op.getOperand(1)]; + finalPredicate = finalPredicate && predOperand.predicate && (predOperand.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Execution Context\n"; + llvm::outs() << "[neura-interpreter] │ └─ Pred : value = " << predOperand.value + << ", [pred = " << predOperand.predicate << "]\n"; + } + } + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Cast type : " << castType << "\n"; + } + + float resultValue = 0.0f; + auto inputType = op.getOperand(0).getType(); + + if (castType == "f2i") { + if (!inputType.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); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Converting float to integer " + << input.value << " -> " << intValue << "\n"; + } + + } else if (castType == "i2f") { + if (!inputType.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); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Converting integer to float " + << intValue << " -> " << resultValue << "\n"; + } + } else if (castType == "bool2i" || castType == "bool2f") { + if (!inputType.isInteger(1)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Cast type '" << castType + << "' requires i1 (boolean) input\n"; + } + return false; + } + bool boolValue = (input.value != 0.0f); + resultValue = boolValue ? 1.0f : 0.0f; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Converting boolean to number " + << (boolValue ? "true" : "false") << " -> " << resultValue << "\n"; + } + } else if (castType == "i2bool" || castType == "f2bool") { + if (!inputType.isInteger() && !inputType.isF32()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Cast type '" << castType + << "' requires integer or f32 input\n"; + } + return false; + } + bool boolValue = (input.value != 0.0f); + resultValue = boolValue ? 1.0f : 0.0f; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] │ └─ Converting number to boolean " + << input.value << " -> " << (boolValue ? "true" : "false") << " (stored as " << resultValue << ")\n"; + } + } else { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Unsupported cast type: " << castType << "\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 + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Load [addr = " << addr << "] => val = " + << val << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[op.getResult()] = { val, finalPredicate }; + 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) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.gep:\n"; + } + + if (op.getOperation()->getNumOperands() < 1) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.gep expects at least 1 operand (base address)\n"; + } + return false; + } + + auto baseVal = valueMap[op.getOperand(0)]; + size_t baseAddr = static_cast(baseVal.value); + bool finalPredicate = baseVal.predicate; + + if(isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Base address: value = " << baseAddr << ", [pred = " << baseVal.predicate << "]\n"; + } + + unsigned numOperands = op.getOperation()->getNumOperands(); + bool hasPredicate = false; + unsigned indexCount = numOperands - 1; + + if (numOperands > 1) { + auto lastOperandType = op.getOperand(numOperands - 1).getType(); + if (lastOperandType.isInteger(1)) { + hasPredicate = true; + indexCount -= 1; + } + } + + auto stridesAttr = op->getAttrOfType("strides"); + if (!stridesAttr) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.gep requires 'strides' attribute\n"; + } + return false; + } + + std::vector strides; + for (auto s : stridesAttr) { + auto intAttr = mlir::dyn_cast(s); + if (!intAttr) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ Invalid type in 'strides' attribute (expected integer)\n"; + } + return false; + } + strides.push_back(static_cast(intAttr.getInt())); + } + + if (indexCount != strides.size()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ GEP index count (" << indexCount + << ") mismatch with strides size (" << strides.size() << ")\n"; + } + return false; + } + + size_t offset = 0; + for (unsigned i = 0; i < indexCount; ++i) { + auto idxVal = valueMap[op.getOperand(i + 1)]; + if (!idxVal.predicate) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ GEP index " << i << " has false predicate\n"; + } + return false; + } + + size_t idx = static_cast(idxVal.value); + offset += idx * strides[i]; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Index " << i << ": value = " << idx << ", stride = " << strides[i] + << ", cumulative offset = " << offset << "\n"; + } + } + + if (hasPredicate) { + auto predVal = valueMap[op.getOperand(numOperands - 1)]; + finalPredicate = finalPredicate && predVal.predicate && (predVal.value != 0.0f); + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Predicate operand: value = " << predVal.value + << ", [pred = " << predVal.predicate << "]\n"; + } + } + + size_t finalAddr = baseAddr + offset; + + PredicatedData result; + result.value = static_cast(finalAddr); + result.predicate = finalPredicate; + result.isVector = false; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Final GEP result: base = " << baseAddr << ", total offset = " << offset + << ", final address = " << finalAddr + << ", [pred = " << finalPredicate << "]\n"; + } + + valueMap[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) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.br:\n"; + } + + Block *destBlock = op.getDest(); + if (!destBlock) { + 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()); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Block Information\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Current block : Block@" << currentBlock << "\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"; + else + llvm::outs() << "[neura-interpreter] │ │ └─ [" << i << "] Block@" << succBlocks[i] << "\n"; + } + llvm::outs() << "[neura-interpreter] │ └─ Target block : Block@" << destBlock << "\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++; + // } + + const auto &args = op.getArgs(); + const auto &destParams = destBlock->getArguments(); + + if (args.size() != destParams.size()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.br: Argument count mismatch (passed " + << args.size() << ", target expects " << destParams.size() << ")\n"; + } + return false; + } + + + for (size_t i = 0; i < args.size(); ++i) { + Value destParam = destParams[i]; + Value srcArg = args[i]; + + if (!valueMap.count(srcArg)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.br: Argument " << i + << " (source value) not found in value map\n"; + } + return false; + } + + valueMap[destParam] = valueMap[srcArg]; + if (isVerboseMode() && i < destParams.size() - 1) { + llvm::outs() << "[neura-interpreter] │ ├─ Param[" << i << "]: value = " + << valueMap[srcArg].value << "\n"; + } else if (isVerboseMode() && i == destParams.size() - 1) { + llvm::outs() << "[neura-interpreter] │ └─ Param[" << i << "]: value = " + << valueMap[srcArg].value << "\n"; + } + } + + lastVisitedBlock = currentBlock; + currentBlock = destBlock; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Control Transfer\n"; + llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@ " << destBlock << "\n"; + } + return true; +} + +bool handleCondBrOp(neura::CondBr op, llvm::DenseMap &valueMap, + Block *¤tBlock, Block *&lastVisitedBlock) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.cond_br:\n"; + } + + if (op.getNumOperands() < 1 || op.getNumOperands() > 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.cond_br expects 1 or 2 operands (condition + optional predicate)\n"; + } + return false; + } + + auto condValue = op.getCondition(); + if (!valueMap.count(condValue)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ cond_br: condition value not found in valueMap! (SSA name missing)\n"; + } + return false; + } + auto condData = valueMap[op.getCondition()]; + + if (!op.getCondition().getType().isInteger(1)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.cond_br: condition must be of type i1 (boolean)\n"; + } + return false; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Condition : value = " << condData.value + << ", [pred = " << condData.predicate << "]\n"; + } + + bool finalPredicate = condData.predicate; + if (op.getNumOperands() > 1) { + auto predData = valueMap[op.getPredicate()]; + finalPredicate = finalPredicate && predData.predicate && (predData.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] ├─ Current block: Block@" << currentBlock << "\n"; + + auto currentSuccsRange = currentBlock->getSuccessors(); + std::vector succBlocks(currentSuccsRange.begin(), currentSuccsRange.end()); + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Block Information\n"; + llvm::outs() << "[neura-interpreter] │ └─ Current block : Block@" << currentBlock << "\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"; + else + llvm::outs() << "[neura-interpreter] │ └─ False block : Block@" << succBlocks[i] << "\n"; + } + } + + if (!finalPredicate) { + 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++; + // } + + 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"; + } + + + if (branchArgs.size() != targetParams.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"; + } + return false; + } + + if (isVerboseMode()) { + if (!branchArgs.empty()) { + 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) + llvm::outs() << "[neura-interpreter] │ ├─ param[" << i << "]: value = " + << valueMap[branchArgs[i]].value << "\n"; + else { + llvm::outs() << "[neura-interpreter] │ └─ param[" << i << "]: value = " + << valueMap[branchArgs[i]].value << "\n"; + } + } + } + + + + lastVisitedBlock = currentBlock; + currentBlock = targetBlock; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Control Transfer\n"; + llvm::outs() << "[neura-interpreter] └─ Jump successfully to Block@" << targetBlock << "\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"; + } + + auto predecessorsRange = currentBlock->getPredecessors(); + std::vector predecessors(predecessorsRange.begin(), predecessorsRange.end()); + size_t predCount = predecessors.size(); + + if (predCount == 0) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Current block has no predecessors\n"; + } + return false; + } + + size_t predIndex = 0; + bool found = false; + for (auto pred : predecessors) { + if (pred == lastVisitedBlock) { + found = true; + break; + } + ++predIndex; + } + + if (!found) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Last visited block not found in predecessors\n"; + } + return false; + } + + auto inputs = op.getInputs(); + size_t inputCount = inputs.size(); + + if (inputCount != predCount) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Input count (" << inputCount + << ") != predecessor count (" << predCount << ")\n"; + } + return false; + } + + if (predIndex >= inputCount) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Invalid predecessor index (" << predIndex << ")\n"; + } + return false; + } + + Value inputVal = inputs[predIndex]; + if (!valueMap.count(inputVal)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.phi: Input value not found in value map\n"; + } + return false; + } + + PredicatedData inputData = valueMap[inputVal]; + valueMap[op.getResult()] = inputData; + + 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]; + } else { + llvm::outs() << "[neura-interpreter] │ └─ [" << i << "]: " << "Block@" << predecessors[i]; + } + if (i == predIndex) { + llvm::outs() << " (→ current path)\n"; + } else { + llvm::outs() << "\n"; + } + } + llvm::outs() << "[neura-interpreter] └─ Result : " << op.getResult() << "\n"; + llvm::outs() << "[neura-interpreter] └─ Value : " << inputData.value + << ", [Pred = " << inputData.predicate << "]\n"; + } + return true; +} + +bool handleReserveOp(neura::ReserveOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.reserve:\n"; + } + + PredicatedData placeholder; + placeholder.value = 0.0f; + placeholder.predicate = false; + placeholder.isReserve = true; + + Value result = op.getResult(); + valueMap[result] = placeholder; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Created placeholder : " << result << "\n"; + llvm::outs() << "[neura-interpreter] ├─ Initial value : 0.0f\n"; + llvm::outs() << "[neura-interpreter] ├─ Initial predicate : false\n"; + 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"; + } + + Value source = op.getValue(); + Value target = op.getTarget(); + + if (!valueMap.count(source)) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Source value not found in value map\n"; + } + return false; + } + + if (!valueMap.count(target) || !valueMap[target].isReserve) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: Target is not a reserve placeholder\n"; + } + return false; + } + + const auto &sourceData = valueMap[source]; + auto &targetData = valueMap[target]; + + if (source.getType() != target.getType()) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.ctrl_mov: 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"; + } + + targetData.value = sourceData.value; + targetData.predicate = sourceData.predicate; + targetData.isVector = sourceData.isVector; + if (sourceData.isVector) { + targetData.vectorData = sourceData.vectorData; + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Updated target placeholder\n"; + llvm::outs() << "[neura-interpreter] └─ value = " << targetData.value + << ", [pred = " << targetData.predicate << "]\n"; + } + + return true; +} + +bool handleNeuraReturnOp(neura::ReturnOp op, llvm::DenseMap &valueMap) { + 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"; + return false; + } + returnValues.push_back(valueMap[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() << llvm::format("%.6f", val); + if (j != data.vectorData.size() - 1) llvm::outs() << ", "; + } + if (isVerboseMode()) { + llvm::outs() << "]"; + } + } else { + float val = data.predicate ? data.value : 0.0f; + if (isVerboseMode()) { + llvm::outs() << llvm::format("%.6f", val); + } + } + if (isVerboseMode()) { + llvm::outs() << ", [pred = " << data.predicate << "]\n"; + } + } + } + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Execution terminated successfully\n"; + } + + return true; +} + +bool handleGrantPredicateOp(neura::GrantPredicateOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.grant_predicate:\n"; + } + + if (op.getOperation()->getNumOperands() != 2) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.grant_predicate expects exactly 2 operands (value, new_predicate)\n"; + } + return false; + } + + auto source = valueMap[op.getValue()]; + auto newPred = valueMap[op.getPredicate()]; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ ├─ Source: value = " << source.value + << ", [pred = " << source.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] │ └─ New predicate: value = " << newPred.value + << ", [pred = " << newPred.predicate << "]\n"; + } + + bool resultPredicate = source.predicate && newPred.predicate && (newPred.value != 0.0f); + + PredicatedData result = source; + result.predicate = resultPredicate; + result.isVector = source.isVector; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result: value = " << result.value + << ", [pred = " << resultPredicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleGrantOnceOp(neura::GrantOnceOp op, llvm::DenseMap &valueMap) { + if(isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.grant_once:\n"; + } + + if (op.getOperation()->getNumOperands() != 1) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.grant_once expects exactly 1 operand (value)\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"; + } + + + static llvm::DenseMap granted; + bool hasGranted = granted[op.getValue()]; + + bool resultPredicate = !hasGranted; + if (!hasGranted) { + granted[op.getValue()] = true; + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ First access - granting predicate\n"; + } + } else { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Subsequent access - denying predicate\n"; + } + } + + PredicatedData result = source; + result.predicate = resultPredicate; + result.isVector = source.isVector; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] └─ Result: value = " << result.value + << ", [pred = " << resultPredicate << "]\n"; + } + + valueMap[op.getResult()] = result; + return true; +} + +bool handleGrantAlwaysOp(neura::GrantAlwaysOp op, llvm::DenseMap &valueMap) { + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] Executing neura.grant_always:\n"; + } + + if (op.getOperation()->getNumOperands() != 1) { + if (isVerboseMode()) { + llvm::errs() << "[neura-interpreter] └─ neura.grant_always expects exactly 1 operand (value)\n"; + } + return false; + } + + auto source = valueMap[op.getValue()]; + bool resultPredicate = true; + PredicatedData result = source; + result.predicate = resultPredicate; + result.isVector = source.isVector; + + if (isVerboseMode()) { + llvm::outs() << "[neura-interpreter] ├─ Operand\n"; + llvm::outs() << "[neura-interpreter] │ └─ Source value: " << source.value << ", [pred = " << source.predicate << "]\n"; + llvm::outs() << "[neura-interpreter] ├─ Granting predicate unconditionally\n"; + llvm::outs() << "[neura-interpreter] └─ Result: value = " << result.value + << ", [pred = " << resultPredicate << "]\n"; + } + + + valueMap[op.getResult()] = result; + return true; +} + int main(int argc, char **argv) { + + for (int i = 0; i < argc; ++i) { + if (std::string(argv[i]) == "--verbose") { + setVerboseMode(true); + } + } + if (argc < 2) { - llvm::errs() << "Usage: neura-interpreter \n"; + llvm::errs() << "[neura-interpreter] Usage: neura-interpreter [--verbose]\n"; return 1; } @@ -40,7 +2212,7 @@ int main(int argc, char **argv) { llvm::SourceMgr sourceMgr; auto fileOrErr = mlir::openInputFile(argv[1]); if (!fileOrErr) { - llvm::errs() << "Error opening file\n"; + llvm::errs() << "[neura-interpreter] Error opening file\n"; return 1; } @@ -48,84 +2220,135 @@ int main(int argc, char **argv) { OwningOpRef module = parseSourceFile(sourceMgr, &context); if (!module) { - llvm::errs() << "Failed to parse MLIR input file\n"; + llvm::errs() << "[neura-interpreter] Failed to parse MLIR input file\n"; return 1; } // Changes map to store PredicatedData instead of just float. llvm::DenseMap valueMap; + Memory mem(1024); // 1MB + + Block *currentBlock = nullptr; + Block *lastVisitedBlock = nullptr; + size_t opIndex = 0; + bool isTerminated = false; + for (auto func : module->getOps()) { - Block &block = func.getBody().front(); + currentBlock = &func.getBody().front(); + opIndex = 0; + isTerminated = false; + + while(!isTerminated && currentBlock) { + auto& operations = currentBlock->getOperations(); + if(opIndex >= operations.size()) { + break; + } - for (Operation &op : block.getOperations()) { + Operation &op = *std::next(operations.begin(), opIndex); if (auto constOp = dyn_cast(op)) { - auto attr = constOp.getValue(); - PredicatedData val{0.0f, true}; // arith constants always have true predicate - - if (auto floatAttr = llvm::dyn_cast(attr)) { - val.value = floatAttr.getValueAsDouble(); - } else if (auto intAttr = llvm::dyn_cast(attr)) { - val.value = static_cast(intAttr.getInt()); - } else { - llvm::errs() << "Unsupported constant type in arith.constant\n"; - return 1; - } - - valueMap[constOp.getResult()] = val; + if(!handleArithConstantOp(constOp, valueMap)) return 1; + ++opIndex; } else if (auto constOp = dyn_cast(op)) { - auto attr = constOp.getValue(); - // Initializes PredicatedData with default values. - PredicatedData val{0.0f, true}; - - // Handles value attribute. - if (auto floatAttr = llvm::dyn_cast(attr)) { - val.value = floatAttr.getValueAsDouble(); - } else if (auto intAttr = llvm::dyn_cast(attr)) { - val.value = static_cast(intAttr.getInt()); - } else { - llvm::errs() << "Unsupported constant type in neura.constant\n"; - return 1; - } - - // Tries getting predicate attribute. - if (auto predAttr = constOp->getAttrOfType("predicate")) { - val.predicate = predAttr.getValue(); - } - - valueMap[constOp.getResult()] = val; - + 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)) { - auto lhs = valueMap[faddOp.getLhs()]; - auto rhs = valueMap[faddOp.getRhs()]; - - // Always performs addition, but combines predicate. - PredicatedData result; - result.value = lhs.value + rhs.value; - result.predicate = lhs.predicate && rhs.predicate; - - valueMap[faddOp.getResult()] = result; + if(!handleFAddOp(faddOp, valueMap)) return 1; + ++opIndex; } else if (auto fsubOp = dyn_cast(op)) { - auto lhs = valueMap[fsubOp.getLhs()]; - auto rhs = valueMap[fsubOp.getRhs()]; - - // Always performs addition, but combines predicate. - PredicatedData result; - result.value = lhs.value - rhs.value; - result.predicate = lhs.predicate && rhs.predicate; - valueMap[fsubOp.getResult()] = result; + 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)) { - auto result = valueMap[retOp.getOperand(0)]; - llvm::outs() << "[neura-interpreter] Output: " << llvm::format("%.6f", result.value); - if (!result.predicate) { - llvm::outs() << " (predicate=false)"; - } - llvm::outs() << "\n"; + 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() << "Unhandled op: "; + llvm::errs() << "[neura-interpreter] Unhandled op: "; op.print(llvm::errs()); llvm::errs() << "\n"; return 1;