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

target_feature 1.1 #2396

Open
wants to merge 9 commits into
base: master
from

Conversation

Projects
None yet
@gnzlbg
Copy link
Contributor

commented Apr 6, 2018

Rendered

@gnzlbg gnzlbg force-pushed the gnzlbg:tf11 branch from f79d0f0 to 76c213b Apr 6, 2018

@Ericson2314

This comment has been minimized.

Copy link
Contributor

commented Apr 6, 2018

This looks good to me 👍. It's a nice conservative "obviously sound" next stop.

I talked a little bit to @gnzlbg about the interaction with the portability lint and target_feature in general; which @gnzlbg says hasn't really been discussed. Thankfully, static #[target_feature ..] and #[cfg(target_feature ...)] should be a shoe-in allowing us to go from a simple equivalence relation (equal set of annotations) to a richer lattice, continuing the trend this RFC establishes. cfg!(target_feature..) is more interesting...

@Centril Centril added the T-lang label Apr 6, 2018

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 7, 2018

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 7, 2018

@newpavlov posted a pre-RFC about target restriction contexts: https://internals.rust-lang.org/t/pre-pre-rfc-target-restriction-contexts/7163/

I don't think this RFC is incompatible with that proposal, but that's something worth triple-checking since those are things that we might want to do later anyways.

@est31

This comment has been minimized.

Copy link
Contributor

commented Apr 7, 2018

Beautiful RFC, big 👍 .

One small point: You probably already know it, but to one important thing for soundness is probably that #[target_feature] attributes should influence the type of the function during function pointer creation. In code:

#[target_feature = "sse2"] fn bar() { }

fn test() {
    let a: fn() = bar; // ERROR mismatched types expected fn() got #[target_feature ="sse2"] fn()
    a();

    let b: #[target_feature] fn() = bar; // works
    b();
}

To get the feature onto stable faster (syntax bikesheds are tiring), we could not implement the let b: #[target_feature] fn() = bar; feature initially, but the type mismatch is important.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 7, 2018

@est31

one important thing for soundness is probably that #[target_feature] attributes should influence the type of the function during function pointer creation

So right now (RFC2045) you can't have a safe function pointer to a #[target_feature] function - this RFC does not change that. That is shown in the "Example 3". Basically one needs to use an unsafe function pointer and coerce the #[target_feature] function into it. The user of the function pointer needs to open an unsafe block and make sure that any pre-conditions are uphold, so AFAICT that is sound.

This restriction can be relaxed later, but doing so adds some complexity, and I don't think that it buys us that much more, so I wouldn't want to delay anything here for that.

The reason it doesn't buy us that much is that today one can already write safe wrappers around unsafe function pointers. The wrapper ensures that if you construct it properly, the function pointer is only set to a function that is actually safe to call, for example, by doing run-time or compile-time feature detection.

@Ericson2314

This comment has been minimized.

Copy link
Contributor

commented Apr 7, 2018

@est31 yeah adding annotations to the type system like that is kind of a big deal. OTOH we can achieve that sort of thing with the portability lint (see rust-lang-nursery/portability-wg#17 for details). In general [i.e with enough squinting :D], the portability lint can be viewed as a sort of overlay effect system with subtyping, so it's perfect for this sort of thing.

@rkruppe

This comment has been minimized.

Copy link
Member

commented Apr 7, 2018

But the portability lint isn't sound, right? The solution for mismatched target features has to be sound because calling a function with the wrong target features is UB.

@Ericson2314

This comment has been minimized.

Copy link
Contributor

commented Apr 8, 2018

@rkruppe the portability lint is only not sound because we choose to make it warn not err. I guess I got ahead of myself thinking of a future epoch where it does raise errors :).

I'll back-pedal and say in the nearer term it would indeed be a separate feature as @est31 describes, but share the same implementation. Then in that future epoch we flip the switch and the features are unified.

@alexcrichton

This comment has been minimized.

Copy link
Member

commented Apr 9, 2018

@gnzlbg thanks for the RFC! On reading I was having a difficult time figuring out the difference to #2212 (which is for now postponed), could you elaborate a bit on the differences?

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 9, 2018

