[Solved] Comments and whitespace changing program behavior

Hi all!

I've run into a weird situation where changing non-code portions of my app (add a comment, change white space, etc.) change the application's behavior. It's a d3d11 app on Windows 10 (an older Dell XPS15) using nightly or stable. I've distilled everything down to a single file that reproduces the issue on my machine. There are three outcomes when running cargo run (or even cargo clean && cargo run):

  1. The expected scene running at approximately the target FPS;
  2. The expected scene running at approximately 4 FPS;
  3. A white window running at approximately the target FPS.

If I update the file in a way that causes a recompilation without actually changing the code, the behavior randomly changes. I cannot see a clear pattern as if I have it running with a stable FPS, add a comment which changes the behavior, deleting the comment doesn't reset it back to the stable state. I can reproduce it using the stable compiler as well as building in release.

Here is the output of rustup show:

Default host: x86_64-pc-windows-msvc
rustup home: C:\Users\xxxxxxxxxx\.rustup

installed toolchains

nightly-x86_64-pc-windows-msvc (default)

active toolchain

nightly-x86_64-pc-windows-msvc (default)
rustc 1.70.0-nightly (2eaeb1eee 2023-04-05)

I have tried:

  • cargo clean && cargo build;
  • Deleting Cargo.lock and letting the dependencies update;
  • Comment out different parts of the d3d11 calls;
  • Copying to another folder and running it from there;
  • Turning incremental builds on and off;
  • Distilling the application down to a single file.

I pulled the sample app together quickly, opting to write most of it by hand to see if I could reproduce the issue (thus this isn't an exercise in architectural review).

What are your thoughts? I'm completely lost at this point!

Update: I filed a GitHub issue as suggested by @quinedot.

1 Like

I'd open a bug report on GitHub. (Please link it here if you do.)

If you always clean before building and running after making one of the changes, do you still get one of the weird behaviors?

If not, that might indicate it's an incremental compilation issue.

Ah, I meant to add that I tried turning off incremental builds and still experienced it. (I'll update my post.) Yes, after a clean I still experience one of the three behaviors. I thought it might be an incremental build issue but it doesn't seem to be.

1 Like

Good call, I'll do that.

You have a fair amount of unsafe code in that file. It might be that your program contains undefined behaviour and the difference in behaviour you see is because LLVM made a slightly different decision when generating machine code, and that decision causes weird things to happen.


I bet that's undefined behaviour. Now, you use a lot of unsafe winapi functions, and I'm not familiar enough with them to check whether you use them correctly. You should carefully consult the documentation.

But one thing which springs out is that you pass a lot of & and &mut to your unsafe functions. Safe references in Rust come with strong compiler guarantees.

  • &mut T must be the unique pointer to T for all its lifetime.
  • The pointee of &T must not be mutated, for as long as the reference lives (exception: areas of memory located in UnsafeCell may be mutated under a &).

It is likely that those rules are violated in your code. I see that the functions in winapi crate have both safe references and unsafe pointers in their signatures. One can only assume that if the function takes a raw pointer, that's because it plans to use it in a way incompatible with Rust's aliasing rules.

I suggest using ptr::add_of_mut! instead of &mut references where possible. Note that ptr::addr_of! has an extra guarantee that the referent isn't mutated (like &), which makes it more error-prone to use with untrusted C code, even if that code claims to take pointers to const values.

1 Like

D'oh, that's probably it. Time to go back to the documentation and reason about aliasing.

Thanks so much! I will see if I can reason about this a bit more to see if I can track down where that may be happening. I appreciate the advice!

Well, I think I found the issue, for anybody who may be interested. I can't seem to reproduce the original issue which stemmed from the joys of programming. (This took me back!)

I decided to run cargo clippy to see if anything interesting would pop up. I saw this message, which didn't make sense:

warning: casting function pointer `mem::size_of::<cgmath::Matrix4<f32>>` to `u32`, which truncate
s the value
   --> src\main.rs:413:24
413 |             ByteWidth: mem::size_of::<cgmath::Matrix4<f32>> as _,
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `mem::size_of::
<cgmath::Matrix4<f32>> as usize`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.ht
    = note: `#[warn(clippy::fn_to_numeric_cast_with_truncation)]` on by default

Here's the full code:

let constant_buffer_desc = D3D11_BUFFER_DESC {
    ByteWidth: mem::size_of::<cgmath::Matrix4<f32>> as _,
    Usage: D3D11_USAGE_DYNAMIC,
    CPUAccessFlags: D3D11_CPU_ACCESS_WRITE,

If you look closely, I was telling d3d11 that my constant buffer is the size of a function pointer--not the sizse of a 4x4 f32 matrix. D'oh! I think this qualifies as my first "I missed a semicolon"-like issue in a long time. So, everybody was right that it was UB and even a race condition--overwriting memory you don't own is a Bad Thing™.

Thanks all for your help and for giving me a chance to put on display my inability to write code!


This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.