-
Notifications
You must be signed in to change notification settings - Fork 277
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
Improve handling of immediate mode arguments #248
Comments
@alexcrichton I have lifted this issue to the rust-lang/rust repo. I think that handling immediate mode arguments properly is something we should attempt to fix before stabilization somehow. That is, either we get |
Thanks! I'm not sure I 100% agree we should block stabilization, but we can always sort out these details later :) |
I guess my thoughts are more aligned with either:
Both choices probably will mean that the strongly-typed SIMD wrapper over stdsimd won't be able to offer the same guarantees that stdsimd does (e.g. the wrapper will need to use the The idea of doing this in the compiler is what @eddyb basically mentioned in #190 . |
Oh right yeah I should mention that if we had a mechanism I'd prefer to stabilize these with "requires a constant semantics" but I'm under the impression that such a mechanism would be pretty far off, and in the meantime our local workaround isn't too bad. |
Oh I thought that we had a mechanism for this in the compiler because the prefetch intrinsic (and others) require this, but it looks like the
(on clang its just I don't think adding @eddyb ? |
We do have the mechanism but it's specifically only used for shuffles right now. Checking/promotion-to-guaranteed-constant: https://github.com/rust-lang/rust/blob/07ea2604075d6f896addce0e6949c7cf25dd3715/src/librustc_mir/transform/qualify_consts.rs#L760-L768 (oh come on GitHub... no pretty code?) Custom lowering of MIR call arguments (this is only needed because arrays are not immediates - for most of the usecases you care about, special-casing this is unnecessary): https://github.com/rust-lang/rust/blob/07ea2604075d6f896addce0e6949c7cf25dd3715/src/librustc_trans/mir/block.rs#L521-L539 So it'd only be a few more lines to add more intrinsics to the checking/promotion side. |
@eddyb do you think this could be implemented as a stdlib-internal function attribute (e.g. that specifies the argument name) or as a function argument attribute (these are not a thing, right?) ? That way we could keep doing things in the library without having to mess with |
@gnzlbg The problem as I see it is that |
@eddyb do you know how easy it would be to experiment with an attribute like this? (aka how hard it would be to implement it) I'm sort of also worried about things like: let f: fn(..) = stdsimd::vendor::function_that_requires_constant; In a situation like that, we presumably want to error, right? Are there other locations in the compiler that will be affected by such "this argument must be a constant" logic? |
@alexcrichton Depends if you want to apply on just the intrinsic or also on some wrapper function and have that wrapper function monomorphized. The latter is harder and basically const generics. |
@eddyb in our case it's all wrapper functions (which transitively call the intrinsic). But ok thanks for the info! |
Oh, sorry, I was getting confused between the intrinsics and the wrappers. There isn't much to do there before |
I've opened up rust-lang/rust#48018 with @eddyb's strategy from above which adds a new unstable attribute which can guarantee that arguments to a call are constant. That I think is the "biggest hole" today but there's still two pieces remaining:
|
Aside from function pointers, there's also the |
In the mean time this might be a solution that we could apply after stabilization: https://internals.rust-lang.org/t/pre-rfc-const-function-arguments/6709/22 |
In aes intrinsics (and probably some others as well) |
@newpavlov We map C's |
On x86-64, stdarch contributes now 56 MB out of 76 MB of libcore.rlib size. It is also responsible for more than doubling the size of libcore.rlib since rustc 1.47 rust-lang/rust#80245. Most of this can be directly attributed to the current handling of immediate arguments combined with support for new intrinsics (for example moving functions calls out of macros reduces rlib size by 9.8 MB in #973). |
The proper solution would be to turn these intrinsics into const generics (with some syntax sugar) so that each variant is instantiated on demand. |
It's probably of note that |
@alex |
Note that you are using a feature that was never meant for the way it is being used right now. It was literally just meant for being used on the simd_shuffle intrinsic argument. Using it on functions works by accident and afaict was never RFCed or discussed with the lang team or const eval team.
Which I am very unhappy about, but I guess that ship has sailed. I still think we should deprecate them and move to const generics, which exist exactly for the use case of having a function that is generic over a constant value, but that's just my personal preference. I have no idea at what point these promoted function arguments will break down on us, but their implementation is totally wonky and was not meant for anything but plain integer literals. |
I don't think we should do a breaking change. We can keep them as they are, but I think we should have new functions using const generics (which are fully specified and soon stable) and mark the other ones as deprecated. My problem is with the promotion that happens without any syntactical obviousness, is completely underspecified (does it allow generics, does it allow const fns, ...) and not actually RFCed by any team. In addition, the way it is specified right now, there is no way to know inside the function that the argument is a constant, which circumvents analyses like If you want this in argument position, we can do something like fn foo<const N: u32>(x: i32, y: ConstU32<N>) {} which is then called as foo(42, ConstU32::<99>) A macro could make it less verbose to construct such values, or we could consider adding a way to allow user-defined construction from literals, at which point |
@oli-obk Yes, some may not like that const arguments could be used for writing methods like |
because it is entirely unclear whether the accidentally stabilized const arguments in this crate will ever have a stable counterpart, and I don't find "we accidentally stabilized an underspecified and broken version of it" a motivating enough reason to stabilize it. Sure we can keep this until there's a decision on the proposal, but in the years that we'll work on getting that to stable, we should limit the arbitrary usage of these APIs. I am very unhappy about this extensive usage of a EDIT: Just to be clear: I am annoyed at myself (and not anyone else) for not realizing that there was a plan to stabilize stuff in |
I don't recall proposing this... though I do agree that this seems like the "logical next thing", then -- but it goes directly against the Rust design decision to clearly separate static and dynamic arguments.
We could reject anything but integer literals and named constants -- but I assume that would be a massive breaking change.
How hard would it be to compile them to inline const expressions? At least then we can avoid relying on promotion. It would expand the scope of what is accepted in these arguments to "all const-expressions", but that might already be pretty much what explicit promotion checks? |
I don't think anything else is used much or even at all. |
Okay, let's see what crater says: rust-lang/rust#80759 |
The fact that these intrinsics were stabilised seems like an abuse of the RFC process, as they require a new language feature that was never properly discussed in public, nor explicitly approved. I strongly agree that const arguments should be deprecated, as they're a hack that was a stopgap before const generics were ready for use. Whether one prefers the syntax or not is irrelevant: new language features should be approved via the RFC process. It seems like it would be very easy to define macros that act like const argument functions, modulo the
I do think something has gone wrong with the process. This should have been a much more visible change to people not keeping up with the intrinsics work. |
I'm happy to block all new uses of The question is, what should we do about the functions which are already stable? I can think of two possibilities:
|
I agree the former leaves much to be desired. Perhaps something to the effect of the second option could be added to the 2021 edition planning document.
Presumably |
Following the discussion in rust-lang/rust#76001, one possible option (maybe just an intermediate solution) would be to turn |
My concern with inline consts is that they don't support referring to generic parameters of the containing |
They don't support this yet, but support is planned (though @lcnr also has some plans about const-eval-well-formed things here that might make this less ergonomic than explicit promotion). |
The size of The largest remaining wrappers are for avx512f intrinsics. Using MIR dump as a proxy for size in rlib, their size often exceeds 1MB, while most have merely a few kB. |
I'll focus on avx512f.rs today, some of the constify macros it uses are huge. |
Has a decision been made by the lang team? I believe that stabilization of "template"-based intrinsics should be blocked on this decision, otherwise we may end up with two incompatible ways of doing the same thing. Also I want to note that AFAIK Rust has explicitly choose to follow vendor intrinsic APIs as close as possible, even though in some cases it makes sense to deviate from them (e.g. by using unsigned types instead of signed ones). Using "template" arguments for immediate mode arguments goes against this choice and I believe will only result in confusion down the line. |
I don't think so, but it's also not really a problem since we are already using the non-generic version. Using a generic version is entirely in the space of the libs team as far as I can tell. So when a PR to bump stdarch in the rustc repo is opened, we can require a libs team FCP on it. |
Now that #1022 is resolved, what is still being tracked here? |
This is done. |
Many intrinsics take immediate more argument (that is, some arguments must be compile-time constants).
We currently implement them by taking these arguments as run-time values, and "converting" them to compile-time values using a match statement with a match arm for each particular case:
If a user pass them a compile-time value the optimizer can select the appropriate match arm and remove the rest of the code.
Passing them a run-time value should generate an error, but with this approach the compiler silently accepts this code, and generates code for all match arms, potentially making binary sizes / compile-times explode.
Even when the user passes a compile-time constant to the immediate arguments, some intrinsics (AMD loves these) require match statements with >= 65k arms that destroy compile-times in all cases (from 2x to 3x increase in compile-times of the whole
stdsimd
test suite if one of this functions is used somewhere). The following intrinsics have such extremely large match arms:sse4a::_mm_extracti_si64
sse4a::_mm_inserti_si64
tbm::bextri
This issue blocks completing SSE4a in #40 and fixing #26 . It was previously discussed in #190 but never got an issue of its own.
Amongst the proposed solutions were to either implement these in the compilers or wait till const generics + extensions arrive (e.g.
foo(x: const i8)
to requirex
to be a compile-time constant).The text was updated successfully, but these errors were encountered: