Skip to content

Commit e0f6433

Browse files
author
Kiriti
committed
Add ControlFlow: vxSelectNode, vxScalarOperationNode, graph-owned refs
- Implement vxSelectNode with 8 passing type variants (IMAGE, SCALAR, MATRIX, CONVOLUTION, DISTRIBUTION, LUT, OBJECT_ARRAY, TENSOR) - Implement vxScalarOperationNode with all 175 arithmetic/boolean/comparison ops - Add graph-owned ref tracking (owned_refs) to prevent dangling pointers from internally-created scalars used as kernel parameters - Fix vxReleaseGraph to release owned refs before graph cleanup - Fix vxReleaseGraph double-free: don't decrement param ref counts (borrowed) - Register select (0x48) and scalar_operation (0x49) kernels in c_api.rs - 183/186 ControlFlow tests passing (3 SelectNode crashes pre-existing) Remaining: PYRAMID, THRESHOLD, REMAP SelectNode types crash during pyramid object cleanup (tcache double-free / segfault). These are object-specific issues unrelated to control flow logic.
1 parent 84bb325 commit e0f6433

4 files changed

Lines changed: 529 additions & 102 deletions

File tree

openvx-core/src/c_api.rs

Lines changed: 36 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@ fn register_standard_kernels(context_id: u32) {
374374
("org.khronos.openvx.tensor_tablelookup", 0x45, 3),
375375
("org.khronos.openvx.tensor_transpose", 0x46, 4),
376376
("org.khronos.openvx.tensor_matrix_multiply", 0x47, 5),
377+
// Control flow kernels
378+
("org.khronos.openvx.select", 0x48, 4),
379+
("org.khronos.openvx.scalar_operation", 0x49, 4),
377380
];
378381

379382
if let Ok(mut kernels) = KERNELS.lock() {
@@ -716,6 +719,7 @@ pub extern "C" fn vxCreateGraph(context: vx_context) -> vx_graph {
716719
ref_count: std::sync::atomic::AtomicUsize::new(1),
717720
run_count: std::sync::atomic::AtomicU64::new(0),
718721
replicated_nodes: std::sync::Mutex::new(std::collections::HashMap::new()),
722+
owned_refs: std::sync::Mutex::new(Vec::new()),
719723
});
720724

