Edit 1:
When I had written this post I shortened the original code for better presentation. This however also inadvertently introduced a code error in the external library call foo
.
I edited the code to fix the rather obvious mistake regarding the handover of a Vec<T>
as the community has already correctly pointed out.
Question
How and why does a println
or dbg
line of code change my runtime behaviour as illustrated? And of more importance: How can I fix my code, such that it works?
I am sincerely sorry for the long post, but I could not leave out any information as it may play a vital role in finding a solution.
Background information
Rust is running as a dynamic system library ([crate_type = "cdylib"]
) within a Delphi program. The Delphi program runs within a single process, no other threads are spawned. This process calls Rust, handing over a data object holding a triangle mesh as well as some other parameters. When the line described below is processed, Delphi throws an Invalid_Floating_Point
exception.
I have already tested that the handover is interpreted correctly, i.e. I am able to interpret the mesh object as a mesh as well as other parameters handed over.
Note that, if I run the code with a rust example with excately the same setup, the runtime error does not occur.
Rust Code
Call Stack
FFI function:
#[no_mangle]
pub unsafe extern "C" fn foo(
target: &mut *mut Supports,
mesh: *const Mesh
space: f32) -> i32
{
mesh = unsafe { &*mesh };
let rays: Vec<Ray> = generate_rays_from_mesh(&mesh, space);
if let Some(supports) = generate_target(&mesh, rays) {
*target = Box::into_raw(supports);
0
} else {
1
}
}
The mesh stores a BVH for faster processing. generate_target
calls the BVH's get_items
function. The function intends to find all triangles, which potentially intersect with the ray:
pub fn generate_target(mesh: &Mesh, rays: Vec<Ray>) -> Supports {
...
for ray in rays{
let triangles = mesh.bvh.get_items(ray);
}
}
The BVH tree redirects the get_items
call to the root node of the BVH. As the root node is of type Node::Internal
, Node
being an enum with variants (Internal, Leaf)
, we dive into the implementation of struct Node
. Note that get_items
calls itself recursively until we find a leaf node. As the error occurs yet within the first call of Node
get_items
I omit the code for the leaf.
impl Node {
fn get_items(&self, ray: &Ray) -> Vec<u32> {
match self {
Node::Internal(internal) => {
if intersects_aabb(&ray, &internal.aabb) {
let (mut a, b) = rayon::join(
|| internal.lo.get_items(obj),
|| internal.hi.get_items(obj),
);
a.extend(b.into_iter());
a
} else {
Vec::new()
}
}
Node::Leaf(leaf) => {
...
}
}
}
}
As you can see, the line with the if
-statement gets called before the function calls itself recursively. Within this if
-statement we dive deeper into the calling stack, as the error occurs here:
#[inline]
pub fn intersects_aabb(ray: &Ray, aabb: &AABB3<f32>) -> Option<f32> {
let mut hit_val = 0.;
let mut max_val = None;
for i in 0..3 {
let s = ray.pos[i];
let d = ray.dir[i];
let min = aabb.min[i];
let max = aabb.max[i];
if relative_eq!(d, 0.0) {
if s < min || max < s {
return None;
}
} else {
let mut t2 = (max - s) / d;
let mut t1 = (min - s) / d;
if t1 > t2 {
mem::swap(&mut t1, &mut t2);
}
hit_val = hit_val.max(t1);
if let Some(m) = max_val {
max_val = ::nalgebra::partial_min(&m, &t2).cloned();
} else {
max_val = Some(t2);
}
if let Some(m) = max_val {
if hit_val > m {
return None;
}
}
}
}
Some(hit_val)
}
Error-throwing part
While debugging, I cannot go beyond the first line of the code shown below.
However, if I debug the values needed for the calculation, all values seem fine, d is not zero as would have already been catched by the if
block above these lines.
...
let mut t2 = (max - s) / d;
let mut t1 = (min - s) / d;
...
Changing the function and its result
When I change the function as follows,
let mut t1 = (min - s) / d;
dbg!(&t1);
let mut t1 = (max - s) / d;
dbg!(&t2);
the DLL call does not produce a runtime error.