Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is_const_eval intrinsic #64683

Closed
wants to merge 2 commits into from
Closed

Conversation

gnzlbg
Copy link
Contributor

@gnzlbg gnzlbg commented Sep 22, 2019

Right now, users often need to pick between writing code in a const fn friendly way that can be executed at compile-time, or run-time performance (e.g. code that uses core::arch intrinsics, inline assembly, etc.). See rust-lang/const-eval#7.

The unsafe core::intrinsic::is_const_eval returns true if it is evaluated at compile-time and false otherwise, allowing users to write different code for the different execution contexts of const fns.

There is a PR for this in miri upstream: rust-lang/miri#959

@rust-highfive
Copy link
Collaborator

r? @eddyb

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive
Copy link
Collaborator

⚠️ Warning ⚠️

  • These commits modify submodules.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 22, 2019
@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

r? @oli-obk

@rust-highfive rust-highfive assigned oli-obk and unassigned eddyb Sep 22, 2019
@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

cc @Centril

@gnzlbg gnzlbg changed the title Add is_const_eval intrinsic [WIP] Add is_const_eval intrinsic Sep 22, 2019
@rust-highfive
Copy link
Collaborator

Your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2019-09-22T09:45:48.2992367Z ##[command]git remote add origin https://github.com/rust-lang/rust
2019-09-22T09:45:48.3184987Z ##[command]git config gc.auto 0
2019-09-22T09:45:48.3264500Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2019-09-22T09:45:48.3323230Z ##[command]git config --get-all http.proxy
2019-09-22T09:45:48.3476823Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/64683/merge:refs/remotes/pull/64683/merge

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@rust-highfive
Copy link
Collaborator

Your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2019-09-22T09:56:26.9589278Z ##[command]git remote add origin https://github.com/rust-lang/rust
2019-09-22T09:56:26.9780309Z ##[command]git config gc.auto 0
2019-09-22T09:56:26.9861098Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2019-09-22T09:56:26.9912585Z ##[command]git config --get-all http.proxy
2019-09-22T09:56:27.0046185Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/64683/merge:refs/remotes/pull/64683/merge

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@rust-highfive
Copy link
Collaborator

Your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2019-09-22T10:24:18.3109467Z ##[command]git remote add origin https://github.com/rust-lang/rust
2019-09-22T10:24:18.3295560Z ##[command]git config gc.auto 0
2019-09-22T10:24:18.3368343Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2019-09-22T10:24:18.3417740Z ##[command]git config --get-all http.proxy
2019-09-22T10:24:18.3546999Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/64683/merge:refs/remotes/pull/64683/merge

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

.gitmodules Outdated Show resolved Hide resolved
src/libcore/intrinsics.rs Outdated Show resolved Hide resolved
@rust-highfive
Copy link
Collaborator

Your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2019-09-22T10:44:46.6574294Z ##[command]git remote add origin https://github.com/rust-lang/rust
2019-09-22T10:44:46.6776859Z ##[command]git config gc.auto 0
2019-09-22T10:44:46.6848050Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2019-09-22T10:44:46.6905598Z ##[command]git config --get-all http.proxy
2019-09-22T10:44:46.7050793Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/64683/merge:refs/remotes/pull/64683/merge

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2019

So.. can you undo the submodule things? It's not going to break miri and we can just merge the miri PR independently

The PR lgtm now, but I'll let @RalfJung and especially @Centril raise any blockers.

This intrinsic could now be used in stable const fn as far as I can tell, which is quite dangerous, because it would create forward compat hazards, where we can't take the decision back without perf regressions due to having to remove SIMD stuff from const fns

Also, this PR will not actually enable anyone to do SIMD stuff, because const qualif will still forbid it.

Previous discussions: rust-lang/const-eval#7

From a const qualif perspective I'd prefer a "write two function bodies for the same function" approach. Essentially unsafe overloading on the constness of a function. I have no idea what the syntax would be, but it can just be compiler magic attributes if we just want it for SIMD.

That said, if we want the proposed intrinsic, the way the PR looks now is how it would be

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

So.. can you undo the submodule things? It's not going to break miri and we can just merge the miri PR independently

Without upgrading miri here to a version that contains those changes, one of the tests I've added should fail :/

This intrinsic could now be used in stable const fn as far as I can tell, which is quite dangerous,

To clarify, it could be used within libcore/liballoc/libstd on const fns that are exposed to stable Rust, but stable Rust - don't do that! It cannot be directly used in stable Rust because it requires the core_intrinsics feature enabled.

Also, this PR will not actually enable anyone to do SIMD stuff, because const qualif will still forbid it.

I intend to use this with -Zunleash to avoid crashing miri there in some cases. As the next iteration of packed_simd evolves I intend to start implementing the "portable" subset of the SIMD intrinsics in miri (~20 intrinsics). The intent is to allow all of packed_simd to be used within const fn on nightly at some point.

From a const qualif perspective I'd prefer a "write two function bodies for the same function" approach. Essentially unsafe overloading on the constness of a function. I have no idea what the syntax would be, but it can just be compiler magic attributes if we just want it for SIMD.

Right now we are kind of at the step "let's see if libstd can do something useful with this", and for that, an unstable intrinsic feels like the simplest solution to the problem (no need to add new syntax, etc.).

If we wanted to expose this to users, then there are many ways in which we could do so syntactically, but AFAICT they are all semantically equivalent (they all allow const fns to execute different code at run-time than at compile-time). I think issue rust-lang/const-eval#7 would be good place to discuss that, but I think that at this point it would be better to try to understand what the use cases would be, and for that some experience using it would be helpful.

@Centril
Copy link
Contributor

Centril commented Sep 22, 2019

The PR lgtm now, but I'll let @RalfJung and especially @Centril raise any blockers.

I'm OK with this as long as it is on a zero-commitment experimental basis and miles away from stable Rust (including through stdarch) whether exposed directly or indirectly.

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2019

Miri won't fail. Miri isn't run on tests

In order to guarantee it not being in stable cons fns (I mean in libcore/std/SIMD ) we need to fix our intrinsic story first

@Centril
Copy link
Contributor

Centril commented Sep 22, 2019

In order to guarantee it not being in stable cons fns (I mean in libcore/std/SIMD ) we need to fix our intrinsic story first

👍 -- Should block on fixing that first then?

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2019

Also, as previously stated, this won't actually allow you to implement anything. What you probably want is an intrinsic that takes two function types (the zst ones) as generic arguments and calls one at compile time and the other at runtime. This way we don't have to start making the const qualif checker more complicated

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

I'm OK with this as long as it is on a zero-commitment experimental basis and miles away from stable Rust (including through stdarch) whether exposed directly or indirectly.

FWIW I at least do not intend to use this in stdarch, or at least, not until we have a stable feature for this, or at least an RFC.

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2019

I think the worry isn't about intent, but accident

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

Would a feature-fate just for this intrinsic do? Or what would be the appropriate way to gate this ?

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2019

We need something like #61495 but additionally handling const stability

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 22, 2019

We need something like #61495 but additionally handling const stability

Ah, I remember that issue, but I don't see why fixing it wouldn't suffice. We can just ban this intrinsic from being used in const fn just like we ban assume, etc. and only lift those restrictions when -Zunleash is used, which is something that libcore/libstd don't do.

@gnzlbg gnzlbg changed the title [WIP] Add is_const_eval intrinsic Add is_const_eval intrinsic Sep 22, 2019
use std::intrinsics::is_const_eval;

