Stream: t-libs/stdarch

Topic: miri and cfg vs is_feature_detected


Luca Barbato (Oct 10 2019 at 13:25, on Zulip):

from here

Luca Barbato (Oct 10 2019 at 13:26, on Zulip):

why you would do this?

Luca Barbato (Oct 10 2019 at 13:26, on Zulip):

and why it would be an UB?

gnzlbg (Oct 10 2019 at 17:16, on Zulip):

it was just an example

gnzlbg (Oct 10 2019 at 17:16, on Zulip):

what i meant is that one can write code like that, that would invoke UB if it were executed

gnzlbg (Oct 10 2019 at 17:17, on Zulip):

but since is_x86_feature_detected! and cfg(target_feature) are "consistent" with each other, such code will never be executed

gnzlbg (Oct 10 2019 at 17:17, on Zulip):

so right now, such UB will never be invoked

gnzlbg (Oct 10 2019 at 17:18, on Zulip):

if we were to change is_x86_..._detected under miri to be inconsistent with cfg(target_feature), then such UB could end up being executed

gnzlbg (Oct 10 2019 at 17:31, on Zulip):

this reminds me of the environment variable to override the macro results

Luca Barbato (Oct 14 2019 at 07:51, on Zulip):

The assumption is that you can always disable a path even if you build such path

Luca Barbato (Oct 14 2019 at 07:53, on Zulip):

the env override is the equivalent of shutting off the simd flag at kernel level or just executing the binary on a different cpu

Luca Barbato (Oct 14 2019 at 07:53, on Zulip):

so if something would be a UB in that case there is a much worse problem than just miri being strange

Luca Barbato (Oct 14 2019 at 07:56, on Zulip):

You may short-circuit is_..._detected! to always return false if cfg(target_feature) is false, but it may and should return true or false if cfg(target_feature) is true.

gnzlbg (Oct 14 2019 at 07:58, on Zulip):

@Luca Barbato if you compile a binary for a CPU that has some feature enable, the behavior is undefined if the CPU does not have the feature enabled

Luca Barbato (Oct 14 2019 at 07:59, on Zulip):

so what happens if I want to build a binary using a compiler that supports avx2 and then run it on a cpu that does not have avx2?

gnzlbg (Oct 14 2019 at 07:59, on Zulip):

so if the following assert fails, your program has exhibited UB the moment you started executing it