@alexcrichton

On reading I was having a difficult time figuring out the difference to #2212 (which is for now postponed), could you elaborate a bit on the differences?

I just read RFC2212 twice and I still don't think I fully understands what it proposes, so these are the differences that I identify (please correct me if I understood RFC2212 incorrectly):

  • RFC2212 introduces feature hierarchies, which allows safe #[target_feature] functions to be called from functions that have the feature enabled, but do not explicitly state so (e.g. because AVX2 implies AVX). This RFC is less powerful but simpler since it just requires the features to match exactly (e.g. AVX2 does not imply AVX). Introducing feature hierarchies is controversial and has to be done with care, it probably deserves its own RFC.
  • RFC2212 mentions that target features become part of a functions type, this RFC does not. I don't understand exactly how target features interact with the type-system in RFC2212 so I am not sure whether this is a big difference, or whether this matters at all or not.
@alexcrichton

This comment has been minimized.

Copy link
Member

commented Apr 9, 2018

Ok cool, thanks for the clarification! I would personally be on board with the construction in this RFC, I think it definitely makes sense!

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 10, 2018

I guess this RFC needs a shepherd then :)

@Centril

This comment has been minimized.

Copy link
Contributor

commented Apr 10, 2018

I guess this RFC needs a shepherd then :)

I can do that =)

@parched

This comment has been minimized.

Copy link

commented Apr 11, 2018

What about

fn foo() { }

#[target_feature = "x"] fn bar() {
    foo(); // is an unsafe block required?
}

Am I right in assuming that no unsafe block is required here because currently we don't allow disabling features and have no negative features? In future when we add feature hierarchies then we can allow disabling features and negative features and the compiler can require a unsafe block here in those cases.

@hsivonen

This comment has been minimized.

Copy link

commented Apr 11, 2018

AFAICT the substantive differences between RFC 2212 and this RFC are that

  1. this RFC doesn't define the hierarchy of higher SSE versions implying the lower ones
  2. this RFC requires the caller and the callee to have the exact same target_feature list for the call to be safe while RFC 2212 requires the callee to have a subset of of the caller's target_feature list (taking into account the features implied by the SSE hierarchy)
  3. RFC 2212 prohibits function pointers to safe target_feature functions but this RFC allows unsafe function pointers to such functions.

Correct?

while it is safe to call an SSE2 function from an AVX one, this would require specifying how features relate to each other in hierarchies. This would unnecessary complicate this RFC and can be done later once we agree on the fundamentals.

I think this is quite unfortunate given how the SSE family is organized. I think it would be beneficial to define the hierarchy for the SSE family even if defining other hierarchies was deferred. I can understand why there could exist controversial hierarchies (esp. AVX-512 family not being strictly additive), but it seems sad to refuse to define any hierarchies when the most common case is a well-known hierarchy.

Also, it seems unnecessarily strict to require the callee to have the same set of features as the caller, since logically a subset should suffice. Surely after this RFC functions with some target_feature are still allowed to call safe functions with no explicit target_feature declarations. That's already a subset relation anyway.

RFC2212 mentions that target features become part of a functions type, this RFC does not. I don't understand exactly how target features interact with the type-system in RFC2212 so I am not sure whether this is a big difference, or whether this matters at all or not.

This RFC needs some information about the functions for the compiler to decide that meow is not allowed to call bar but bark is. What that information is called is a matter of terminology, but I don't think the two RFCs differ in substance on this point.

In any case, as the author of RFC 2212, I'm in favor of this RFC, since this RFC is a step closer to the goal of RFC 2212 (and exceeds it on the issue of function pointers). Thank you!

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 11, 2018

@parched

Am I right in assuming that no unsafe block is required here because currently we don't allow disabling features and have no negative features?

Yes, thanks for bringing this up. That example has to be written as:

fn foo() { }
#[target_feature(enable = "x")] fn bar() {
    foo(); // OK
}

If we were to allow "negative" features, then:

fn foo() { }
#[target_feature(disable = "y")] fn bar() {
    foo(); // ERROR: foo is y but bar is not y
    unsafe { foo() }; // OK
}

I'll add how this could be extended to negative features to the unresolved questions part of the RFC.


@hsivonen

Correct?

Yes, that's correct! However, you spotted the following bug which changes point 2.

this RFC requires the caller and the callee to have the exact same target_feature list

The guide-level explanation states:

safe #[target_feature] functions can be called without an unsafe {} block only from functions that have at least the exact same set of #[target_feature]s.

while the reference section states:

safe #[target_feature] functions can be called without an unsafe {} block only from functions with the exact same set of #[target_feature]s.

which obviously don't match. So which is it? First, this is a bug in the RFC, thank you for spotting this. The reference section is wrong and it should state what the guide-level explanation says. That is, the calle and the caller must have the same target features enabled, but the caller can have more target features enabled than the callee.


@hsivonen

I think this is quite unfortunate given how the SSE family is organized. I think it would be beneficial to define the hierarchy for the SSE family even if defining other hierarchies was deferred.

I think that now that std::arch is merged it would be enough to open a tracking-issue in rust-lang/rust that requests that #[target_feature(enable = "avx")] also enables sse2,3,4,4.2,.... That will need little motivation, but one will have to go through the specs collecting the information that such a hierarchy is guaranteed to work on all past, present, and future Intel/AMD CPUs. Depending on how that issue evolves a mini-FCP might be enough for that.

I prefer to not derail this RFC into a discussion about the hierarchy, but I'd gladly chime in into a tracking issue for that if somebody opens it.

@gnzlbg

This comment has been minimized.

Copy link
Owner Author

commented on text/0000-target-feature-1.1.md in b73fc95 Apr 11, 2018

@Centril I've added some wording about the interaction with an effect systems and some of the things we discussed on IRC. Sadly, my IRC logs are gone, so I might have missed a couple of things. Please triple-check this :)

This comment has been minimized.

Copy link

replied Apr 12, 2018

It looks good to me =)

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 11, 2018

@hsivonen

This RFC needs some information about the functions for the compiler to decide that meow is not allowed to call bar but bark is. What that information is called is a matter of terminology, but I don't think the two RFCs differ in substance on this point.

I was talking with @Centril on IRC the other day and I agree: this RFC does change the typing rules since programs that are now rejected would become accepted. For the cases introduced in this RFC, it might be enough to extend the checks of unsafe for expressions in librustc_mir. What this RFC does not do, and what RFC2212 did not do either, is make #[target_feature] a proper part of a function's type, so that one could, for example, let x: #[target_feature(...)] fn() -> () = foo(); and make the error a "type mismatch" error.

@Centril

This comment has been minimized.

Copy link

commented on text/0000-target-feature-1.1.md in b73fc95 Apr 12, 2018

Well; it is an effect (or restriction if you are disabling things already permitted in safe functions) regardless of whether we have effect polymorphism or an effect system or not. Having #[target_feature] on its lonesome just makes it less well-integrated with all other effects.

@hsivonen

This comment has been minimized.

Copy link

commented Apr 23, 2018

clang clearly believes that the SSE hierarchy that one would expect exists.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Apr 23, 2018

@hsivonen it might well be that clang can't target any of the larrabees pre-skylake where this was the case yet.

EDIT: indeed, while llc -mcpu=help returns that clang can target knl and knm cpus, it cannot target knc/knf which are the cpus that support AVX-512 but don't support MMX, SSE, etc.

@hsivonen

This comment has been minimized.

Copy link

commented May 8, 2018

I remarked earlier "(esp. AVX-512 family not being strictly additive)". The weirdness of AVX-512 doesn't invalidate the notion that the SSE levels (and, it seems, the non-512 AVX) form a hierarchy.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented May 8, 2018

@hsivonen my point is only that if future Intel CPUs support AVX/AVX2 but not SSE, we would be screwed if we had changed the meaning of #[target_feature(enable = "avx2")] to imply that sse4.2 is enabled.

IMO, instead of adding a hierarchy, it makes more sense to just add new features that enable multiple features just like we do for crypto, which enables sha1, sha2, aes, ...

For SIMD we could, for example, add:

  • simd128 => sse, sse2, ..., sse4.2 on x86, asimd on aarch64, altivec and vsx on powerpc...,
  • simd256 => avx, avx2 on x86, nothing on others,
  • simd128and256 => simd128, simd256

That way those who want finer grained control can still use avx, and those who don't care can just use simd128 and call it a day. If intel one day decides to ship a CPU with SSE4.2 but not SSE2 for whatever reason, simd128 would just be disabled, but sse4.2 would still work fine.

However, this will only be a minor ergonomic improvement, and things will still be extremely painful to use because of the RFC changes of repeating the arch name in both core::arch::arch_name and in the run-time feature detection macros.

#[target_feature(enable = "simd128")]  // yay, all 128-bit SIMD enabled everywhere !
unsafe foo() {
    #[cfg(target_arch = "x86")]  use core::arch::x86::*;  // ohh
    #[cfg(target_arch = "x86_64")]  use core::arch::x86_64::*; // urghhh
    #[cfg(target_arch = "arm")] use core::arch::arm::*;
    #[cfg(target_arch = "aarch64")] use core::arch::aarch64::*;  // my eyes are bleeding
    // powerpc...

     // well run-time feature detection must be better right ?
     #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
         if is_x86_feature_detected!("simd256") { 
             // not that bad for x86 and x86_64
             bar_256();
         } else {  ... }
     }
     // ouch
     #[cfg(target_arch = "arm")] {
        if is_arm_feature_detected!("simd256") { } else { .. }
     }
     // please, seriously, kill me now
     #[cfg(target_arch = "aarch64")] {
        if is_aarch6_feature_detected!("simd256") { } else { .. }
     }
}

With the std::arch RFC version that was submitted, the above would look like this:

#[target_feature(enable = "simd128")]  // yay, all 128-bit SIMD enabled everywhere !
unsafe foo() {
    use core::arch::x86::*;

    // we can add `simd128` and `simd256` to all
    // archs, and make it return false!
    if is_feature_detected!("simd256") { 
             bar_256();
         } else {  
            ...
    }
}

I don't know if the decision can be reverted at this point.

@hsivonen

This comment has been minimized.

Copy link

commented May 9, 2018

The SSE levels have been designed not to even make sense without the previous levels present, so I think we shouldn't design for Intel breaking the SSE hierarchy. I can concede the point of not relying on non-512 AVX always implying the presence of the SSE hierarchy.

Note that taken to absurdity, you can't even rely on instruction groupings like SSE levels: There was a MIPS clone that omitted a single instruction. That sort of thing just can't be planned for. OTOH, the LLVM approach of just not supporting the too weird Xeon Phis and supporting the newer less weird ones is a valid approach to ISA steward doing something messy.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented May 9, 2018

The SSE levels have been designed not to even make sense without the previous levels present, so I think we shouldn't design for Intel breaking the SSE hierarchy.

The only thing intel actually defines is CPUID feature bits. If Intel ever breaks the hierarchy, which is something it has done before and might do again, that's the only thing that Intel guarantees that can be trusted because those bits are part of each ISA extension (otherwise there is a bug in the implementation of the ISA).

The simd128 is a design that assumes that Intel will never break the SSE hierarchy. If it does, it also allows us to fix it for the CPUs in which that happens easily, by doing different run-time feature detection for particular CPU models.

I can concede the point of not relying on non-512 AVX always implying the presence of the SSE hierarchy.

AVX and AVX2 were not available either.

OTOH, the LLVM approach of just not supporting the too weird Xeon Phis and supporting the newer less weird ones is a valid approach to ISA steward doing something messy.

Note that LLVM doesn't assume the presence of pervious members of the hierarchy when doing run-time feature detection for SSE4.2; it considers them to be completely disjoint features: https://github.com/llvm-mirror/compiler-rt/blob/dcd249132f2d06fb3209b0f965ad1b09ef61feb0/lib/builtins/cpu_model.c#L464


FWIW, I am not opposed to hierarchies in general, but Rust is a low-level language, and the lowest level is CPUID bit flags, which is what actually maps to each individual ISA extension.

I'd be willing to explore how to add "hierarchies" or something that solves the same or similar problems in a different RFC, but for this one it is out-of-scope.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Aug 6, 2018

@Centril As the champion, what's your opinion of the status of this RFC ? We should decide whether we advance this, postpone this until after the Rust2018 edition release, etc.

Even if we merge this, that won't necessarily mean that it would get implemented before Rust 2018, and even if it did, it will remain an unstable feature for a while since I don't think anybody can be sure that things will work as "nicely" as the RFC puts them. We might not see any big issues with this now, but during implementation new issues will definitely be discovered.

@Centril

This comment has been minimized.

Copy link
Contributor

commented Aug 6, 2018

@gnzlbg At the moment, given lead up to the edition, the language team's agenda is pretty overbooked. The team is more or less failing to clear agenda items on the meetings every week and moving them over to the next week. Also, I don't think this RFC is a priority item, so it will take some time before it moves into the agenda.

Personally, I think it is fine for this RFC to remain open even if we don't get to it for a while ("informally postponed"); However, if you wish to formally postpone it, we can do that as well.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Aug 6, 2018

@Centril can PRs for implementing informally postponed RFCs be accepted?

@scottmcm

This comment has been minimized.

Copy link
Member

commented Aug 6, 2018

@gnzlbg I assume it's up to the compiler team whether they're willing to accept speculative work.

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Dec 14, 2018

Now that Rust2018 is out, what would be the next step to make progress on this?

EDIT: FWIW I think this RFC should word that, since calling a safe #[target_feature] function from a function with "incompatible" target features requires unsafe { }, this actually adds a new "unsafe-superpower".

@joshtriplett

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

This RFC looks completely sensible to me, and it'll help make the majority of the intrinsics much safer to call (anything that doesn't use raw pointers). I don't see any objections here; this just got deferred due to everyone getting busy with the edition.

We can absolutely take further steps in the future. For instance, I'd like to have a mechanism to make calls safely inside blocks that the compiler can statically determine to have checked for a given target feature (e.g. inside an if), and that would let some programs avoid unsafe entirely when using intrinsics. However, this RFC seems like a completely straightforward and sensible update that will substantially reduce usage of unsafe.

@rfcbot merge

@rfcbot

This comment has been minimized.

Copy link

commented Mar 15, 2019

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

commented Mar 22, 2019

We discussed this RFC in our @rust-lang/lang meeting yesterday -- unfortunately, @joshtriplett wasn't present! As with RFC 2580, we had a bit of trouble deciding what to do about it. The problem is not so much in the content of the RFC as it is in the process questions -- specifically, we are trying to adopt a more directed structure, where we don't start a bunch of things and then leave them half-done, but instead only start things when we are dedicated to seeing them through.

In the case of this RFC, I think one of the key concerns would be how much consistency we want beyond the "pseudo-effect system" described here and the effect system that is going to wind up existing around const fns.

But also, what is the motivation to do this now? Can we connect it to the roadmap, or to an active working group? (For broader context, please read my comment on RFC 2580 -- I think basically the same concerns apply.

Anyway, we'll leave this nominated, and hopefully in a meeting when @joshtriplett is present we might be able to dig in a bit there.

@joshtriplett

This comment has been minimized.

Copy link
Member

commented Mar 23, 2019

@gnzlbg

This comment has been minimized.

Copy link
Contributor Author

commented Mar 25, 2019

In the case of this RFC, I think one of the key concerns would be how much consistency we want beyond the "pseudo-effect system" described here and the effect system that is going to wind up existing around const fns.

@nikomatsakis Without making these concerns more concrete (e.g. which exact part of this RFC would not work for the currently proposed effect systems), this feedback is not really actionable. In particular, #[target_feature] is an effect that's already available on stable, this RFC does not add a new type of effect to the language. So I would be particularly interested on an example that exists with this RFC, but does not exists on stable Rust.

@Centril Centril removed the I-nominated label Apr 11, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.