0

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.

Nevsden
  • 99
  • 6
  • 3
    A behavior changed with a print denotes an undefined behavior in your code. – Boiethios Dec 05 '19 at 15:30
  • I may be wrong, but I think that `&mut *mut Supports` is invalid in a C function signature. It should be `*mut *mut Supports`. – Boiethios Dec 05 '19 at 15:32
  • @FrenchBoiethios: From the [Nomicon](https://doc.rust-lang.org/nomicon/ffi.html#interoperability-with-foreign-code): "References can safely be assumed to be non-nullable pointers directly to the type.", but then a caveat explaining that you should prefer raw pointers. – rodrigo Dec 05 '19 at 15:57
  • 2
    @FrenchBoiethios: The `Vec` however, I think it is not FFI safe. – rodrigo Dec 05 '19 at 15:59
  • @rodrigo So I was wrong ^^ Thanks for the link. – Boiethios Dec 05 '19 at 15:59
  • 2
    Does this answer your question? [How to expose a Rust \`Vec\` to FFI?](https://stackoverflow.com/questions/39224904/how-to-expose-a-rust-vect-to-ffi) – Boiethios Dec 05 '19 at 16:00
  • 2
    [`Vec`](https://doc.rust-lang.org/src/alloc/vec.rs.html#295-298) is `#[repr(Rust)]` implicitly, so it has an essentially indeterminate abi (In the eyes of the compiler) and is therefore undefined behavior. Also, to quote the nomicon: "If `T` is an FFI-safe non-nullable pointer type, `Option` is guaranteed to have the same layout and ABI as `T` and is therefore also FFI-safe. As of this writing, this covers `&`, `&mut`, and function pointers, all of which can never be null." So it's best to have an `Option<&T>` as the parameter instead of raw pointers. – Optimistic Peach Dec 06 '19 at 03:17
  • Assuming I constructed the method header of `foo` with a handover of `Vec` your answer would probably explain the undefined behaviour. This however was a mistake I unintentionally introduced whilst writing this post in order to shorten the code. I edited the code to make the FFI call FFI safe. – Nevsden Dec 06 '19 at 09:06
  • I created a workaround for the described problem by using the compiler optimization level 2 instead of 3. After having compiled my code this way let the problem vanish. It needs to be analysed, if the problem still persists with the newest update of rust. – Nevsden Sep 15 '20 at 14:17

0 Answers0