-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: target_feature #2045
RFC: target_feature #2045
Conversation
a2791a4
to
07e23d4
Compare
Question: isn't triggering an illegal instruction a form of memory unsafety? Or other things like handling the FPU state. |
@dlight No, crashes are safe. Deterministically causing a |
Similarly with instructions. Unless an instruction is defined as invalid
(e.d. ud2), it's encoding could be used in some future processors for e.g.
rocket launching. It is also possible that there are certain
already-released SKUs which use encoding of the invalid instruction for
some secret instruction or decode to any other valid instruction.
|
I asked @chandlerc on IRC about the "safety" of
and mentioned two examples:
@chandlerc Did I summarize your points correctly? So IIUC calling a function marked with the EDIT:
// Raw intrinsic function: dispatches to LLVM directly.
// Calling this on an unsupported target is undefined behavior.
extern "C" { fn raw_intrinsic_function(f64, f64) -> f64; }
// Software emulation of the intrinsic,
// works on all architectures.
fn software_emulation_of_raw_intrinsic_function(f64, f64) -> f64;
// Safe zero-cost wrapper over the intrinsic
// (i.e. can be inlined -- whether this is a good idea
// is an unresolved question).
fn my_intrinsic(a: f64, b: f64) -> f64 {
#[cfg!(target_feature = "some_feature")] {
// If "some_feature" is enabled, it is safe to call the
// raw intrinsic function
unsafe { raw_intrinsic_function(a, b) }
}
#[not(cfg!(target_feature = "some_feature"))] {
// if "some_feature" is disabled calling
// the raw intrinsic function is undefined behavior (per LLVM),
// we call the safe software emulation of the intrinsic:
software_emulation_of_raw_intrinsic_function(a, b)
}
}
// Provides run-time dispatch to the best implementation:
// ifunc! is a procedural macro that generates copies
/// of `my_intrinsic` with different target features,
// does run-time feature detection on binary initialization,
// and sets my_intrinsic_rt to dispatch to the appropriate
// implementation:
static my_intrinsic_rt = ifunc!(my_intrinsic, ["default_target", "some_feature"]);
// This procedural macro expands to the following:
// Copies the tokens of `my_intrinsic` for each feature
// into a different function and annotates it with
// #[target_feature]. Due to this, calling this function
// is unsafe, since doing so on targets without the feature
// introduces undefined behavior.
#[target_feature = "some_feature"]
unsafe fn my_intrinsic_some_feature_wrapper(a: f64, b: f64)
{
#[cfg!(target_feature = "some_feature")] {
// this branch will always be taken because
// `#[target_feautre = "..."]` defines `cfg!(target_feature = "...") == true`
unsafe { raw_intrinsic_function(a, b) }
}
#[not(cfg!(target_feature = "some_feature"))] {
// dead code: this branch will never be taken
software_emulation_of_raw_intrinsic_function(a, b)
}
}
// This function does run-time feature detection to return a function pointer
// to the "best" implementation (run-time feature detection is not part of this RFC):
fn initialize_my_intrinsic_fn_ptr() {
if std::cpuid::has_feature("some_feature") -> typeof(my_intrinsic) {
// Since we have made sure that the target the binary is running on
// has the feature, calling my_intrinsic_some_feature_wrapper is safe:
unsafe {
my_intrinsic_some_feature_wrapper
/* do we need a cast here?: as typeof(my_intrinsic) */
}
} else {
// because we passed "default_target" to ifunc we fall back
// to the safe implementation. We could otherwise return a
// function pointer to a stub function:
// fn my_intrinsic_fallback(f64,f64) -> f64 { panic!("nice error message") }
my_intrinsic
}
} Note how here one can still run into undefined behavior by using |
5693463
to
0aa1e75
Compare
So I've updated the RFC with the discussion of undefined behavior and run-time feature detection and these issues as unresolved questions. |
Updated the RFC with the interaction between Still need to go through the whole RFC, update the motivation for making |
So I have resolved the unsafety issue and pushed a much smaller and certain version of the RFC. |
text/0000-target-feature.md
Outdated
`#[target_feature]` requires run-time feature detection is required. | ||
|
||
How to do this is an unresolved question of this RFC that must be resolved | ||
before stabilization. This section does not propose a solution, but explores the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does run-time feature detection need to be resolved before this RFC is stabilized? It doesn't seem required to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What must be resolved is whether we need to stabilize run-time detection alongside this RFC or not. I need to phrase this differently, it's a bit meta.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. I think we'd need a very strong reason to couple this with run-time detection as well. I'd much rather leave run-time detection to the Wild West (i.e., crates.io) until it's more thoroughly understood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BurntSushi I've rewritten this to make it more clear, let me know if it helps or does not help.
text/0000-target-feature.md
Outdated
API for run-time feature detection? | ||
|
||
- Can we lift the restriction on `target_feature="-feature"` to provide | ||
substractive features? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What restriction is this referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the RFC only allows #[target_feature = "+..."]
, that is, adding features to the default features set. This is an attempt to avoid non-sensical code like this example that you showed.
Since I haven't given #[target_feature = "-..."]
much thought I've listed it as an unresolved question in case somebody wants to give it a try.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see. I seem to recall someone giving me a use case for "-..."
, but I can't recall it off the top of my head. However, if we're making #[target_feature]
unsafe anyway, then its quirks seem less risky?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
00c48d7
to
98d5ec6
Compare
Alright I've now merged this RFC, thanks again @gnzlbg for it! |
@est31 :
Why not? Suppose I have code that's compiled generally for x86_64, which includes SSE2. Let's call this SSE2 code. Then for some functionality, I have a faster SSE4 implementation that get conditionally called at run-time. Let's call this SSE4 code. All the functions that are part of SSE4 code need to use AFAICT, the part that's unsafe is the call from SSE2 code into SSE4 code. It would devalue the safe/ |
Let me check IIUC. You prefer this: #[target_feature = "sse4.1"]
unsafe fn foo() {
safe_bar();
unsafe { sse41_intrinsic() }
} to this: #[target_feature = "sse4.1"]
unsafe fn foo() -> u32x4 {
let a = bar();
sse41_intrinsic(a) // no unsafe block required
} Is that correct? |
I prefer
over
That is, |
How would you call
So whether |
I wrote So I revise my preference to drop
|
That makes more sense. The definition of Why do you need For |
Because foo is unsafe to call from baz.
I guess the key insight here is that the nature of Therefore, in order to avoid everything becoming unsafe and unsafe/safe distinction getting less useful as a result, the rule that only a callee marked (Alternatively, there could be a block with a keyword other than |
What you propose sounds in general like a good idea, I encourage you to write an RFC that explores that and please ping us here once you have a draft! As mentioned in the RFC, removing the need for Some thoughts:
|
I also like this proposal and think it should definitely be implemented at some stage (it bugs me that there are AArch64 NEON intrinsics in stdsimd that require unsafe, yet there are no AArch64 builtin targets in rustc without NEON enabled by default). Though, as @gnzlbg mentions, it is backward compatible so no hurry to do it now.
That's required now anyway unless cross crate calls are always going to use the lowest common denominator calling convention. This also won't be able to be applied where the target features are unknown e.g., dynamic dispatch but that's not a big drawback. It just means functions with |
However, that is the normal case. Most if not all So, I think it makes a lot of sense that a function which implicitly makes assumptions about the instruction set of the CPU should be unsafe. This function is not safe to call anytime, anywhere. Rather, similar to |
@RalfJung I think we could phrase
So the following code: #[target_feature = "+sse4.2"] fn foo_sse42();
#[target_feature = "+avx"] fn bar_avx();
unsafe baz();
#[target_feature = "+sse4.2"]
fn meow() {
foo_sse42(); // OK: compiler can prove this safe
bar_avx(); // ERROR: unsafe fn
baz(); // ERROR: unsafe fn
} could desugar to: #[target_feature = "+sse4.2"] unsafe(target_feature = "+sse4.2") fn foo_sse42();
#[target_feature = "+avx"] unsafe(target_feature = "+avx") fn bar_avx();
unsafe baz();
#[target_feature = "+sse4.2"]
unsafe(target_feature = "+sse4.2") fn meow() {
safe(target_feature = "+sse4.2") {
// unsafe(target_feature = "+sse4.2") functions are safe to call
foo_sse42(); // OK:
bar_avx(); // ERROR: unsafe to call
baz(); // ERROR: unsafe to call
}
unsafe {
bar_avx(); // OK
baz(); // OK
}
} where I have introduced Expanding on this, currently, when the user does some checking, unsafe is still required: if cfg_feature_enabled("avx") {
// here AVX is enabled at run-time
unsafe { foo_avx() }; // but foo_avx is unsafe to call
} something like this (handwaiving) could allow us to remove the need for the unsafe block there if we can make the |
The "well-typed" bit is the key. In order to avoid Like the type of the arguments imposes a precondition the caller has to satisfy when calling safe functions, the set of instruction set extensions should be viewed as a precondition that the caller has to satisfy for the call to be safe. However, unlike the type checking of arguments, which can't be waived with In general, I think this issue should be analyzed from the perspective of avoiding impractical results that damage the |
Both of these statements are very similar to my own views on the matter. One can even go a step further and allow safe calling of target_feature fn's inside contexts where you know the target via a cfg macro or cfg attribute. However, I think all of this can be done as a refinement. Remember, right now Rust has no target feature at all :). See this as a first step! |
This almost sounds like having an "unsafe_{if,unless}" feature, or safety preconditions/effects. |
@eddyb yes is a bit novel because you'd have to do some control flow analysis for the feature (for reference, what I want is to have Now |
What if |
I wrote a draft. |
@hsivonen the RFC or the unresolved questions section should mention
function pointers and trait methods (e.g calling a target feature function
via a trait object).
If you phrase that as an improvement over the current proposal, you might
get away with just falling back to having to annotate those with unsafe
(that is, your RFC would improve most cases, but for function pointers and
trait methods we would need an improvement over that).
Also it might be worth to point if there are any issues in this RFC that
would make improvements like the one you propose to require a backward
incompatible change.
@est31
'if cfg_feature_enabled!("sse4.1") { ... }'
Could expand to:
'if __runtime_detection("sse4.1") #[safe(target_feature = "sse4.1")] { ...
}'
allowing us to remove the need for unsafe in those blocks (but we would
need to allow attributes in more positions).
…On Tue 7. Nov 2017 at 13:11, Henri Sivonen ***@***.***> wrote:
What you propose sounds in general like a good idea, I encourage you to
write an RFC that explores that and please ping us here once you have a
draft!
I wrote a draft
<https://github.com/hsivonen/rfcs/blob/minimal-target-feature-unsafe/text/0000-minimal-target-feature-unsafe.md>
.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2045 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AA3NpstR0Cb-f4QCHfvgQgdx25fU1O2-ks5s0EkAgaJpZM4OFlXl>
.
|
This is an RFC for
cfg!(target_feature)
and#[target_feature]
.Rendered