-
Notifications
You must be signed in to change notification settings - Fork 436
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
Add ScopeGuard
and use it in Binder.
#317
Conversation
This comment has been minimized.
This comment has been minimized.
/// | ||
/// The [`Cleaner::dismiss`] function prevents the cleanup function from running. | ||
pub struct Cleaner<T: FnOnce()> { | ||
cleanup_func: Option<T>, |
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.
This could be ManuallyDrop<T>
? On dismiss
we can call drop
, and on drop
we can call take
. This avoids extra memory.
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.
Yes, it could. But the size of the object is 1 byte, which I expect to be optimised out on optimised builds anyway.
I don't think adding ManuallyDrop
and two extra unsafe calls is worth the loss is readability here.
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.
It's indeed usually optimised out, but having a known-to-be-true if statement in the drop method still feels awkward.
(BTW it's align_of::<T>
extra bytes)
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.
It's indeed usually optimised out, but having a known-to-be-true if statement in the drop method still feels awkward.
The if
statement isn't always true, it is false when dismiss
is called.
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.
The
if
statement isn't always true, it is false whendismiss
is called.
Haha, I know you're going to say this :) I only realized that after I send out that message. Obviously you can add a forget
and it'll be always true :)
Option
it's not zero-cost if used in async fn
. We're unlikely to use async
any time soon though, so I don't feel too strongly in favor of ManuallyDrop
. I do prefer to rely as few on the optimiser as possible though.
Also, please add #[inline]
for all of new, dismiss, drop
. This helps in opt-level<=1.
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.
Also LTO probably means that we would need LLD somewhere in our pipeline? Anyway I still think we shouldn't require or assume people will use LTO.
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.
By the way, please do not take me the wrong way when I complain about stuff like this. I usually push you guys a lot (@nbdd0121 and @bjorn3) because I know you are experts on this stuff.
The idea being that either 1) you tell me how I am completely stupid (and then I learn more about rustc
/LLVM internals/limitations, which is a win for me; plus we collectively learn how to answer similar questions if they happen to be raised in e.g. the LKML); or 2) I convince you to improve Rust/LLVM in some areas and everybody wins ;-)
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.
Well, Rust does not guess whether something is suitable for inlining at all; it's all up to LLVM. Therefore for non-generic functions, cross-crate inlining requires either
#[inline]
or LTO. It's likestatic inline
function vs anextern
function.
I was thinking LLVM could give back its cost heuristics after optimizing a function, and then the frontend decide if it wants to generate a Rust generic-like function or go with a normal one etc.
Yeah, it is likely not trivial if LLVM is not setup to do that, and most likely not worth it anyway since using LTO is simpler and a more general approach. But hey, it would improve things for all non-LTO-enabled projects and remove the needs for so many hints.
They may not care that much, but requiring a function call for
&CStr
to*const c_char
conversion is not acceptable.
Yes, but what I am saying is the amount of hints we use depends on whether we say LTO is the way to go or not, and how much the overall % lost. The idea being that we could try to be better than C here and have the hints that actually matter, instead of systematically providing hints everywhere.
At the current time, the safest approach is indeed doing it manually like the C side does (providing hints, having functions in headers, using macros to hack it, etc.).
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.
Also LTO probably means that we would need LLD somewhere in our pipeline? Anyway I still think we shouldn't require or assume people will use LTO.
Yeah:
config HAS_LTO_CLANG
...
depends on CC_IS_CLANG && CLANG_VERSION >= 110000 && LD_IS_LLD
It is still experimental, but since it is also a requirement for CFI, I bet it will become way more common soon enough.
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.
Also LTO probably means that we would need LLD somewhere in our pipeline? Anyway I still think we shouldn't require or assume people will use LTO.
Rustc's LTO support normally doesn't require LLD as rustc performs it itself. Due to the way rustc is used, it can't do LTO itself though and will need linker plugin LTO. This should work with linkers other than LLD though, but requires a linker plugin for the right LLVM version rustc uses.
I was thinking LLVM could give back its cost heuristics after optimizing a function, and then the frontend decide if it wants to generate a Rust generic-like function or go with a normal one etc.
By the time codegen happens, the frontend has already emitted the metadata file.
This comment has been minimized.
This comment has been minimized.
Cleaner
and use it in Binder.ScopeGuard
and use it in Binder.
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.
LGTM, but see the doc request.
/// Runs a cleanup function/closure when dropped. | ||
/// | ||
/// The [`ScopeGuard::dismiss`] function prevents the cleanup function from running. |
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.
Please add two or three examples, e.g. a trivial one with a explicit scope for extra clarity, one with dismiss()
to show the difference and one with e.g. the ?
operator etc. in the middle (like in your use case in Binder).
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.
Added two examples.
Also please put the |
/// | ||
/// The [`ScopeGuard::dismiss`] function prevents the cleanup function from running. | ||
pub struct ScopeGuard<T: FnOnce()> { | ||
cleanup_func: Option<T>, |
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.
Not even a nit, probably closer to a code style question. Why name the field if it's the only one in the struct? Presumably you could also do:
pub struct ScopeGuard<T: FnOnce()>(Option<T>);
Is this because an explicit name provides more information than .0
and makes the code more readable?
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.
Yeah, we should decide which style to use.
One tiny advantage of non-tuple ones is avoiding tuple constructor shenanigans:
struct T(u8);
pub fn f() -> u8 {
T as _
}
I would like to have a lint for that -- from a quick look there does not seem to be one.
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.
Sometimes this is an advantage, e.g. you can use the tuple struct name in functions such as map
.
Obviously for non-public structs there's no difference though.
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.
I think a simple rule would be to avoid tuple ones unless the tuple constructor is intended to be used, e.g. for newtypes.
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.
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.
That is really shocking! This would be a great interview question to weed out people like me :)
I still don't understand where the
192_u8
comes from. See playground.
It is just the bottom bits of the address: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a36bc1e4b100255e9f2c4e5b7786d5d1
Notice 20
as the last byte in the full address.
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.
So you're saying that Rust leaks memory addresses just like that...? I'm not sure what to think about that.
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.
From my point of view, this discussion shouldn't derail @wedsonaf 's PR.
LGTM.
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.
So you're saying that Rust leaks memory addresses just like that...? I'm not sure what to think about that.
Well, it is memory-safe to do so on its own ;)
(More seriously: you are right, in the kernel we definitely want to avoid leaking addresses -- but that is a bit orthogonal to this).
From my point of view, this discussion shouldn't derail @wedsonaf 's PR. LGTM.
No worries!
(I will be logging off for the weekend myself in a few minutes anyway -- I guess Wedson will add the updated docs on Monday).
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.
Have a great week-end. Thanks for updating my understanding of Rust!
LGTM as well apart from the name |
I'm not asking to change the PR, just thinking out loud about the changes I see here. The way this new |
Signed-off-by: Wedson Almeida Filho <[email protected]>
This is in preparation for introducing additional error paths that require sending an error reply to the transaction issuer, but return a success result to the function caller. Signed-off-by: Wedson Almeida Filho <[email protected]>
Done. |
Binder has the idea of a transaction (borrowed from the C code), but it has a different meaning from the traditional "something that can be prepared and then committed or rolled-back". It's more of a 'message' between two entities with additional properties. That said, I'm curious about your idea of a transaction abstraction -- I'm all for simplifying concepts :) Would you mind sharing more about what you had in mind? |
One size definitely doesn't fit all, it depends on what you have to work with. But normally, a transaction abstraction is an object that:
|
LGTM. |
I'm glad you noticed it :) In this particular case, I thought you had had some inspiration and found a way to represent some [more complicated] code as a transaction. But I guess you were just wondering in general, which is also perfectly ok. |
Yes I had nothing more to offer than a general remark - I've never read or familiarized myself with your Binder Rust code. And that's too bad, as it's the only practical, performance-sensitive, real-world application we have at the moment... (I think what you did with Binder is great BTW) |
We can see that "bswap32: Takes an unsigned 32-bit number in either big- or little-endian format and returns the equivalent number with the same bit width but opposite endianness" in BPF Instruction Set Specification, so it should clear the upper 32 bits in "case 32:" for both BPF_ALU and BPF_ALU64. [root@linux fedora]# echo 1 > /proc/sys/net/core/bpf_jit_enable [root@linux fedora]# modprobe test_bpf Before: test_bpf: #313 BSWAP 32: 0x0123456789abcdef -> 0xefcdab89 jited:1 ret 1460850314 != -271733879 (0x5712ce8a != 0xefcdab89)FAIL (1 times) test_bpf: #317 BSWAP 32: 0xfedcba9876543210 -> 0x10325476 jited:1 ret -1460850316 != 271733878 (0xa8ed3174 != 0x10325476)FAIL (1 times) After: test_bpf: #313 BSWAP 32: 0x0123456789abcdef -> 0xefcdab89 jited:1 4 PASS test_bpf: #317 BSWAP 32: 0xfedcba9876543210 -> 0x10325476 jited:1 4 PASS Fixes: 4ebf921 ("LoongArch: BPF: Support unconditional bswap instructions") Acked-by: Hengqi Chen <[email protected]> Signed-off-by: Tiezhu Yang <[email protected]> Signed-off-by: Huacai Chen <[email protected]>
In case when is64 == 1 in emit(A64_REV32(is64, dst, dst), ctx) the generated insn reverses byte order for both high and low 32-bit words, resuling in an incorrect swap as indicated by the jit test: [ 9757.262607] test_bpf: Rust-for-Linux#312 BSWAP 16: 0x0123456789abcdef -> 0xefcd jited:1 8 PASS [ 9757.264435] test_bpf: Rust-for-Linux#313 BSWAP 32: 0x0123456789abcdef -> 0xefcdab89 jited:1 ret 1460850314 != -271733879 (0x5712ce8a != 0xefcdab89)FAIL (1 times) [ 9757.266260] test_bpf: Rust-for-Linux#314 BSWAP 64: 0x0123456789abcdef -> 0x67452301 jited:1 8 PASS [ 9757.268000] test_bpf: Rust-for-Linux#315 BSWAP 64: 0x0123456789abcdef >> 32 -> 0xefcdab89 jited:1 8 PASS [ 9757.269686] test_bpf: Rust-for-Linux#316 BSWAP 16: 0xfedcba9876543210 -> 0x1032 jited:1 8 PASS [ 9757.271380] test_bpf: Rust-for-Linux#317 BSWAP 32: 0xfedcba9876543210 -> 0x10325476 jited:1 ret -1460850316 != 271733878 (0xa8ed3174 != 0x10325476)FAIL (1 times) [ 9757.273022] test_bpf: Rust-for-Linux#318 BSWAP 64: 0xfedcba9876543210 -> 0x98badcfe jited:1 7 PASS [ 9757.274721] test_bpf: Rust-for-Linux#319 BSWAP 64: 0xfedcba9876543210 >> 32 -> 0x10325476 jited:1 9 PASS Fix this by forcing 32bit variant of rev32. Fixes: 1104247 ("bpf, arm64: Support unconditional bswap") Signed-off-by: Artem Savkov <[email protected]> Tested-by: Puranjay Mohan <[email protected]> Acked-by: Puranjay Mohan <[email protected]> Acked-by: Xu Kuohai <[email protected]> Message-ID: <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]>
This is in preparation for introducing additional error paths that
require sending an error reply to the transaction issuer, but return a
success result to the function caller.