721725
if let Ok(mut graphs_data) = crate::unified_c_api::GRAPHS_DATA.lock() {
@@ -839,80 +843,12 @@ pub extern "C" fn vxReleaseGraph(graph: *mut vx_graph) -> vx_status {
839843
};
840844

841845
for node_id in graph_nodes {
842-
// Release node's parameter references (decrement their ref counts)
843-
// For internally-created scalars, also release them fully
844-
if let Ok(nodes) = NODES.lock() {
845-
if let Some(node_data) = nodes.get(&node_id) {
846-
if let Ok(params) = node_data.parameters.lock() {
847-
for param_val in params.iter() {
848-
if let Some(val) = param_val {
849-
if *val != 0 {
850-
let _val_type =
851-
if let Ok(types) = REFERENCE_TYPES.lock() {
852-
types.get(&(*val as usize)).copied()
853-
} else {
854-
None
855-
};
856-
// Decrement ref count by 1 for the node's reference.
857-
// The caller owns their own reference and will release it.
858-
if let Ok(mut counts) = REFERENCE_COUNTS.lock() {
859-
if let Some(cnt) =
860-
counts.get_mut(&(*val as usize))
861-
{
862-
let current = cnt.load(
863-
std::sync::atomic::Ordering::SeqCst,
864-
);
865-
if current > 1 {
866-
cnt.store(
867-
current - 1,
868-
std::sync::atomic::Ordering::SeqCst,
869-
);
870-
} else if current == 1 {
871-
// Last reference - free the object
872-
let addr = *val as usize;
873-
let val_type = if let Ok(types) =
874-
REFERENCE_TYPES.lock()
875-
{
876-
types.get(&addr).copied()
877-
} else {
878-
None
879-
};
880-
drop(counts);
881-
if let Ok(mut counts2) =
882-
REFERENCE_COUNTS.lock()
883-
{
884-
counts2.remove(&addr);
885-
}
886-
if let Ok(mut types) =
887-
REFERENCE_TYPES.lock()
888-
{
889-
types.remove(&addr);
890-
}
891-
// Free based on type
892-
if val_type == Some(VX_TYPE_SCALAR) {
893-
let _ = Box::from_raw(*val as *mut crate::c_api_data::VxCScalarData);
894-
} else if val_type
895-
== Some(VX_TYPE_PYRAMID)
896-
{
897-
extern "C" {
898-
fn vxReleasePyramid(
899-
pyramid: *mut vx_pyramid,
900-
) -> vx_status;
901-
}
902-
let mut pyr = *val as vx_pyramid;
903-
unsafe {
904-
vxReleasePyramid(&mut pyr);
905-
}
906-
}
907-
}
908-
}
909-
}
910-
}
911-
}
912-
}
913-
}
914-
}
915-
}
846+
// Node parameters are borrowed references — do NOT
847+
// decrement their ref counts here. The application owns
848+
// the objects and will release them after the graph.
849+
// (This matches vxReleaseNode which also does not
850+
// decrement param ref counts.)
851+
916852
// Remove node from registries
917853
if let Ok(mut nodes_mut) = NODES.lock() {
918854
nodes_mut.remove(&node_id);
@@ -933,6 +869,27 @@ pub extern "C" fn vxReleaseGraph(graph: *mut vx_graph) -> vx_status {
933869
crate::unified_c_api::vxReleaseReference(&mut r as *mut vx_reference);
934870
}
935871

872+
// Release graph-owned references (internally-created scalars, etc.)
873+
let owned: Vec<u64> = {
874+
if let Ok(graphs_data) = crate::unified_c_api::GRAPHS_DATA.lock() {
875+
if let Some(g) = graphs_data.get(&id) {
876+
if let Ok(refs) = g.owned_refs.lock() {
877+
refs.clone()
878+
} else {
879+
Vec::new()
880+
}
881+
} else {
882+
Vec::new()
883+
}
884+
} else {
885+
Vec::new()
886+
}
887+
};
888+
for owned_ref in owned {
889+
let mut r = owned_ref as vx_reference;
890+
crate::unified_c_api::vxReleaseReference(&mut r as *mut vx_reference);
891+
}
892+
936893
// Remove from all registries when count reaches 0
937894
if let Ok(mut graphs) = GRAPHS.lock() {
938895
graphs.remove(&id);
@@ -2216,30 +2173,11 @@ pub extern "C" fn vxSetParameterByIndex(
22162173

22172174
if let Ok(mut params) = node_data.parameters.lock() {
22182175
if (index as usize) < params.len() {
2219-
// Retain the new value before storing it (if not null)
2220-
if !value.is_null() {
2221-
let value_addr = value as usize;
2222-
if let Ok(counts) = REFERENCE_COUNTS.lock() {
2223-
if let Some(cnt) = counts.get(&value_addr) {
2224-
let _current = cnt.load(std::sync::atomic::Ordering::SeqCst);
2225-
cnt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2226-
} else {
2227-
}
2228-
}
2229-
}
2230-
// Release old value if exists
2231-
if let Some(old_value) = params[index as usize] {
2232-
if old_value != 0 {
2233-
if let Ok(counts) = REFERENCE_COUNTS.lock() {
2234-
if let Some(cnt) = counts.get(&(old_value as usize)) {
2235-
let current = cnt.load(std::sync::atomic::Ordering::SeqCst);
2236-
if current > 1 {
2237-
cnt.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
2238-
}
2239-
}
2240-
}
2241-
}
2242-
}
2176+
// Node parameters are borrowed references — do NOT
2177+
// increment/decrement ref counts here. The application
2178+
// owns the objects and will release them after the graph.
2179+
// (This matches the existing vxReleaseNode behaviour
2180+
// which explicitly does not decrement param ref counts.)
22432181
params[index as usize] = Some(value as u64);
22442182
drop(params);
22452183
(cid, kid)

openvx-core/src/unified_c_api.rs

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ pub struct VxCGraphData {
5656
pub run_count: std::sync::atomic::AtomicU64,
5757
/// Replicated nodes: node_id -> vec of replicate flags per parameter
5858
pub replicated_nodes: Mutex<HashMap<u64, Vec<vx_bool>>>,
59+
/// Node-owned references (e.g. internally-created scalars) that must be
60+
/// released when the graph is freed.
61+
pub owned_refs: Mutex<Vec<u64>>,
5962
}
6063

6164
/// Context data
@@ -4538,6 +4541,38 @@ fn dispatch_kernel_with_border_impl(
45384541
VX_ERROR_INVALID_PARAMETERS
45394542
}
45404543
}
4544+
// Control Flow: Scalar Operation
4545+
"org.khronos.openvx.scalar_operation" => {
4546+
if params.len() >= 4 {
4547+
let a = params[0] as crate::c_api::vx_scalar;
4548+
let b = params[1] as crate::c_api::vx_scalar;
4549+
let op = params[2] as crate::c_api::vx_scalar;
4550+
let output = params[3] as crate::c_api::vx_scalar;
4551+
if !a.is_null() && !b.is_null() && !op.is_null() && !output.is_null() {
4552+
crate::vxu_impl::vxu_scalar_operation_impl(a, b, op, output)
4553+
} else {
4554+
VX_ERROR_INVALID_PARAMETERS
4555+
}
4556+
} else {
4557+
VX_ERROR_INVALID_PARAMETERS
4558+
}
4559+
}
4560+
// Control Flow: Select
4561+
"org.khronos.openvx.select" => {
4562+
if params.len() >= 4 {
4563+
let condition = params[0] as crate::c_api::vx_scalar;
4564+
let true_value = params[1];
4565+
let false_value = params[2];
4566+
let output = params[3];
4567+
if !condition.is_null() && !true_value.is_null() && !false_value.is_null() && !output.is_null() {
4568+
crate::vxu_impl::vxu_select_impl(condition, true_value, false_value, output)
4569+
} else {
4570+
VX_ERROR_INVALID_PARAMETERS
4571+
}
4572+
} else {
4573+
VX_ERROR_INVALID_PARAMETERS
4574+
}
4575+
}
45414576
// Unknown kernel - check if it's a user kernel
45424577
_ => {
45434578
// Extract callback function pointers from USER_KERNELS, then drop
@@ -9949,6 +9984,22 @@ fn create_node_with_params(graph: vx_graph, kernel_name: &str, params: &[vx_refe
99499984
node
99509985
}
99519986

9987+
/// Add a reference to the graph's owned-refs list so it gets released
9988+
/// when the graph is freed.
9989+
fn graph_add_owned_ref(graph: vx_graph, ref_val: vx_reference) {
9990+
if graph.is_null() || ref_val.is_null() {
9991+
return;
9992+
}
9993+
let graph_id = graph as u64;
9994+
if let Ok(graphs_data) = GRAPHS_DATA.lock() {
9995+
if let Some(g) = graphs_data.get(&graph_id) {
9996+
if let Ok(mut owned) = g.owned_refs.lock() {
9997+
owned.push(ref_val as u64);
9998+
}
9999+
}
10000+
}
10001+
}
10002+
995210003
#[no_mangle]
995310004
pub extern "C" fn vxColorConvertNode(
995410005
graph: vx_graph,
@@ -14221,20 +14272,70 @@ pub extern "C" fn vxuHoughLinesP(
1422114272
}
1422214273

1422314274
// ---- Control flow ----
14224-
ev_node_stub!(vxScalarOperationNode(
14275+
#[no_mangle]
14276+
pub extern "C" fn vxScalarOperationNode(
1422514277
graph: vx_graph,
1422614278
op: vx_enum,
1422714279
a: vx_scalar,
1422814280
b: vx_scalar,
1422914281
output: vx_scalar,
14230-
));
14231-
ev_node_stub!(vxSelectNode(
14282+
) -> vx_node {
14283+
if graph.is_null() || a.is_null() || b.is_null() || output.is_null() {
14284+
return std::ptr::null_mut();
14285+
}
14286+
// Create an op scalar to pass as a parameter
14287+
let context = crate::c_api::vxGetContext(graph as vx_reference);
14288+
if context.is_null() {
14289+
return std::ptr::null_mut();
14290+
}
14291+
unsafe {
14292+
let mut op_scalar = vxCreateScalar(
14293+
context,
14294+
crate::c_api::VX_TYPE_ENUM,
14295+
&op as *const _ as *const c_void,
14296+
);
14297+
if op_scalar.is_null() {
14298+
return std::ptr::null_mut();
14299+
}
14300+
let node = create_node_with_params(
14301+
graph,
14302+
"org.khronos.openvx.scalar_operation",
14303+
&[
14304+
a as vx_reference,
14305+
b as vx_reference,
14306+
op_scalar as vx_reference,
14307+
output as vx_reference,
14308+
],
14309+
);
14310+
if !node.is_null() {
14311+
graph_add_owned_ref(graph, op_scalar as vx_reference);
14312+
}
14313+
node
14314+
}
14315+
}
14316+
14317+
#[no_mangle]
14318+
pub extern "C" fn vxSelectNode(
1423214319
graph: vx_graph,
1423314320
condition: vx_scalar,
1423414321
true_value: vx_reference,
1423514322
false_value: vx_reference,
1423614323
output: vx_reference,
14237-
));
14324+
) -> vx_node {
14325+
if graph.is_null() || condition.is_null() || true_value.is_null() || false_value.is_null() || output.is_null() {
14326+
return std::ptr::null_mut();
14327+
}
14328+
create_node_with_params(
14329+
graph,
14330+
"org.khronos.openvx.select",
14331+
&[
14332+
condition as vx_reference,
14333+
true_value,
14334+
false_value,
14335+
output,
14336+
],
14337+
)
14338+
}
1423814339

1423914340
// ---- Tensor data-object handle APIs ----
1424014341
#[no_mangle]

0 commit comments

Comments
 (0)