Skip to content

Inconsistent behavior when comparing function pointers in release #54685

Closed

Description

@repnop

Playground link: https://play.rust-lang.org/?gist=62d362e2bf72001bd3f3c4a3ed0c842c&version=stable&mode=release&edition=2015

Switching to debug causes it to print both addresses twice (expected behavior), but in release both functions are optimized into one, so x and y are given the same address. @CryZe took a look at the assembly, and LLVM removed the first comparison (and evaluated it to a constant false), but not the second, which then evaluates to true. This is very reminiscent of undefined behavior in C and C++.

Thanks to @memoryruins for sending the original link which allowed me to discover this.

fn foo(i: i32) -> i32 {
    1
}
fn bar(i: i32) -> i32 {
    1
}

fn main() {
    let x: fn(i32) -> i32 = foo;
    let y: fn(i32) -> i32 = bar;
    
    if x == y {
        println!("true")
    } else {
        println!("{:?}, {:?}", x, y);
    }

    if x == y {
        println!("true")
    } else {
        println!("{:?}, {:?}", x, y);
    }
}

Debug output:

0x562a7b399600, 0x562a7b399620
0x562a7b399600, 0x562a7b399620

Release output:

0x560d1e3a1310, 0x560d1e3a1310
true

stable rustc 1.29.1 (b801ae6 2018-09-20)

Activity

  1. repnop commented on Sep 30, 2018

    @repnop
    ContributorAuthor

    Also worth noting (pointed out by @memoryruins) that MIRI throws an error in the comparison saying it dereferenced a function pointer.

  2. ishitatsuyuki commented on Sep 30, 2018

    @ishitatsuyuki
    Contributor

    We run mergefunc pass on release builds. Anyway, can you explain why this is a problem? Rust does not make any guarantee on pointer values.

  3. CryZe commented on Sep 30, 2018

    @CryZe
    Contributor

    It seems like LLVM starts comparing the pointer of bar to bar instead of comparing bar to foo. This very much looks like LLVM is messing up in some way.
    https://cdn.discordapp.com/attachments/273539705595756544/495820067091513347/unknown.png
    https://i.imgur.com/dvWMTjE.png

    Unless LLVM is allowed to break function pointers entirely.

    Actually that may be what mergefunc is doing.

  4. Havvy commented on Sep 30, 2018

    @Havvy
    Contributor

    @ishitatsuyuki The problem is that they shouldn't produce an x == y expression that is false in one statement and then true in the next. They should either always be equal or never be equal, but this code snippet shows that this is not always true.

    @rep-nop Can you include the source code from the playground and the compilation results (under release and debug) into your opening comment?

  5. eddyb commented on Sep 30, 2018

    @eddyb
    Contributor

    (came here from #54695)

    According to @rkruppe, this could be caused by constant-folding &a == &b to false in LLVM (when a and b are different globals).
    IMO this optimization shouldn't apply to functions and/or any unnamed_addr globals.

    cc @rust-lang/compiler

  6. VictorKoenders commented on Sep 30, 2018

    @VictorKoenders

    Insanity Is Doing the Same Thing Over and Over Again and Expecting Different Results

  7. strega-nil commented on Sep 30, 2018

    @strega-nil
    Contributor

    This is definitely a bug in LLVM, afaict - this expression should never be true in C++. It may be that they never set unnamed_addr on functions?

  8. VictorKoenders commented on Sep 30, 2018

    @VictorKoenders
    • Still happens with no_mangle
    • Still happens with pub
    • Still happens with extern "C"
  9. hanna-kruppe commented on Sep 30, 2018

    @hanna-kruppe
    Contributor

    @ubsan Yeah Clang doesn't add unnamed_addr anywhere as aggressively as rustc, and unnamed_addr is the one that allows completely merging the functions (which is required for the comparison to return true).

  10. strega-nil commented on Sep 30, 2018

    @strega-nil
    Contributor

    @rkruppe seems like someone added an optimization that assumes that functions are always distinct, even when unnamed_addr. We might want to open a bug on LLVM?

  11. hanna-kruppe commented on Sep 30, 2018

    @hanna-kruppe
    Contributor

    I don't see why this would be an LLVM bug. The observed behavior seems perfectly in line with the meaning of unnamed_addr: the address is not significant, so address comparisons can evaluate either way. If a frontend does not want that, it shouldn't add unnamed_addr to functions whose address is observed. (Not saying that's what rustc should do, if it came down to it I would argue for the current behavior, just describing what would probably be a reasonable position of LLVM developers.)

  12. RalfJung commented on Oct 1, 2018

    @RalfJung
    Member

    Oh this is beautiful. ;) Finally we have a real-life example of pointer comparison being non-deterministic. It's not at all how I expected that example to look, though...

    Non-determinism does not mean that we have UB, so I do not think there is a huge problem here. But non-deterministic comparison is certainly surprising. It means, for example, that the PartialEq implementation for function pointers does not satisfy the PartialEq contract, which is something we should keep in mind.

    I suppose we could also have an analysis to see which functions have their address taken, and not set unnamed_addr for them?

  13. arielb1 commented on Oct 1, 2018

    @arielb1
    Contributor

    @RalfJung

    What would be a problem would be if LLVM also assumes that equality is deterministic and duplicates 2 occurrences of the comparison.

  14. hanna-kruppe commented on Oct 1, 2018

    @hanna-kruppe
    Contributor

    @arielb1

    What would be a problem would be if LLVM also assumes that equality is deterministic and duplicates 2 occurrences of the comparison.

    That is a very good point. I don't think there is anything preventing that, and in principle that could lead to unsoundness. However, this problem applies to all comparisons that might be non-deterministic, right? So "just don't use unnamed_addr" wouldn't solve it.

    @RalfJung

    I suppose we could also have an analysis to see which functions have their address taken, and not set unnamed_addr for them?

    LLVM already does that, or more precisely, it adds unnamed_addr to functions that are guaranteed to not have their address taken. This is only knowable for internal functions, so it's a significant downgrade from the status quo in non-LTO builds. It also can't distinguish e.g. between only putting a function in a vtable (which is not accessible to users and only ever used for calling) and reifying a function pointer in user code. The latter could be solved by an analysis in rustc, but the former is a fundamental limitation of separate compilation.

  15. RalfJung commented on Oct 1, 2018

    @RalfJung
    Member

    @arielb1 Ah right, forgot about that... so, is it UB under LLVM IR to take the address of a function tagged unnamed_addr?

    However, this problem applies to all comparisons that might be non-deterministic, right?

    True, but it is not clear if there are any. I cannot give a satisfactory definition of LLVM semantics that makes ptr comparison deterministic, but I also was unable to find an example that would demonstrate that it is not deterministic.

  16. 16 remaining items

  17. added
    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    C-bugCategory: This is a bug.
    on Jan 27, 2019
  18. added 2 commits that reference this issue on Mar 30, 2020
  19. tmiasko commented on May 23, 2021

    @tmiasko
    Contributor

    This issue was fixed in https://reviews.llvm.org/D87123

  20. added
    E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.
    on May 24, 2021
  21. nikomatsakis commented on May 24, 2021

    @nikomatsakis
    Contributor

    Tagging as "needs test" -- do we have a minimal example?

  22. added 2 commits that reference this issue on Jun 21, 2021
  23. added a commit that references this issue on Jun 22, 2021
  24. added a commit that references this issue on Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-bugCategory: This is a bug.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions