-
Notifications
You must be signed in to change notification settings - Fork 566
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 local-only preprocessor cache AKA "direct mode" #1882
Conversation
@Alphare could you please write a trivial PR (typo fix, spaces, etc?) so that I don't have to approve the CI runs in the future? :) |
Ah sorry, not used to working with Github too much, Mozilla's workflow even less. Will do quickly, thanks for the heads up. :) |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1882 +/- ##
==========================================
+ Coverage 29.70% 29.81% +0.10%
==========================================
Files 50 51 +1
Lines 18022 19128 +1106
Branches 8703 9237 +534
==========================================
+ Hits 5354 5703 +349
- Misses 7491 7960 +469
- Partials 5177 5465 +288
☔ View full report in Codecov by Sentry. |
f015f48
to
8b0671b
Compare
Aside from a couple of grunt-work TODOs, this should be good enough to review. I'd love to get some feedback while I address those + add the user-level docs so that we don't get too much round-trip lag. :) |
src/config.rs
Outdated
}; | ||
|
||
if env::var("SCCACHE_DIRECT").is_ok() && env::var("SCCACHE_NODIRECT").is_ok() { | ||
warn!("Both `SCCACHE_DIRECT` && `SCCACHE_NODIRECT` are defined"); |
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.
warn!("Both `SCCACHE_DIRECT` && `SCCACHE_NODIRECT` are defined"); | |
warn!("Conflicting options: Both `SCCACHE_DIRECT` && `SCCACHE_NODIRECT` are defined"); |
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.
Imho we should prefer a single env var name and use its value.
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 works for me. I don't think accepted boolean values are unified across sccache. Usually (1
, on
, true
, 0
, off
, false
), but maybe we want to be stricter (and complain when we don't get a valid value)?
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 there was a clap extension / or impl. to handle the different variants which should be used for all of these features long term, but that'd be a breaking change. Adding new features / envs being interpreted should assume that uniform version https://docs.rs/clap/latest/clap/builder/struct.BoolishValueParser.html , at least that would simplify behavior.
@@ -0,0 +1,743 @@ | |||
# 0 "tests/test.c" |
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.
would it be possible to have a clang preproc file too ?
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.
For this particular test, preprocessor output was identical. I'm not exactly sure how complex the test would need to grow to get a different preprocessor output. Does anyone have an example? I'll try to look into it if not.
Is it possible to make direct mode work of storage backend like |
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 code quality and documentation is very good! There is a range of minor nits that should be addressed before merging.
Thank you for your PR!
src/util.rs
Outdated
/// plus MAX_HAYSTACK_LEN bytes of the previous chunk, to account for | ||
/// the possibility of partial reads splitting a time macro | ||
/// across two calls. | ||
previous_small_read: Option<Vec<u8>>, |
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.
These changes should go in the previous commit rather than this one. I wonder if it wouldn't be simpler to use a single buffer instead of overlap_buffer + previous_small_read. (I'll also note an Option<Vec<T>>
is kind of pointless if you don't expect there to be a Some(vec![]).
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.
These changes should go in the previous commit rather than this one.
Good catch.
I wonder if it wouldn't be simpler to use a single buffer instead of overlap_buffer + previous_small_read.
IMO it would make the logic harder to read.
(I'll also note an
Option<Vec<T>>
is kind of pointless if you don't expect there to be a Some(vec![]).
True. While it doesn't matter in terms of performance, I'll switch to just a Vec<u8>
, should make the code simpler.
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.
IMO it would make the logic harder to read.
I'm not sure it would. I'm having a hard time convincing myself that the current one is correct with all the dancing around between both buffers.
@@ -173,7 +173,7 @@ impl Storage for DiskCache { | |||
) -> Result<()> { | |||
let key = normalize_key(key); | |||
let mut buf = vec![]; | |||
preprocessor_cache_entry.write(&mut buf)?; | |||
preprocessor_cache_entry.serialize_to(&mut buf)?; |
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 weird that you do a mix of amends and additional commits to fixup things.
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, sorry, I usually always to amends, but I was starting to take too much time to do history editing. I should probably stick to new commits in this case, unless otherwise asked (like with the time macros thing above).
src/compiler/gcc.rs
Outdated
@@ -435,6 +437,11 @@ where | |||
}, | |||
}; | |||
|
|||
too_hard_for_direct_mode = match arg.flag_str() { | |||
Some(s) => s == "-Xpreprocessor", |
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.
We should probably add -Wp in ARGS. Probably something like take_arg!("-Wp", OsString, Concatenated(','), PreprocessorArgument)
I guess you could put this check for -Xpreprocessor/-Wp where ProcessorArgument are handled, rather than for all args. Then there's the question about the second loop for Xclangs, and, how @-files are handled with direct mode.
@@ -560,7 +560,7 @@ mod test { | |||
let mut buf = vec![0; HASH_BUFFER_SIZE * 2]; | |||
// Make the pattern overlap two buffer chunks to make sure we account for this | |||
let start = HASH_BUFFER_SIZE - MAX_TIME_MACRO_HAYSTACK_LEN / 2; | |||
buf[start..start + b"__TIMESTAMP__".len()].copy_from_slice(b"__TIMESTAMP__"); | |||
buf[start..][..b"__TIMESTAMP__".len()].copy_from_slice(b"__TIMESTAMP__"); |
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.
Could me amended into the commit where you add these tests.
src/compiler/c.rs
Outdated
@@ -884,7 +884,7 @@ fn remember_include_file( | |||
|
|||
let original_path = path; | |||
// Canonicalize path for comparison; Clang uses ./header.h. | |||
if path.starts_with(b"./") { | |||
if path.starts_with(b"./") || path.starts_with(br".\") { |
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.
In theory a ".\foo" file could literally exist on Unix, and that would break in that case.
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'll special case this for Windows, thanks.
(For some reason Github doesn't want me replying to this comment, so I'll do it here)
Sounds good
Looking at
IIUC, |
da230ae
to
6dbf004
Compare
This last push should fix the clippy issue. I think I've addressed your comments. |
src/compiler/c.rs
Outdated
// Canonicalize path for comparison; Clang uses ./header.h. | ||
#[cfg(windows)] | ||
{ | ||
if path.starts_with(br".\") { |
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 fun thing about windows is that it can also use forward slash as a path separator.
| Some(PreprocessorArgumentPath(_)) => &mut preprocessor_args, | ||
| Some(PreprocessorArgumentPath(_)) => { | ||
too_hard_for_preprocessor_cache_mode = match arg.flag_str() { | ||
Some(s) => s == "-Xpreprocessor" || s == "-Wp", |
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 that it would make a huge difference, but both of these are PreprocessorArgument, you could put the check behind PreprocessorArgument rather than all the PreprocessorArgument*.
src/compiler/gcc.rs
Outdated
@@ -2054,5 +2056,12 @@ mod test { | |||
o => panic!("Got unexpected parse result: {:?}", o), | |||
}; | |||
assert!(parsed_args.too_hard_for_preprocessor_cache_mode); | |||
|
|||
let args = stringvec!["-c", "foo.c", "-o", "foo.o", r#"-Wp,-DFOO="'something'""#]; |
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.
You can remove the single quotes.
src/util.rs
Outdated
/// plus MAX_HAYSTACK_LEN bytes of the previous chunk, to account for | ||
/// the possibility of partial reads splitting a time macro | ||
/// across two calls. | ||
previous_small_read: Option<Vec<u8>>, |
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.
IMO it would make the logic harder to read.
I'm not sure it would. I'm having a hard time convincing myself that the current one is correct with all the dancing around between both buffers.
src/util.rs
Outdated
} else { | ||
self.previous_small_read = visit.to_owned(); | ||
} | ||
self.find_macros(&self.previous_small_read.to_owned()); |
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.
Useless to_owned?
src/util.rs
Outdated
.copy_from_slice(visit); | ||
|
||
// Check both the concatenation with the previous small read | ||
self.find_macros(&self.previous_small_read.to_owned()); |
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.
Useless to_owned
src/util.rs
Outdated
// Check both the concatenation with the previous small read | ||
self.find_macros(&self.previous_small_read.to_owned()); | ||
// ...and the overlap buffer | ||
self.find_macros(&self.overlap_buffer.clone()); |
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.
Useless clone
src/util.rs
Outdated
let right_half = visit.len() - MAX_HAYSTACK_LEN; | ||
self.overlap_buffer[..MAX_HAYSTACK_LEN].copy_from_slice(&visit[right_half..]); | ||
} | ||
self.find_macros(&self.overlap_buffer.clone()); |
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.
Useless clone
src/util.rs
Outdated
// Copy the left side of the visit to the right of the buffer | ||
let left_half = MAX_HAYSTACK_LEN; | ||
self.overlap_buffer[left_half..].copy_from_slice(&visit[..left_half]); | ||
self.find_macros(&self.overlap_buffer.clone()); |
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.
Useless clone
6dbf004
to
738ad92
Compare
Build failed here:
|
This will be useful in a later commit to persist file paths using the same binary format on all platforms. This also introduces a decoding operation, which adapts the code from the original library ever so slightly to be less likely to provoke UB from a future change. Instead of: ``` // Convert to UTF-16 let mut wstr: Vec<u16> = Vec::with_capacity(len as usize); wstr.set_len(len as usize); let len = winapi::um::stringapiset::MultiByteToWideChar( codepage, flags, multi_byte_str.as_ptr() as winapi::um::winnt::LPSTR, multi_byte_str.len() as i32, wstr.as_mut_ptr(), len, ); ``` Do: ``` // Convert to UTF-16 let mut wstr: Vec<u16> = Vec::with_capacity(len as usize); let len = winapi::um::stringapiset::MultiByteToWideChar( codepage, flags, multi_byte_str.as_ptr() as winapi::um::winnt::LPSTR, multi_byte_str.len() as i32, wstr.as_mut_ptr(), len, ); wstr.set_len(len as usize); Which moves the `set_len` to *after* the Vec has been initialized. ```
This will be useful in a future commit to compare timestamps with as much precision as possible while also handling obviously fake/corrupted times.
This will be useful to handle time macros properly during caching preprocessor output in a future commit, while also not reading the contents twice.
`ccache` has a direct mode¹ by default, which caches the preprocessor step whenever possible, which speeds up local computation considerably. This only applies to C/C++ compilers and local storage. [1] https://ccache.dev/manual/4.3.html#_the_direct_mode
Useful for debugging.
This makes testing much easier and the structure of the overall function clearer, at the cost of some added complexity.
This stops using a dummy `TimeMacroFinder` and just separates the logic between the two modes.
This is not technically required to work, but will make our cache more reproducible.
It's more idiomatic in the Rust world, and makes it more obvious.
We define the `-Wp,*` argument for GCC/Clang, disable direct mode if it's present, since it's too hard to handle. `ccache` allows `-Wp,-MD,path`, `-Wp,-MMD,path` and `-Wp,-D_define_`. We don't handle these for now, since they need more careful attention.
well done @Alphare! |
@robertmaynard if you have a chance to test it before the release, don't hesitate |
@@ -84,6 +99,7 @@ configuration variables | |||
|
|||
* `SCCACHE_DIR` local on disk artifact cache directory | |||
* `SCCACHE_CACHE_SIZE` maximum size of the local on disk cache i.e. `2G` - default is 10G | |||
* `SCCACHE_PREPROCESSOR_MODE` enable/disable preprocessor caching (see [the local doc](Local.md)) |
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 should be "SCCACHE_DIRECT" no ?
Relates to
#219 #160 #497