fn main() {
const X: bool = unsafe { is_const_eval() };
Copy link
Contributor Author

@gnzlbg gnzlbg Sep 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So AFAICT the only remaining issue is that this currently works due to #61495 allowing all intrinsics to run in const-contexts (notice that feature(const_fn) is not enabled!).

Even if we were to fix #61495, this would still "just work" behind the const_fn feature. That would allow it to "silently" be used in APIs exposed by libstd. I'm not sure how to fix that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is fixed as I imagine it, the const fn feature gate won't have an effect on it. It would only permit a whitelist of intrinsics and forbid all others outside unleash mode

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with @oli-obk. Why would just "just fork" with const_fn? That gate won't make non-const-fn callable, and all intrinsics (except for a whitelist) should be considered non-const-fn.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the "why" issue is already resolved in the updated OP description and the new test that shows an use case that works with this, but wouldn't work without it.

let name = &*tcx.item_name(def_id).as_str();
if name == "is_const_eval" {
return Ok(ty::Const::from_bool(tcx, true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this mean that it returns true in Miri, which seems odd (for now, anyway)?

This should query a machine flag to determine the intrinsic's return value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this mean that it returns true in Miri, which seems odd (for now, anyway)?

This returns true in miri. Were it to return false, miri would fail trying to do something it doesn't support.

Copy link
Contributor Author

@gnzlbg gnzlbg Sep 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is odd at all given that miri already provides cfg!(miri) which can be used to the same effect.

This should query a machine flag to determine the intrinsic's return value.

Does cfg!(miri) do this?

EDIT: users that want to do something else for miri can just use cfg(miri) on top of this, instead of making the result of this intrinsic be target dependent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is that miri is more like codegen and not like const eval in its semantics. Cfgs flags are their own world independent of the runtime

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miri is implementing Rust's run-time semantics as much as it can. So is_const_eval must return false.

If you actually want to check "is this a restricted interpreted environment that does not support SIMD", then you should pick an appropriate name for the intrinsic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miri is implementing Rust's run-time semantics as much as it can. So is_const_eval must return false.

I could cfg(miri) is_const_eval to always return false, but then miri won't be able to execute any code for which is_const_eval makes sense using.

@RalfJung
Copy link
Member

I share @Centril's concern. This operation has the chance to break many things about our const-story. It should really be conisdered "unsafe" with the safety contract being "the code must do the same thing no matter the return value".
(EDIT: oh, seems like the doc comments already say that. Nice.)

What worries me most, TBH, is that the PR description does not even state a motivation for this. That should definitely be added before considering to merge the PR.

///
/// Code that uses this intrinsic must be extremely careful to ensure that
/// `const fn`s remain referentially-transparent independently of when they
/// are evaluated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the reference of referential transparency here mostly unhelpful. At least, I would have no idea what exactly const code must (not) do, based on this description.

But also, this does not affect const-qualif. It doesn't actually introduce any referential intransparency. So personally I'd trim this down to "the code must do the same thing for both cases". Which leaves you wonder why this is even added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this is used in C++ to speed up the run-time code with SIMD and whatnot but that both code paths should observably behave same (for some notion of observational equivalence). https://en.cppreference.com/w/cpp/types/is_constant_evaluated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this SIMD code used run-time feature detection, couldn't be "just" make that report "no SIMD support" for const-eval to avoid needing any other magic intrinsic?

Either way though, this should be stated in the PR description.

Copy link
Contributor Author

@gnzlbg gnzlbg Sep 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this SIMD code used run-time feature detection, couldn't be "just" make that report "no SIMD support" for const-eval to avoid needing any other magic intrinsic?

How would that run-time feature detection thing be implemented ? 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that already existed for SIMD anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_x86_feature_detected! has an if cfg!(miri) { false } branch in it, but it is not usable inside const fns. To be able to use the macro in const fns we'd need something like this, the macro uses inline assembly internally..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that run-time feature detection thing be implemented ? wink

Not sure, but I don't think we need this intrinsic for it? (And if we do, the name should be is_miri.)

Is the problem just that you cannot do if in const fn? We are accumulating more and more bad hacks to work around that and I'd rather put the line down before we add intrinsics for this reason. You can for example already do

#[cfg(miri)]
const fn is_miri() { true }
#[cfg(not(miri))]
const fn is_miri() { false }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you have understood what the problem is here.

is_x86_feature_detected! needs to use inline assembly at run-time. If you want to call that from const fns, either const fns need to support inline assembly, or you need to provide a way to do something else at compile time.

If you want to "properly" emulate is_x86_feature_detected on miri then miri would need to support inline assembly. Right now, we just have a #[cfg(miri)] return false; that does nothing on miri instead. That could be improved with some "hook" that miri provides for doing run-time feature detection, that calls some function from the "miri run-time" that is implemented using inline assembly or similar. This has nothing to do with the problem being discussed here though, which is how to call is_x86_feature_detected! from a const fn, or more generally, how to be able to call efficient run-time code that uses "stuff that constant evaluation probably won't ever support" like inline assembly by providing an alternative slower implementation for constant evaluation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I completely misunderstood const fns scope and the plan is indeed to actually support inline assembly in const fns at some point, that is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I completely misunderstood const fns scope and the plan is indeed to actually support inline assembly in const fns at some point, that is.

😱: https://github.com/CraneStation/cranelift/issues/444#issuecomment-531574394

@RalfJung
Copy link
Member

RalfJung commented Sep 23, 2019

We can just ban this intrinsic from being used in const fn just like we ban assume, etc. and only lift those restrictions when -Zunleash is used, which is something that libcore/libstd don't do.

Yeah, we should do that IMO. But #61495 is basically about intrinsics not being actually banned right now in const fn, even when they should...
I also don't think that issue is complicated to fix? The only complication is that there's other stuff going on in const qualif at the same time -- but IMO that just means #61495 should be fixed earlier rather than later.

Basically, the only thing that protects us from disaster (all intrinsics being const-callable) right now is that no intrinsics are actually callable on stable, except for transmute, and that one is (I think) handled properly. But that only protects us from disaster on stable, not in libstd. But even "bad" intrinsics generally will just ICE when used because they are not implemented (AFAIK we don't currently carry intrinsic implementations in rustc itself that are controversial). But here, we are adding an intrinsic impl to the CTFE engine that we do not want even unstable code to use without -Zunleash. This, I think, requires #61495, which should thus block this PR.

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Sep 23, 2019

But that only protects us from disaster on stable, not in libstd. But even "bad" intrinsics generally will just ICE when used because they are not implemented (AFAIK we don't currently carry intrinsic implementations in rustc itself that are controversial). But here, we are adding an intrinsic impl to the CTFE engine that we do not want even unstable code to use without -Zunleash.

Can we ban a feature gate from being used in libstd ?

You propose above that the libstd run-time detection functionality should "do this somehow" instead. That would mean that normal Rust code within libstd would need to be able to detect constant evaluation somehow, and branch on that... So... that code within libstd would need to be able to use this feature, or something like this, so banning this in libstd doesn't appear to be what we want either :/

x[0] == y[0] && x[1] == y[1] && x[2] == y[2] && x[3] == y[3]
} else {
unsafe {
let x = _mm_loadu_si128(&x as *const _ as *const _);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially what I worried about. Such code will never ever be accepted by rustc AFAIK. If you want it for experimentation, that seems fine but ultimately pointless. Writing a const qualification that could accept this would be the motherload of unsoundness dangers. My suggestion with an intrinsic, that calls one of two functions given to it depending on the context, would be much more manageable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long version:

In contrast to C++, we statically prove that your const fn is sane. Of course we also have escape hatches via unsafe, but that's a different story. What your example has is an if condition that works differently whether you are const evaluating or codegenning. Now, this is not a problem by itself, but the problem arises with the else arm's content. That content contains statically provable nonconst code and since it's part of a const fn that will be errored about. I'm presuming the intent is to decide whether to run const checks depending on the arm that one is in. This may seem easy in the given example, but once you involve loops, short circuiting, matching, break and continue, this can get very complex to decide. Additionally even the simple version will complicate the const qualif check a lot, making one of the most fragile things keeping Rust's safety guarantees even more fragile. If there is a less complex version (and I believe there is), we should go for that one.

Copy link
Contributor Author

@gnzlbg gnzlbg Sep 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Yes, the "boolean intrinsic" approach implemented here would require the constant evaluator to only try to prove things about the branches that constant evaluation actually can reach. I can see how that could get nasty.

IIUC, what you are proposing is something like:

mod intrinsics {
    pub fn const_eval_select<T, U, V>(called_in_const: T, called_at_rt: U) -> V
        where T: const Fn() -> V, U: Fn() -> V;
}

that can be used to rewrite the example above as follows:

const fn eq_ct(x: [i32; 4], y: [i32; 4]) -> bool {
    x[0] == y[0] && x[1] == y[1] && x[2] == y[2] && x[3] == y[3]
}

fn eq_rt(x: [i32; 4], y: [i32; 4]) -> bool {
    let x = _mm_loadu_si128(&x as *const _ as *const _);
    let y = _mm_loadu_si128(&y as *const _ as *const _);
    let r = _mm_cmpeq_epi32(x, y);
    let r = _mm_movemask_ps(transmute(r) );
    r == 0b1111
}

const fn eq(x: [i32; 4], y: [i32; 4]) -> bool {
    const_eval_select(|| eq_ct(x, y), || eq_rt(x, y)) 
    // unclear to me how to make these closures work well here
}

?

Is that correct? Maybe you could post about it in the tracking issue (rust-lang/const-eval#7) ? Your last comment there appeared to be in agreement with using a "boolean intrinsic", but it does have quite a bit of issues, so maybe we can move the discussion there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While closures would be neat, they seem an unnecessary complication of such a low level function

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

you could then use it as

const fn eq_ct((x, y): ([i32; 4], [i32; 4])) -> bool {
    x[0] == y[0] && x[1] == y[1] && x[2] == y[2] && x[3] == y[3]
}

fn eq_rt((x, y): ([i32; 4], [i32; 4])) -> bool {
    let x = _mm_loadu_si128(&x as *const _ as *const _);
    let y = _mm_loadu_si128(&y as *const _ as *const _);
    let r = _mm_cmpeq_epi32(x, y);
    let r = _mm_movemask_ps(transmute(r) );
    r == 0b1111
}

const fn eq(x: [i32; 4], y: [i32; 4]) -> bool {
    const_eval_select((x, y), eq_ct, eq_rt)
    // unclear to me how to make these closures work well here
}

The const_eval_select function then compiles down to a call to eq_rt(arg) in llvm and is interpreted as a call to eq_ct(arg) in const eval

Copy link
Contributor Author

@gnzlbg gnzlbg Oct 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds great.

So how do I implement this? IIUC, I just add an intrinsic to typeck like I did here, and implement it with the "const semantics" in const-eval, and then add a different implementation for it in the llvm backend, and that's it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the intrinsic checks would report errors on any illegal uses, but I don't know how easy that is to do

I can't imagine this is feasible, we'd need to analyze the "runtime code" and figure out if it does something non-const friendly, but we can't really tell much about that because the point of this feature is to be able to call SIMD intrinsics or inline assembly, which is essentially stuf we cannot reason about.

I'd be fine with saying that this intrinsic is unsafe to use, and making the user responsible for using it correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's not what I meant. I just mean that

  • the signature of the two functions needs to match,
  • the first function needs to be const fn,
  • the return type of both functions is the same as the generic RET parameter
  • the argument of both functions is the same type as the ARG parameter

The content of the functions is indeed left to the user as per the docs you already provided

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, gotcha. Hm, what about just saying:

mod intrinsics {
    pub fn const_eval_select<ARG, F, G, RET>(
        arg: ARG called_in_const: F, called_at_rt: G
    ) -> RET 
    where F: FnOnce(ARG) -> RET, G: FnOnce(ARG) -> RET
}

?

That way, typeck enforces that the arguments and return types match, and well, that they are function types. I don't know how to check that the first function is const in typeck though, but maybe it is possible to do during const qualif.

Copy link
Member

@RalfJung RalfJung Oct 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we wouldn't go for closures, to keep things simple? Then it would just be

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

Copy link
Contributor

@oli-obk oli-obk Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function pointers are not the same thing as the zst arguments and won't do us any favours here. The intrinsic would not be able to figure out which function to call and would have to insert function pointer calls and we can't have const function pointers. While all solvable problems, I'd rather not go down that road

I mean you can add Fn bounds as you suggestd, that should suffice indeed.

that they are function types

That's not checked, it could be a closure. Thus it needs an extra check to make sure only functions are used as generic arguments

@bors
Copy link
Contributor

bors commented Sep 25, 2019

☔ The latest upstream changes (presumably #64766) made this pull request unmergeable. Please resolve the merge conflicts.

@JohnCSimon
Copy link
Member

Ping from triage.
This PR has sat idle for the last 10 days -
@gnzlbg @oli-obk Can you please address the merge conflict?

Thank you!

@RalfJung
Copy link
Member

RalfJung commented Oct 10, 2019

(Moving to top-level as this is becoming a high-level discussion, not a comment related to some particular part of this PR.)

I don't think you have understood what the problem is here.

That might be related to the fact that you never properly explained what the problem is. ;) As @oli-obk explained, for the problem you seem to want to solve ("const-qualif rejects my inline assembly" is my current guess, please correct if I am wrong), the intrinsic is of no use. So when you talk about "allowing users to write different code for the different execution contexts of const fns" I naturally assumed you are talking about the dynamic part only, ignoring the checks, but at that point I think we can have better solutions. Now it seems like you do care about the checks, so the PR description is misleading (and maybe I was just assuming too much about how familiar everyone is with the architecture of this).

Also notice that for the Miri interpreter, inline assembly in dead code is no problem. And yet we talked about Miri here. I feel like there are at least 2, maybe more, issues being conflated in this PR.

So maybe start with a clear problem statement and let's go from there? Here's what I think I gathered from the conversation so far:

  • One issue is "const fn can't have inline assembly in their body" (so we need some improvement to the static analysis to accept more code without sacrificing const safety). The intrinsic does not help here.
  • Another one is "Miri and CTFE cannot execute inline assembly" (so we need some improvement to the dynamic semantics such that constrained execution environments can pick a fall-back implementation). The intrinsic could help here but I think it is a crude way to achieve this, and given that we already have a dynamic test to check for SIMD capabilities I think we should re-use that. In particular this issue, while related to the first, is not at all specific to CTFE but also applies to Miri, so "is_const" is not a good name for whatever solves it.

@oli-obk's intrinsic with two closures (basically a user-defined if) could solve the first issue (it could be used inside the SIMD detection to make it return false when run in Miri [CTFE Miri or stand-alone Miri]). But for the second issue it doesn't really help I feel -- it "solves" it but only crudely and unergonomically.
Currently I imagine people write things like

if have_simd() { do_simd() } else { do_fallback() }

And this would basically have to turn into

if_miri(
  || do_fallback(),
  || if have_simd() { do_simd() } else { do_fallback() } // not const-checked
)

So, we have code duplication and the fact that have_simd is const fn does not even help.

A different solution might be to make have_simd an intrinsic (or somehow special magic otherwise) and make const-qualif understand that if have_simd() { ... } has dead code it need not analyze. At least that avoids the ergonomic hit in the example above, though it dosn't scale to user-defined checks. I am not sure if there is a reasonable way to support those safely.

Isn't there also cfg_feature_enabled! (quoting from this RFC)? Maybe it would make most sense to make const-qualif understand that so that code like

fn foo() {
    if cfg_feature_enabled!("avx") {
      unsafe { foo_avx() } // not const-checked
    } else if cfg_feature_enabled!("sse4") {
      unsafe { foo_sse4() } // not const-checked
    } else {
      foo_impl() // use the default version
    }
}

can work.

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Oct 10, 2019

The problem is explained in rust-lang/const-eval#7, and this PR implements the solution proposed by @oli-obk in their comment there: rust-lang/const-eval#7 (comment)

The other issues that @oli-obk raised here are new, and were not raised there.

Also notice that for the Miri interpreter, inline assembly in dead code is no problem.

Without a way to detect miri, the inline assembly wouldn't be in the dead code.

Now it seems like you do care about the checks, so the PR description is misleading (and maybe I was just assuming too much about how familiar everyone is with the architecture of this).

I would prefer the C++ solution, where Rust performs the const fns checks lazily, but @oli-obk wants to leave the door open for those checks to be performed eagerly, which is fair. This PR doesn't disable the checks, but you can manually disable the checks by passing -Zunleash-miri-... which works for me.

@bjorn3
Copy link
Member

bjorn3 commented Oct 10, 2019

Isn't there also cfg_feature_enabled! (quoting from this RFC)? Maybe it would make most sense to make const-qualif understand that so that code like

That is is_x86_feature_detected and friends. The problem with that is they always check for if enabled target features are enabled at compile time first (using cfg!) which may be true even when running with miri.

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Oct 10, 2019

The problem with that is they always check for if enabled target features are enabled at compile time first (using cfg!) which would may be true even when running with miri.

EDIT (see next comment): When running with miri, cfg(miri) is currently used there to report all features as disabled. That can be problematic in other ways though.

@gnzlbg
Copy link
Contributor Author

gnzlbg commented Oct 10, 2019

@bjorn3 you are right, if a feature is enabled at compile-time, is_x86_feature_detected! reports true under miri - the only thing cfg(miri) is used for is to avoid executing inline assembly when it would otherwise execute at run-time.

Code that wants to run under miri would need to have cfg(miri) on top of is_x86_feature_detected to avoid using intrinsics that are enabled at compile-time, so probably we should remove that fix.

@lu-zero
Copy link
Contributor

lu-zero commented Oct 10, 2019

The easiest way out it is to have miri disable all the features. (as in -Zunleash-miri-... -> all features disappears)

@wirelessringo
Copy link

Ping from triage. @oli-obk any updates on this? Thanks.

@oli-obk oli-obk added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 19, 2019
@JohnCSimon
Copy link
Member

Ping from triage.
@gnzlbg This PR has sat idle for the last few days. Can you please address the merge conflicts?
CC @oli-obk
Thank you!

@JohnCSimon JohnCSimon added the S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. label Nov 2, 2019
@JohnCSimon
Copy link
Member

Ping from triage.
Unfortunately, I'm going to close this one out as inactive.
Feel free to re-open this PR at any time when you're ready to move forward.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.