Change:
contains architecture-specific vendor intrinsics, some of which have arguments that are required to be compile-time constants. This is tricky because LLVM will often assert/ICE if a constant is not passed into the LLVM intrinsic at the LLVM IR level. This was previously handled by using a statement to manually monomorphize the intrinsic: The downside of this approach is that it generates huge MIR and severely bloats the size of to the point where it actually affects compilation time. The attribute was used to restrict arguments to constants so that we could eventually replace this with const generics. Note that it effectively only acts as a hard lint, the is still needed to manually monomorphize the LLVM intrinsic. All the intrinsics in have been converted to use the newly stabilized const generics: To preserve backwards compatibility with the already stabilized intrinsics using , a new attribute was in which rewrites function calls of the form to . This new attribute is not intended to ever be stabilized, it is only intended for use in as a replacement for . allowed constant expressions derived from generic arguments in the caller, but this is not allowed with const generics. This technically makes this a breaking change. Another potential breaking change is that the intrinsics now use a post-monomorphization to validate constant arguments. Previously, intrinsics would either use runtime asserts or simply pick an arbitrary default behavior for out-of-range constants. Do we want to force all callers to use the const generics syntax in the 2021 edition? Pros: It makes it clearer that these intrinsics expect a constant as an argument. Intrinsics are rendered with the const generics syntax in rustdoc. Cons: We will still need to support the argument rewriting anyways for 2015/2018 code. Intrinsics no longer have the same function signature as the original C vendor intrinsics.
Apparently the implementation of this so far reduces the size of libcore from 58MB to 40MB, which is still pretty substantial. Is this an acceptable size? Is there reason to think this will shrink further in the future?
All of the functions have been converted to const generics, so I don't expect any further shrinkage.
The is around 17 MB now.
That size comment was about the size of , not per se, and was before we had converted the huge AVX512 intrinsics: the latest size I saw during conversion was around 16MB.
In the Edition 2021 meeting, mentioned that the libs team had this idea as a compromise: New edition only accepts proper rust syntax (2nd one) But produce a warning with a machine applicable fix from the 1st one
We discussed this in today's meeting. In today's discussion, there was some debate about this migration, particularly since we already need to maintain the mechanism indefinitely to support existing code. We would, ideally, love to get some feedback from existing users of the SIMD intrinsics, perhaps by asking the maintainers of crates that use intrinsics such as ; perhaps a search through for occurrences of any of the intrinsics requiring a const parameter could turn up such usage. We'd also love to get some feedback from developers of tools that operate on Rust code, regarding the difficulty of supporting this. On balance, we may potentially want to support this on an ongoing basis, in order to match the vendor intrinsics documentation, and in particular the argument order. There was concern that people reading such code would find that code harder to follow if the arguments in a different order. The possibility also came up (though there was not consensus on it) of providing a macro for each such intrinsic, and rustfix-ing code using the function to use the macro rather than using const generics syntax.
A macro seems like the most principled way of doing this, especially considering that these are also implemented via macros in C. It would resolve the tension between wanting these APIs to look idiomatic vs wanting them to exactly mirror the vendor documentation. That sure would be a whole lot of macros, though...
I personally think that we should use the 'legacy' attribute on all intrinsics (both existing and potentially ones in the future) that need the compile-time generic argument. The intrinsics API has been designed to match the platform API (in particular, usually the one available in C), and the choice was explicitly made to note deviate from the vendor intrinsics in naming or calling convention. It would be odd, IMO, to change that choice in the case of the subset of intrinsics which need compile-time arguments. I think that these intrinsics are already pretty hard to read and changing the argument order by pulling out compile-time arguments is likely to be relatively painful for users, particularly if trying to align C and Rust code. And since we have to support the legacy syntax on some intrinsics, I think it would be preferable to support it on all intrinsics, to avoid some having one API and some having a different API. Splitting the intrinsics into two groups just based on their stabilization date seems quite strange and not easily learnable by users. I also think adding macros here may not necessarily be a bad idea, but it would be a lot of macros, and these intrinsics are not all that different compared to other intrinsics from the caller's perspective AFAIK -- Intel just made the choice to not embed the compile-time constant in the name of the intrinsic, as is done for some of the other implicit constants. Deviating for some intrinsics to macros seems like it needlessly complicates the interface; and moving all intrinsics to macros feels pretty obviously the wrong choice, and not very Rust-y. It was proposed that we deprecate (i.e., with a warning) the non-const-generic invocation (i.e. where all arguments are given inside the parens, including the compile-time ones), which I think would cause needless churn -- there's not really anything wrong with the way these intrinsics have been called up until now. Plus, I think there's some possibility that we may eventually want to add support for passing const values as function parameters (which would desugar to generics), similar to how we have -- if we do, then there would be a potentially much smaller surface area of 'special case' by these legacy attributes, and potentially they'd be entirely removable. While I don't think we should try to design that now, I do think it's meaningful to say that the idea is not entirely unlikely. One thing I did think of is that we may want to consider that eventually the function-argument position for const generics may want to gain a block around it to avoid an implicit promotion context that may otherwise exist. That may mitigate some of the concerns we had around tooling supporting in the meeting.
That may not be entirely true; I have heard that the magical nature of these functions is a pain point for the IDE use case.
I'd like to hear some folks who are creating tooling weigh in on that point. I was saying in the meeting that it seemed like two sets of people are affected by this: those using the intrinsics; tool makers, who have to consider all the edge cases of the language. For most other folks, it hardly matters. One other thing to consider is that we may want to support this use case natively at some point. I could imagine supporting something like or -- perhaps closer to what is being proposed here -- Obviously i don't want to block progress on some wild new features, it's just that I can imagine going the other way, and making this feature be something that people can use in all APIs to improve inference and make them more ergonomic.
That said, having let this sit, I'm somewhat inclined to get stricter in Rust 2021, and we could always get loose again. But I would very much like to hear from affected users to have a sense of whether it will be a pain. I agree with that trying to make the intrinsics match the documentation precisely was a design goal, and it does seem like a bit of a shame to give it up, especially given that we have to have the backwards compatibility code anyway. (On the other hand, if we get stricter in Rust 2021+, we may find we can remove that code at some point.)
This is remarkably convenient in RISC-V's vector V instruction set. V set contains types and functions where it would not be clear to just make a huge list for all of them.
can you elaborate on that? Do you mean specifically that being able to invoke the functions without using "turbofish syntax" is remarkable convenient?
Hello. Maintainer of . It covers all safe-able Stable intrinsics. As people have noted, many functions have been exposed via macrorules. Just this past weekend I've been updating it to use minconst_generics. My impression is that the basic code readability is going down the more I convert and update things to the const generic version.
and therefore you might prefer to keep the "in-place call" notation?
Yeah. The big problem is that turbofish is cute and I'll fight anyone who tries to remove it, but also it's cute because it's rare. Having to put a turbofish on every single call to a function quickly becomes a chore, and so far I'm not even writing "real usage code", just converting doc tests to integration tests. That said I will continue to convert the crate to minconstgenerics to not use macrorules because for the purposes of wrapping unsafe code a function call is a much sturdier thing to trust than a macrorules expansion (even if you think you probably really locked down that macro). However, the long term "what makes const generics good to use with function calls?" solution will essentially require that the const value be passable within the parameters instead of as a turbofish. Anything less just reads super poorly for functions in a way that doesn't happen as badly for types because types can infer or be trivially aliased, and functions not so much.
This is roughly my opinion. I think we're going to want this feature sooner or later in some form. That is partly why I'm ok leaving this hack in for the time being.
The language team discussed this in the last triage meeting (8 days ago). The feeling was that additional feedback is going to be hard to source without active investment, and the status quo is quite reasonable to leave until the next edition (e.g., 2024). Indeed, some people in the room (e.g., me) felt that the legacy syntax is better, and should be preferred; feedback since then from continues to agree with this. So, the current (meeting) consensus in the lang team was that we'd un-nominate this issue for the time being. We didn't feel there was a need to take action before the 2021 edition to deprecate and/or remove this pattern; it seemed fine to continue with the status quo. However, one thing I'm not realizing we did not discuss, and seems like we may want an FCP for, is whether the legacy attribute should be to not-yet stabilized intrinsics, or kept strictly to things that we cannot change due to backwards compatibility concerns. There was some discussion a few weeks back on this, but I don't believe there were firm conclusions from that discussion (even in the room). -- I think an async FCP may be good here, but we may want some discussion as to which of these options to prefer. Libs may also have an opinion here. exact status-quo. Do not stabilize any new intrinsics/functions using the legacy attribute, which enables both const-generic call syntax (i.e., turbofish) and parameter-as-const-value call syntax. intrinsics stabilized with the legacy attribute. (1) is, in my opinion, not really coherent. We don't gain anything by refusing to add more legacy attributes -- there are already 1424 instances of the legacy attribute on master stdarch (though not all may be on stable functions, I'd need to do more work to check that). We do gain overall cohesion with (2), though, as users can always use the same syntax to call any intrinsic. I wouldn't suggest expanding that to more general std/core functions (e.g., iterator methods) at this time, but intrinsics are always a bit special and IMO it makes sense to have a uniform calling syntax there (well, two alternatives, but still).
I agree with (2). All of stdarch should use the same legacy attribute for consistency across the module. As was mentioned earlier in the thread: in a few years no one will care what historical order the intrinsics stabilized in as an explanation for an inconsistency, they will just see the inconsistency and consider it an annoying weird rule.
I also agree with (2), for the obvious reasons.
We also discussed this in our Edition 2021 meeting. We were considering removing this from the list of 2021 Edition candidates as a decision doesn't appear to be even close and our deadline has long since passed, but we were saying that we might want to have some more explicit decision from the teams to leave things as they are for Rust 2021 at least. That said, I'm not sure that an FCP makes sense, since this seems to be the status quo, and it's more that an FCP would be required to prevent it. I will say that I personally am inclined against making changes here. I think the current form is more ergonomic and rather than forcing people to migrate, I would like to consider whether we can rationalize the form so that more functions can enjoy this property (as mentioned). I would be happy to revisit the question after more progress on const generics and so forth has been made.
I guess that thinking more about this, there is a decision to be made, to expose the new forms to the users. There is also a bit of a backwards compatibility question, in that it's unclear whether we wish to support form (as opposed to, say, or ). Supporting the form where the parameter is not provided at all somewhat constraints what future language developments might look like (I'm imagining, for example, that we might have a parameter whose "type" indicates that it is an "integer that equals the constant C", sort of like , in which case passing a value for would probably be mandatory). So here are the range of behaviors we might choose: form on stable but keep unstable (my preference). both and on stable (not my preference, as I believe it interferes with possible future developments). form only on Rust 2018 and earlier, and require in the future (not my preference because it seems strictly less ergonomic, also may present a problem if we try to rationalize it later, so likely to live on indefinitely as a special case). There is also the "almost orthogonal" question of whether to apply the above policies only to existing intrinsics or also to newer ones. I'm inclined to be consistent across all SIMD intrinsics, regardless of when they were , unless we opt for option 3 (in which case we are already migrating folks). I think the main argument in favor of 3 is that it keeps the overall language smaller. This argument assumes we would never want to have a way to invoke functions with const generic arguments inline, but I am not convinced this is true (and I consider it quite likely to be false). Random observation that you can already write code like this (): so it's not a huge stretch to imagine something like working somehow.
I've watched the 2021-04-06 lang meeting discussing this topic, and there was no mention of the fact that this is technically a breaking change, as mentioned in the open questions/issues in the OP. A crater run was done in , with only around 4 root crates regressing, and overall 50 regressions. 2 of these 4 root regressions were fixed already, and semver-compatible versions released. A PR was opened for the 3rd. Is this a concern for t-libs, a concern for t-lang, or not a concern given those crater results ?
I was under the impression that the breaking change was not expected to occur in practice, but I guess this is not entirely true. UPDATE: This is the source of the breakage?
That is the source of breakage for 2 of the crates. The other 2 were due to a missing on one of the previously stabilized intrinsics.
I see. Does that mean that the const is not, in fact, required by the underlying LLVM intrinsic? Or was it that the optimizer was able to eventually figure out that the value is constant?
I don't know about LLVM's behaviour for the intrinsic here, but the one case I fixed (the 2 already-fixed crates were the ones using the 2 intrinsics missing the attribute) was a simple shift with a constant, which I've seen LLVM optimize easily before (and I wouldn't be surprised if it saw the value as constant indeed).
I did a bit of research on that specific intrinsic: the argument of must be a constant, but GCC, Clang and ICC all allow specifying a runtime value, in which case it is internally converted to use which takes a vector argument that holds the shift value. Note that the SSE2 variant of this instruction () does have .
is a shift with a constant not a constant expression?
yes, but to be clear it is the lack of in the 2 stable intrinsics which allowed this case to happen (not the switch to const generics): by using a custom type and impl where it called the intrinsic, this turned the constant used in the shift expressions into a runtime value in the impl.
I admit I don't really understand. Is there a good link to the error etc?
So there are a couple sources of breaking changes in the bump that have been hit in the crater run: one is related to using associated consts of generic parameters the other one is that a few shift-related AVX2 intrinsics were stabilized without the attribute that ensures the arguments documented as immediates are indeed constant values. Two of those were hit in the crater run, and I'll explain those below. noticing and fixing this by updating these intrinsics to const generics: they the attribute which for all intents and purposes in this explanation is the same as if they had only the missing attribute. of the difference on stable: the AVX2 example compiles even though the shift amount is not a constant value, while the SSE2 version only compiles when passed a constant argument. (Amanieu explained above why it can also run fine). The crater cases were very similar to these examples: different versions of : which caused for example on: in the impl of their custom vector type and which caused also uses the same technique as used to (before the switch to const generics) to ensure the arguments are effectively constant: a and so was to use this for the intrinsic that should have required it. does not use such a heavy trick (and funnily enough the author had noticed the discrepancy between their SSE and AVX code implementations, only the AVX code path could have that impl). Since their only use of this custom operator was it was easy to remove. I've seen LLVM excel in optimizing similar mathematical expressions with constants before, even with custom operators like did, and that could explain why the crates work fine today. But it seems that even if/when that's not the case, Amanieu's investigation shows that at least some of these intrinsics were made to handle runtime values instead of only constant values as one would expect from the official docs.
For the related question of "should Rust have a first-class feature to allow const generics to be passed as regular parameters", I've opened a Zulip thread to discuss: https://rust-
I think there are two remaining issues that need to be addressed before this reaches stable: We've decided to continue allowing these intrinsics to be called with the old syntax in the 2021 edition. Should we also allow calling these intrinsics with the const generics syntax? Should we change rustdoc to render the const generics differently when is present?
This has landed just after the recent beta cut, right? So it'll reach stable in 11 weeks, on July 29th?
I'm inclined not to support it, to give ourselves more room to design a first-class feature around this.
I was briefly very confused by the term "intrinsics" here. These are not intrinsics in the rustc sense, are they? They are just regular functions? The true intrinsics are an internal implementation detail of stdarch, not visible to the user? Might be better to not call them "intrinsics" then in the rustc issue tracker...
Another example of code that compiles on stable but not on nightly:
In particular it would be consistent for a hypothetical const-param-in-argument-position to act similarly to impl-trait-in-argument-position, where can't be invoked as (arguably this restriction is controversial, but it can always be lifted later).
All the rust docs call them arch intrinsics, cpu intrinsics, or vendor intrinsics. It's simply the term for them by now, that train has long left the station.
"arch intrinsics" is much less confusing than just plain "intrinsics". :) I assume when reading the issue front-to-back this is clear from context, but when coming back to this discussion after weeks and just reading the last 2 or 3 messages, being more explicit helps.
We checked for this in crater and only 2 crates were affected. Both have since been fixed to work around this issue.
Ah, and I guess those failures would be caught even by the check-only run.
I think there should be some indication that the "legacy" call signature is allowed. Perhaps even defaulting to that rendering would be preferable, given that it matches both (a) historical practice in Rust and (b) the convention in C or C++ when invoking these intrinsics. given that this is in 1.54 which ships in 2 weeks, do we think we can create a patch that adds some indication to the rendering by then? Right now a page for these intrinsics (e.g., ) doesn't give any indication that both calling modes are supported, which seems quite confusing for any user coming across a call in the old convention. Is it intentional that e.g. references "imm8" as the name of the parameter, but the const generic has now changed this to ? I am surprised to see us rewriting Intel's docs and names, given that the idea with x86_64 intrinsics was to try and stay "as close as possible" to the canonical names from the intel docs...
Maybe ... what kind of UI do you expect here?
I think there's a couple options: them as-if they were still in the old format (i.e., re-inline the const generic, perhaps as something like rather than ). a box like the unstable/target feature/etc with a note like "this function is also callable with the generic inline with the arguments" it's not very clear how to indicate which argument with this form. Maybe something like "with the const generic as the 5th argument" could work, though. I'm likely at least a little biased, but since I recall some expression from some members of in meetings on this of potentially wanting to add the "not genericy" name as a stable alternative/feature instead of the #[rustclegacyconst(...)] attributes, I'd probably roughly suggest option one. Maybe folks on T-libs or T-lang have opinions though.
a really cool way to do this would be to have this annotation, and you can click it to swap the signature.
Let's try to keep the UI as simple as possible (even though that sounds good). :laughing: Once we've reached a consensus on the what the UI should look like, I can send a PR if you want.
Note: Intel only uses the name, and documents its acceptable width in the documentation (it's not clearly spelled out there, and has to be inferred from text or the pseudo-code), and explicitly in the guide's xml data like so: .
I was not aware of that context, thanks -- in that case I think the names seem like an improvement indeed. Maybe we can put a quick section in the root (e.g., and ) that mentions that the names of immediate parameters describe their widths (imm1 -1 bit, etc.)? Could help readers who are broadly unfamiliar otherwise. I would still like to see improvements to the rendered documentation, though.
Rust 1.54 now supports both and . Was it an explicit decision to stabilize the latter? Judging by comments I was expecting that only the former will be stable for the time being. Release notes do not contain any mention of this. I hope it was not an accidental stabilization...
The current documentation is problematic. I was just bitten (cryptocorrosion/cryptocorrosion) by using which the docs say has been stable since 1.27, but the documented syntax is actually much newer than that; only the original syntax, which is not referenced in the documentation, has that level of compatibility.
I think we should at the very least change rustdoc to render the function using the old syntax instead of the new one. How difficult do you think this would be? We essentially need generics to appear as normal arguments in rustdoc for functions marked with .
Without checking too much, I think it wouldn't be too complicated. To be confirmed. I'll work on it in the following days.

Discussion:
} else { hir::Constness::NotConst }; <ins> clean_fn_decl_legacy_const_generics(&mut func, attrs); </ins> FunctionItem(func) } } } <ins> /// This is needed to make it more "readable" when documenting functions using /// `rustc_legacy_const_generics`. More information in /// <https://github.com/rust-lang/rust/issues/83167>. fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[ast::Attribute]) { for meta_item_list in attrs .iter() .filter(|a| a.has_name(sym::rustc_legacy_const_generics)) .filter_map(|a| a.meta_item_list()) { for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.literal()).enumerate() { match literal.kind { ast::LitKind::Int(a, _) => { let gen = func.generics.params.remove(0); if let GenericParamDef { name, kind: GenericParamDefKind::Const { ty, .. } } = gen { func.decl .inputs .values .insert(a as _, Argument { name, type_: *ty, is_const: true }); } else { panic!("unexpected non const in position {}", pos); } } _ => panic!("invalid arg index"), } } } } </ins> impl<'a> Clean<Function> for (&'a hir::FnSig<'a>, &'a hir::Generics<'a>, hir::BodyId) { fn clean(&self, cx: &mut DocContext<'_>) -> Function { let (generics, decl) = enter_impl_trait(cx, |cx| {