if cfg!(target_feature = "X") {
    assert!(is_x86_feature_detected!("X");
}
gnzlbg (Oct 14 2019 at 07:59, on Zulip):

@Luca Barbato that's UB

gnzlbg (Oct 14 2019 at 08:00, on Zulip):

we will optimize your whole program under the assumption that AVX2 is available

gnzlbg (Oct 14 2019 at 08:00, on Zulip):

so we make no guarantees about what happens if you try to run such program on a CPU without AVX2

Luca Barbato (Oct 14 2019 at 08:00, on Zulip):

it is a problem

gnzlbg (Oct 14 2019 at 08:00, on Zulip):

anything can happen

Luca Barbato (Oct 14 2019 at 08:00, on Zulip):

that makes the runtime checks for cpu features not exactly useful

gnzlbg (Oct 14 2019 at 08:01, on Zulip):

you can compile your whole program without AVX2, and one function with AVX2

gnzlbg (Oct 14 2019 at 08:01, on Zulip):

as long as you don't call that function on CPUs without AVX2, no UB happens

gnzlbg (Oct 14 2019 at 08:01, on Zulip):

the run-time checks tell you whether calling that function is ok

gnzlbg (Oct 14 2019 at 08:01, on Zulip):

but if you compiled your whole program for AVX2, then the run-time check returns always true

Luca Barbato (Oct 14 2019 at 08:02, on Zulip):

that's my expectation, I'm not sure I understand then "compile your whole program for"

gnzlbg (Oct 14 2019 at 08:02, on Zulip):

because we don't allow such programs running on CPUs without AVX2

gnzlbg (Oct 14 2019 at 08:02, on Zulip):

-C target-feature=+avx2

gnzlbg (Oct 14 2019 at 08:03, on Zulip):

The only way cfg!(target_feature = "avx2") returns true is if AVX2 is enabled for all functions in a program

Luca Barbato (Oct 14 2019 at 08:03, on Zulip):

this needs some additional documentation

gnzlbg (Oct 14 2019 at 08:03, on Zulip):

That's documented in the std::arch docs

gnzlbg (Oct 14 2019 at 08:03, on Zulip):

and in the compiler docs

gnzlbg (Oct 14 2019 at 08:03, on Zulip):

(the compiler user guide)

gnzlbg (Oct 14 2019 at 08:04, on Zulip):

-C target-feature=+avx2 says the CPU this binary runs on has the AVX2 feature, so use it wherever you want

gnzlbg (Oct 14 2019 at 08:04, on Zulip):

-C target-cpu=foo says the CPU this binary runs on is foo, so use all features that foo has

gnzlbg (Oct 14 2019 at 08:05, on Zulip):

The different target triples enable different base CPUs, features, etc.

gnzlbg (Oct 14 2019 at 08:05, on Zulip):

This is not different than compiling a binary for x86_64-unknown-linux-gnu, and trying to run that binary on windows

gnzlbg (Oct 14 2019 at 08:05, on Zulip):

we can't even guarantee that that fails

gnzlbg (Oct 14 2019 at 08:06, on Zulip):

there are no checks or anything that we can reasonably insert to prevent the execution of such a program

gnzlbg (Oct 14 2019 at 08:06, on Zulip):

those checks would be compiled for linux, and they would be meaningless on other systems

gnzlbg (Oct 14 2019 at 08:06, on Zulip):

same for AVX2

Luca Barbato (Oct 14 2019 at 08:06, on Zulip):

My reading of this

gnzlbg (Oct 14 2019 at 08:06, on Zulip):

the compiler will generate code for those checks under the assumption that AVX2 is available

Luca Barbato (Oct 14 2019 at 08:07, on Zulip):

would be that you need to turn the target_feature on to build the code

gnzlbg (Oct 14 2019 at 08:07, on Zulip):

Statically enabling a feature is typically done with the -C target-feature or -C target-cpu flags to the compiler.
Note that when you compile a binary with a particular feature enabled it's important to ensure that you only run the binary on systems which satisfy the required feature set.

Luca Barbato (Oct 14 2019 at 08:08, on Zulip):

the question now is "how do I build something that has avx2 code while running also on non-avx2 machines?"

gnzlbg (Oct 14 2019 at 08:08, on Zulip):

https://github.com/rust-lang-nursery/reference/blob/master/src/behavior-considered-undefined.md

Behavior considered undefined: [...]
Executing code compiled with platform features that the current platform does not support (see target_feature).

gnzlbg (Oct 14 2019 at 08:09, on Zulip):

the question now is "how do I build something that has avx2 code while running also on non-avx2 machines?"

You compile for a target that doesn't have AVX2, use #[target_feature] to enable AVX2 on the functions in which you want to allow the compiler to use it, and guard calls to those functions with is_x86_feature_detected!("avx2")

Luca Barbato (Oct 14 2019 at 08:09, on Zulip):

#[target_feature(enable = "avx2")] vs `#[cfg(target_feature = "avx2")]

Luca Barbato (Oct 14 2019 at 08:10, on Zulip):

this should be stressed ^^

gnzlbg (Oct 14 2019 at 08:10, on Zulip):

https://doc.rust-lang.org/core/arch/index.html#dynamic-cpu-feature-detection

gnzlbg (Oct 14 2019 at 08:11, on Zulip):

Sometimes statically dispatching isn't quite what you want. Instead you might want to build a portable binary that runs across a variety of CPUs, but at runtime it selects the most optimized implementation available. This allows you to build a "least common denominator" binary which has certain sections more optimized for different CPUs.

Taking our previous example from before, we're going to compile our binary without AVX2 support, but we'd like to enable it for just one function.

Luca Barbato (Oct 14 2019 at 08:12, on Zulip):

I guess that's enough and it is me not reading carefully

Luca Barbato (Oct 14 2019 at 08:15, on Zulip):

having a -C target_feature=miri would be enough to avoid the UB?

gnzlbg (Oct 14 2019 at 08:34, on Zulip):

Well not really - cc @RalfJ

gnzlbg (Oct 14 2019 at 08:34, on Zulip):

The problem is that miri doesn't want to be a rust target

gnzlbg (Oct 14 2019 at 08:34, on Zulip):

But instead emulate all targets supported by Rust

gnzlbg (Oct 14 2019 at 08:35, on Zulip):

So if that's miris goal, the problem is that miri doesn't emulate run-time feature detection yet

gnzlbg (Oct 14 2019 at 08:35, on Zulip):

nor the intrinsics

gnzlbg (Oct 14 2019 at 08:36, on Zulip):

We could implement emulation of run-time feature detection for miri, e.g., by just returning the same value as cfg!(target_feature), for example

gnzlbg (Oct 14 2019 at 08:36, on Zulip):

and then we could implement all intrinsics that those features allow on miri

gnzlbg (Oct 14 2019 at 08:37, on Zulip):

that's a titanic amount of work, but if miris goal is to emulate the semantics of a target, then... that's what it has to do

gnzlbg (Oct 14 2019 at 08:37, on Zulip):

at least, to be able to run code that uses these parts of the language

gnzlbg (Oct 14 2019 at 08:39, on Zulip):

I'm tending more that this would be the right thing to do according to miris goal's, this means that all code using intrinsics would just abort on the first intrinsic, and until miri supports them such code can just cfg!(miri) its way out of the situation

gnzlbg (Oct 14 2019 at 08:40, on Zulip):

is_x86_feature_detected! can use cfg!(miri) to just return the value of cfg!(target_feature), but it cannot returne false for a query for which cfg!(target_feature) returns true because that would be an error in the abstract machine

gnzlbg (Oct 14 2019 at 08:41, on Zulip):

if that happens, the behavior is undefined, according to the reference, so doing so would be purportedly implementing is_x86_feature_detected to have UB under miri

gnzlbg (Oct 14 2019 at 08:44, on Zulip):

So I think that the logic that you implemented @Luca Barbato is indeed the correct one

Luca Barbato (Oct 14 2019 at 08:47, on Zulip):

I wonder if -C target_feature=-${foo} is doing what it should

Luca Barbato (Oct 14 2019 at 08:47, on Zulip):

that _might_ solve the current problem and allows to flip on the cpu feature once it is supported

gnzlbg (Oct 14 2019 at 08:47, on Zulip):

Maybe we could expand the cfg(miri) comment with the information, that a consequence of returning true for the features enabled at compile-time is that branches on, e.g., is_x86_feature_detected!("sse") will return true for miri causing miri to try to execute SSE intrinsics, which will fail. Users that don't want this to happen should handle the cfg!(miri) case instead.

gnzlbg (Oct 14 2019 at 08:48, on Zulip):

@Luca Barbato -C target-feature=-sse disables SSE for the crates compiled with that flag

gnzlbg (Oct 14 2019 at 08:48, on Zulip):

and in those crates, cfg!(target_feature = "sse") will return false

gnzlbg (Oct 14 2019 at 08:48, on Zulip):

and so will is_x86_feature_detected!

Luca Barbato (Oct 14 2019 at 08:48, on Zulip):

so it would short circuit correctly

gnzlbg (Oct 14 2019 at 08:49, on Zulip):

in general, libstd is not re-compiled with -C target-features

gnzlbg (Oct 14 2019 at 08:49, on Zulip):

so SSE will still be enabled for libstd, and that changes the Rust ABI of, e.g., which registers are used for passing floats

Luca Barbato (Oct 14 2019 at 08:49, on Zulip):

it would just need a tiny wrapper to turn off all the unsupported features

gnzlbg (Oct 14 2019 at 08:49, on Zulip):

so that would trigger UB of the form "calling an function through an incompatible ABI declaration"

gnzlbg (Oct 14 2019 at 08:50, on Zulip):

If miri re-compiles libstd

gnzlbg (Oct 14 2019 at 08:50, on Zulip):

Then that would not have UB on miri though

gnzlbg (Oct 14 2019 at 08:50, on Zulip):

So miri could, on x86_64, always call rustc with -C target-feature=-mmx

gnzlbg (Oct 14 2019 at 08:51, on Zulip):

to avoid many of the stable intrinsics

gnzlbg (Oct 14 2019 at 08:51, on Zulip):

as long as it recompiles libstd with it

gnzlbg (Oct 14 2019 at 08:51, on Zulip):

Maybe we could add a -C target-cpu=miri that does this ? cc @RalfJ

Luca Barbato (Oct 14 2019 at 08:51, on Zulip):

sounds a path to consider to me

gnzlbg (Oct 14 2019 at 08:52, on Zulip):

I don't know if -C target-feature=-mmx would be enough, maybe one needs -C target-feature=-mmx,-sse,-sse2,...,-avx512f,avx512foobarbaz

Luca Barbato (Oct 14 2019 at 08:53, on Zulip):

yep, thus why I suggested a wrapper :)

Luca Barbato (Oct 14 2019 at 08:53, on Zulip):

or just the shorthand above

gnzlbg (Oct 14 2019 at 08:53, on Zulip):

miri would probably want to detect whether the user tries to use -C target-feature or -C target-cpu in their RUSTFLAGS or cargo config and error or warn

Luca Barbato (Oct 14 2019 at 08:53, on Zulip):

=miri -> =-all

gnzlbg (Oct 14 2019 at 08:53, on Zulip):

yeah

gnzlbg (Oct 14 2019 at 08:54, on Zulip):

I'll open an issue on the miri repo

Last update: Nov 15 2019 at 09:40UTC