mirror of
https://github.com/rui314/mold.git
synced 2026-11-08 17:48:41 -10:00
Consume glob construction state into an immutable matcher
This commit is contained in:
1 parent
be74299a61
commit
ffca665b28
3 files changed
+106
-113
No files matched your search
+7
-3
@@ -10,7 +10,7 @@ use bstr::{ByteSlice, ByteVec};
|
||||
use crate::arch;
|
||||
use crate::elf::*;
|
||||
use crate::mapped_file::MappedFile;
|
||||
use crate::util::glob::Glob;
|
||||
use crate::util::glob::{Glob, GlobBuilder};
|
||||
use crate::util::perf::Counter;
|
||||
use crate::util::{self, align_down};
|
||||
use crate::{fatal, out, warn};
|
||||
@@ -1048,6 +1048,8 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[OsString]) -> ParsedArgs
|
||||
// --as-needed, that apply to the files after them; each job gets a
|
||||
// snapshot of the state at its position.
|
||||
let mut a = Args::default();
|
||||
let mut undefined_glob = GlobBuilder::default();
|
||||
let mut unique = GlobBuilder::default();
|
||||
let mut jobs: Vec<ReaderJob> = Vec::new();
|
||||
let mut rctx = ReaderContext::default();
|
||||
let mut rctx_stack: Vec<ReaderContext> = Vec::new();
|
||||
@@ -1348,7 +1350,7 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[OsString]) -> ParsedArgs
|
||||
} else if read_arg!("sysroot", true) {
|
||||
a.sysroot = PathBuf::from(raw_arg);
|
||||
} else if read_arg!("unique", true) {
|
||||
if !a.unique.add(raw_arg.as_encoded_bytes(), 1) {
|
||||
if !unique.add(raw_arg.as_encoded_bytes(), 1) {
|
||||
fatal!(
|
||||
"-unique: invalid glob pattern: {}",
|
||||
raw_arg.to_string_lossy()
|
||||
@@ -1363,7 +1365,7 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[OsString]) -> ParsedArgs
|
||||
} else if read_arg!("undefined", true) || read_arg!("u", true) {
|
||||
a.undefined.push(raw_arg.as_encoded_bytes().to_vec());
|
||||
} else if read_arg!("undefined-glob", true) {
|
||||
if !a.undefined_glob.add(raw_arg.as_encoded_bytes(), 0) {
|
||||
if !undefined_glob.add(raw_arg.as_encoded_bytes(), 0) {
|
||||
fatal!(
|
||||
"--undefined-glob: invalid pattern: {}",
|
||||
raw_arg.to_string_lossy()
|
||||
@@ -2198,6 +2200,8 @@ pub fn parse_args(target: &TargetTraits, raw_cmdline: &[OsString]) -> ParsedArgs
|
||||
a.dynamic_linker.clear();
|
||||
}
|
||||
|
||||
a.undefined_glob = undefined_glob.build();
|
||||
a.unique = unique.build();
|
||||
ParsedArgs { args: a, jobs }
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -49,7 +49,7 @@ use crate::symbol::{
|
||||
is_c_identifier, Bins, Symbol, SymbolId, NEEDS_CANONICAL, NEEDS_GOT, NEEDS_GOTTP, NEEDS_PLT,
|
||||
NEEDS_PPC_OPD, NEEDS_TLSDESC, NEEDS_TLSGD,
|
||||
};
|
||||
use crate::util::glob::Glob;
|
||||
use crate::util::glob::GlobBuilder;
|
||||
use crate::util::perf::Counter;
|
||||
use crate::util::{align_to, leak_bytes};
|
||||
use crate::{error, fatal, out, warn};
|
||||
@@ -2998,8 +2998,8 @@ pub fn apply_version_script<E: Arch>(ctx: &mut Context<E>) {
|
||||
|
||||
// Assign versions to symbols specified with `extern "C++"` or
|
||||
// wildcard patterns first.
|
||||
let mut matcher = Glob::new();
|
||||
let mut cpp_matcher = Glob::new();
|
||||
let mut matcher = GlobBuilder::default();
|
||||
let mut cpp_matcher = GlobBuilder::default();
|
||||
|
||||
// The "local:" label has a special meaning in the version script.
|
||||
// It can appear in any VERSION clause, and it hides matched symbols
|
||||
@@ -3032,6 +3032,8 @@ pub fn apply_version_script<E: Arch>(ctx: &mut Context<E>) {
|
||||
}
|
||||
}
|
||||
|
||||
let matcher = matcher.build();
|
||||
let cpp_matcher = cpp_matcher.build();
|
||||
if !matcher.is_empty() || !cpp_matcher.is_empty() {
|
||||
ctx.symbols.par_for_each_global_mut(|sym| {
|
||||
if !matches!(sym.file(), Some(FileId::Obj(_))) {
|
||||
@@ -3302,8 +3304,8 @@ pub fn compute_import_export<E: Arch>(ctx: &mut Context<E>) {
|
||||
}
|
||||
};
|
||||
|
||||
let mut matcher = Glob::new();
|
||||
let mut cpp_matcher = Glob::new();
|
||||
let mut matcher = GlobBuilder::default();
|
||||
let mut cpp_matcher = GlobBuilder::default();
|
||||
for p in &ctx.dynamic_list_patterns {
|
||||
if p.is_cpp {
|
||||
if !cpp_matcher.add(p.pattern, 1) {
|
||||
@@ -3329,6 +3331,8 @@ pub fn compute_import_export<E: Arch>(ctx: &mut Context<E>) {
|
||||
handle_match(&mut ctx.symbols[id]);
|
||||
}
|
||||
|
||||
let matcher = matcher.build();
|
||||
let cpp_matcher = cpp_matcher.build();
|
||||
if !matcher.is_empty() || !cpp_matcher.is_empty() {
|
||||
ctx.symbols.par_for_each_global_mut(|sym| {
|
||||
if !matches!(sym.file(), Some(FileId::Obj(_))) || (shared && !sym.is_exported()) {
|
||||
|
||||
+90
-105
@@ -23,7 +23,6 @@
|
||||
//! with the [`Glob`] type.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Token {
|
||||
@@ -501,16 +500,14 @@ pub struct Glob {
|
||||
suffixes: Vec<Literal>,
|
||||
patterns: Vec<Pattern>, // "foo*bar"
|
||||
aho_corasick: AhoCorasick,
|
||||
compiled: OnceLock<Compiled>,
|
||||
nfa: Nfa,
|
||||
is_empty: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Compiled {
|
||||
exacts: Vec<Literal>,
|
||||
nfa: Nfa,
|
||||
patterns: Vec<Pattern>,
|
||||
aho_corasick: AhoCorasick,
|
||||
/// Collects patterns before compiling an immutable matcher.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GlobBuilder {
|
||||
glob: Glob,
|
||||
}
|
||||
|
||||
fn is_literal(pat: &[u8]) -> bool {
|
||||
@@ -527,12 +524,82 @@ impl Default for Glob {
|
||||
suffixes: Vec::new(),
|
||||
patterns: Vec::new(),
|
||||
aho_corasick: AhoCorasick::default(),
|
||||
compiled: OnceLock::new(),
|
||||
nfa: Nfa::default(),
|
||||
is_empty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobBuilder {
|
||||
/// Adds a pattern. Returns false if the pattern is malformed.
|
||||
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
debug_assert!(value >= 0);
|
||||
self.glob.is_empty = false;
|
||||
self.glob.max_value = self.glob.max_value.max(value);
|
||||
|
||||
// Match-all, exact, prefix and suffix patterns are handled with
|
||||
// plain string comparisons instead of the matchers below, which
|
||||
// have to scan the entire input string on every query.
|
||||
if pat == b"*" {
|
||||
self.glob.match_all = self.glob.match_all.max(value);
|
||||
return true;
|
||||
}
|
||||
if is_literal(pat) {
|
||||
self.glob.exacts.push(Literal {
|
||||
pat: pat.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.prefixes.push(Literal {
|
||||
pat: prefix.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.suffixes.push(Literal {
|
||||
pat: suffix.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if AhoCorasick::can_handle(pat) {
|
||||
self.glob.aho_corasick.add(pat, value);
|
||||
return true;
|
||||
}
|
||||
match Pattern::compile(pat, value) {
|
||||
Some(pattern) => {
|
||||
self.glob.patterns.push(pattern);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the construction state. The result supports parallel queries.
|
||||
pub fn build(self) -> Glob {
|
||||
let mut glob = self.glob;
|
||||
if glob.match_all == glob.max_value {
|
||||
return glob;
|
||||
}
|
||||
|
||||
// For duplicate names, retain the largest value.
|
||||
glob.exacts
|
||||
.sort_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
|
||||
glob.exacts.dedup_by(|a, b| a.pat == b.pat);
|
||||
if glob.patterns.len() >= 64 {
|
||||
glob.nfa = Nfa::compile(&glob.patterns);
|
||||
glob.patterns = Vec::new();
|
||||
}
|
||||
glob.aho_corasick.compile();
|
||||
glob
|
||||
}
|
||||
}
|
||||
|
||||
impl Glob {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@@ -542,100 +609,15 @@ impl Glob {
|
||||
self.is_empty
|
||||
}
|
||||
|
||||
/// Adds a pattern. Returns false if the pattern is malformed.
|
||||
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
debug_assert!(value >= 0);
|
||||
debug_assert!(self.compiled.get().is_none());
|
||||
self.is_empty = false;
|
||||
self.max_value = self.max_value.max(value);
|
||||
|
||||
// Match-all, exact, prefix and suffix patterns are handled with
|
||||
// plain string comparisons instead of the matchers below, which
|
||||
// have to scan the entire input string on every query.
|
||||
if pat == b"*" {
|
||||
self.match_all = self.match_all.max(value);
|
||||
return true;
|
||||
}
|
||||
if is_literal(pat) {
|
||||
self.exacts.push(Literal {
|
||||
pat: pat.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
|
||||
self.prefixes.push(Literal {
|
||||
pat: prefix.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
|
||||
self.suffixes.push(Literal {
|
||||
pat: suffix.to_vec(),
|
||||
value,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if AhoCorasick::can_handle(pat) {
|
||||
self.aho_corasick.add(pat, value);
|
||||
return true;
|
||||
}
|
||||
match Pattern::compile(pat, value) {
|
||||
Some(pattern) => {
|
||||
self.patterns.push(pattern);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn compiled(&self) -> &Compiled {
|
||||
self.compiled.get_or_init(|| {
|
||||
// If the same name was added more than once, keep only the entry
|
||||
// with the largest value, as find() returns the largest match.
|
||||
// Sorting by (name, negated value) places that entry first in
|
||||
// each run of duplicates, which is the one unique() keeps.
|
||||
let mut exacts = self.exacts.clone();
|
||||
exacts.sort_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
|
||||
exacts.dedup_by(|a, b| a.pat == b.pat);
|
||||
|
||||
let mut patterns = self.patterns.clone();
|
||||
let nfa = if patterns.len() >= 64 {
|
||||
let nfa = Nfa::compile(&patterns);
|
||||
patterns.clear();
|
||||
nfa
|
||||
} else {
|
||||
Nfa::default()
|
||||
};
|
||||
|
||||
let mut aho_corasick = self.aho_corasick.clone();
|
||||
aho_corasick.compile();
|
||||
|
||||
Compiled {
|
||||
exacts,
|
||||
nfa,
|
||||
patterns,
|
||||
aho_corasick,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the largest value of a matching pattern, or -1 if none match.
|
||||
pub fn find(&self, s: &[u8]) -> i64 {
|
||||
let compiled = self.compiled();
|
||||
let mut value = self.match_all;
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
|
||||
if let Ok(i) = compiled
|
||||
.exacts
|
||||
.binary_search_by(|l| l.pat.as_slice().cmp(s))
|
||||
{
|
||||
value = value.max(compiled.exacts[i].value);
|
||||
if let Ok(i) = self.exacts.binary_search_by(|l| l.pat.as_slice().cmp(s)) {
|
||||
value = value.max(self.exacts[i].value);
|
||||
}
|
||||
for p in &self.prefixes {
|
||||
if value < p.value && s.starts_with(&p.pat) {
|
||||
@@ -650,14 +632,14 @@ impl Glob {
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
value = value.max(compiled.aho_corasick.find(s));
|
||||
value = value.max(self.aho_corasick.find(s));
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
if !compiled.nfa.is_empty() {
|
||||
value = value.max(compiled.nfa.matches(s));
|
||||
if !self.nfa.is_empty() {
|
||||
value = value.max(self.nfa.matches(s));
|
||||
}
|
||||
for p in &compiled.patterns {
|
||||
for p in &self.patterns {
|
||||
if value < p.value && p.matches(s) {
|
||||
value = p.value;
|
||||
}
|
||||
@@ -671,11 +653,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn glob(pats: &[&str]) -> Glob {
|
||||
let mut g = Glob::new();
|
||||
let mut g = GlobBuilder::default();
|
||||
for (i, p) in pats.iter().enumerate() {
|
||||
assert!(g.add(p.as_bytes(), i as i64));
|
||||
}
|
||||
g
|
||||
g.build()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -684,8 +666,9 @@ mod tests {
|
||||
assert!(g.is_empty());
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
|
||||
let mut g = Glob::default();
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"present", 7));
|
||||
let g = g.build();
|
||||
assert!(!g.is_empty());
|
||||
assert_eq!(g.find(b"present"), 7);
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
@@ -693,7 +676,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shared_priorities_preserve_highest_match() {
|
||||
let mut g = Glob::new();
|
||||
let mut g = GlobBuilder::default();
|
||||
for (pattern, priority) in [
|
||||
("*", 0),
|
||||
("*inner*", 2),
|
||||
@@ -704,6 +687,7 @@ mod tests {
|
||||
] {
|
||||
assert!(g.add(pattern.as_bytes(), priority));
|
||||
}
|
||||
let g = g.build();
|
||||
for (name, expected) in [
|
||||
("none", 0),
|
||||
("hasinnersuffix", 2),
|
||||
@@ -713,9 +697,10 @@ mod tests {
|
||||
] {
|
||||
assert_eq!(g.find(name.as_bytes()), expected);
|
||||
}
|
||||
let mut g = Glob::new();
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"*inner*", 0));
|
||||
assert!(g.add(b"*suffix", 0));
|
||||
let g = g.build();
|
||||
assert_eq!(g.find(b"innersuffix"), 0);
|
||||
assert_eq!(g.find(b"none"), -1);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user