Skip to content

Support detecting const-eval vs non-const-eval in a const fn #7

Open
@oli-obk

Description

@oli-obk
Contributor

There are multiple thousand intrinsics for various platforms which users might want to use to speed up their code. It is unrealistic to implement all of these (although a certain subgroup like https://github.com/rust-lang-nursery/stdsimd/blob/05c2f61c384e2097a3a4c648344114fc4ac983be/coresimd/simd_llvm.rs seem to be manageable.

@rkruppe and @gnzlbg mentioned (with discomfort) a way to "runtime" detect whether we're in const eval or runtime (which would get completely optimized during codegen). While this would be trivial to implement, it seems very unfortunate to have to resolve to this.
There's precedent in c++ for such an escape hatch.

I am not very happy that we even need that many intrinsics outside the libstd and don't quite understand why these can't be optimizations that "just happen". Requiring users to "fix" optimizer deficiencies by giving them multiple thousand ways to gain a few cycles seems suboptimal.

It's perfectly fine not to support calling e.g. an interrupt register fiddling intrinsic at compile-time (there's no use for doing that, or it could be a noop in many cases). But when/if performance intrinsics are sprinkled onto code used by many users and there's an algorithm that works both at compile-time and runtime and could be optimized, it seems very unfortunate to scrap compile-time evaluability.

Activity

hanna-kruppe

hanna-kruppe commented on Aug 30, 2018

@hanna-kruppe
gnzlbg

gnzlbg commented on Aug 30, 2018

@gnzlbg

There is a solution proposed for C++20 (I am not sure if it has been merged - EDIT: It was accepted LEWG and sent to LWG and CWG) called is_constant_evaluated (latest paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0595r1.html) that attacks this problem.

Having to choose between whether code can be const evaluated or whether it performs good at run-time is a weird spot to be in as a programmer. I don't know whether this problem is worth solving, but the problem is real. The C++ solution, doesn't feel right, but I don't have any alternatives either.

oli-obk

oli-obk commented on Aug 30, 2018

@oli-obk
ContributorAuthor

@gnzlbg gave us a great summary on irc. Here's the gist:

  • while it moved from LEWG to CWG and LWG, people had mixed feelings about it
    • basically, std library writers want it,
    • and compiler writers wanted to expose it as an extension
  • everybody agreed that it shouldn't be a widely used feature, and that it is hard to reason about it
  • it was also argued when exactly should it return true
    • const FOO: i32 = foo(); it will return true
    • but what about let foo: i32 = foo(); ?
    • should it also return true if foo() is const propagated by optimizatons ?
    • will that depend on compiler-optimizations ?
      • so there was some agreement that it should not, and that it should be clear what it returns for every program
  • they changed the name from if constexpr() { ... } to if std::is_constant_evaluated() to discourage its use
  • clang/LLVM main devs argued strongly on its favour
oli-obk

oli-obk commented on May 24, 2019

@oli-obk
ContributorAuthor

Note that we can "just" add such a std::is_constant_evaluated function and make it unsafe to make it clear what's going on. The unsafe documentation would then state that the result of this function may not change the behaviour of any code. There could even be an additional -Z flag for fuzzers and testing that turns the flag to true during regular compilation

gnzlbg

gnzlbg commented on May 24, 2019

@gnzlbg

When miri evaluates Rust program like this:

fn bar(x: i32) -> i32 { x * 2 }
const fn foo(x: i32) -> i32 {
    if unsafe { is_constant_evaluated() } {
        x * 2
    } else {
        // unsafe required because `bar` is 
        // not a `const fn` (hence not `const`-safe):
        unsafe { bar(x) }
    }
}

it could attempt to evaluate both branches, and compare that both produce the same result, which is the only effect that both branches can have. If the bar branch attempts to mutate global memory, read from a static, etc. miri could error.

oli-obk

oli-obk commented on Jan 20, 2020

@oli-obk
ContributorAuthor

We have changed course. Discussion is in https://github.com/rust-lang/rust/pull/64683/files/223c832b3894fe6ce6d61d4f459f0aa827bec264#discussion_r327153517

The TLDR is that we'd create an intrinsic

pub fn const_eval_select<ARG, F, G, RET>(arg: ARG, called_in_const: F, called_at_rt: G) -> RET;

that calls called_in_const(arg) if we're in a const eval context and called_at_rt(arg) if not. F and G must be function types (not function pointers).

minerva

minerva commented on Jan 20, 2020

@minerva

When miri evaluates Rust program like this:

fn bar(x: i32) -> i32 { x * 2 }
const fn foo(x: i32) -> i32 {
    if unsafe { is_constant_evaluated() } {
        x * 2
    } else {
        // unsafe required because `bar` is 
        // not a `const fn` (hence not `const`-safe):
        unsafe { bar(x) }
    }
}

it could attempt to evaluate both branches, and compare that both produce the same result, which is the only effect that both branches can have. If the bar branch attempts to mutate global memory, read from a static, etc. miri could error.

How about a language-level if const { .. }? I think C++20 demonstrates why a library function to handle this job in conjunction with a regular if is a bad solution.

const fn foo(x: i32) -> i32 {
    if const {
        x * 2
    } else {
        unsafe { bar(x) }
    }
}

Similar to how ISO NB comments tried to change C++20's if (std::is_constant_evaluated()) { .. } to
if consteval { .. }.

oli-obk

oli-obk commented on Jan 20, 2020

@oli-obk
ContributorAuthor

I think we race-conditioned on posting. See my above post for a solution that is much simpler to implement, since it doesn't pollute the const part of a function with the non-const part.

24 remaining items

Loading
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @tarcieri@RalfJung@oli-obk@gnzlbg@hanna-kruppe

        Issue actions

          Support detecting const-eval vs non-const-eval in a `const fn` · Issue #7 · rust-lang/const-eval