if name.is_empty() { name = kw::Underscore; } <del> Argument { name, type_: ty.clean(cx) } </del> <ins> Argument { name, type_: ty.clean(cx), is_const: false } </ins> }) .collect(), }

.map(|(i, ty)| Argument { name: name_from_pat(body.params[i].pat), type_: ty.clean(cx), <ins> is_const: false, </ins> }) .collect(), }

.map(|t| Argument { type_: t.clean(cx), name: names.next().map_or(kw::Empty, |i| i.name), <ins> is_const: false, </ins> }) .collect(), },

crate struct Argument { crate type_: Type, crate name: Symbol, <ins> /// This field is used to represent "const" arguments from the `rustc_legacy_const_generics` /// feature. More information in <https://github.com/rust-lang/rust/issues/83167>. crate is_const: bool, </ins> } #[derive(Clone, PartialEq, Debug)]

args.push_str(" <br>"); args_plain.push(' '); } <ins> if input.is_const { args.push_str("const "); args_plain.push_str("const "); } </ins> if !input.name.is_empty() { args.push_str(&format!("{}: ", input.name)); args_plain.push_str(&format!("{}: ", input.name));

<ins> #![crate_name = "foo"] #![feature(rustc_attrs)] // @has 'foo/fn.foo.html' // @has - '//*[@class="rust fn"]' 'fn foo(x: usize, const Y: usize, z: usize) -> [usize; 3]' #[rustc_legacy_const_generics(1)] pub fn foo<const Y: usize>(x: usize, z: usize) -> [usize; 3] { [x, Y, z] } // @has 'foo/fn.bar.html' // @has - '//*[@class="rust fn"]' 'fn bar(x: usize, const Y: usize, const Z: usize) -> [usize; 3]' #[rustc_legacy_const_generics(1, 2)] pub fn bar<const Y: usize, const Z: usize>(x: usize) -> [usize; 3] { [x, Y, z] } </ins>