Skip to main content

rspack_core/
runtime.rs

1use std::{cmp::Ordering, collections::hash_map, fmt::Debug, ops::Deref};
2
3use rspack_cacheable::{
4  cacheable,
5  with::{AsRefStr, AsVec},
6};
7#[cfg(allocative)]
8use rspack_util::allocative;
9use rustc_hash::FxHashMap;
10use ustr::{Ustr, UstrSet};
11
12use crate::{EntryOptions, EntryRuntime};
13
14#[cacheable]
15#[derive(Debug, Default, Clone)]
16#[cfg_attr(allocative, derive(allocative::Allocative))]
17pub struct RuntimeSpec {
18  #[cacheable(with=AsVec<AsRefStr>)]
19  inner: UstrSet,
20  key: String,
21}
22
23impl std::fmt::Display for RuntimeSpec {
24  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25    let mut iter = self.iter();
26    if let Some(first) = iter.next() {
27      write!(f, "{first}")?;
28    }
29    for r in iter {
30      write!(f, ",")?;
31      write!(f, "{r}")?;
32    }
33    Ok(())
34  }
35}
36
37impl std::hash::Hash for RuntimeSpec {
38  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39    self.key.hash(state);
40  }
41}
42
43impl std::cmp::PartialEq for RuntimeSpec {
44  fn eq(&self, other: &Self) -> bool {
45    self.key == other.key
46  }
47}
48
49impl std::cmp::Eq for RuntimeSpec {}
50
51impl Deref for RuntimeSpec {
52  type Target = UstrSet;
53
54  fn deref(&self) -> &Self::Target {
55    &self.inner
56  }
57}
58
59impl From<UstrSet> for RuntimeSpec {
60  fn from(value: UstrSet) -> Self {
61    Self::new(value)
62  }
63}
64
65impl FromIterator<Ustr> for RuntimeSpec {
66  fn from_iter<T: IntoIterator<Item = Ustr>>(iter: T) -> Self {
67    Self::new(UstrSet::from_iter(iter))
68  }
69}
70
71impl IntoIterator for RuntimeSpec {
72  type Item = Ustr;
73  type IntoIter = <UstrSet as IntoIterator>::IntoIter;
74
75  fn into_iter(self) -> Self::IntoIter {
76    self.inner.into_iter()
77  }
78}
79
80impl RuntimeSpec {
81  pub fn new(inner: UstrSet) -> Self {
82    let mut this = Self {
83      inner,
84      key: String::new(),
85    };
86    this.update_key();
87    this
88  }
89
90  pub fn from_entry(entry: &str, runtime: Option<&EntryRuntime>) -> Self {
91    let r = match runtime {
92      Some(EntryRuntime::String(s)) => s,
93      _ => entry,
94    }
95    .to_string();
96    Self::from_iter([r.into()])
97  }
98
99  pub fn from_entry_options(options: &EntryOptions) -> Option<Self> {
100    let r = match &options.runtime {
101      Some(EntryRuntime::String(s)) => Some(s.to_owned()),
102      _ => options.name.clone(),
103    };
104    r.map(|r| Self::from_iter([r.into()]))
105  }
106
107  pub fn subtract(&self, b: &RuntimeSpec) -> Self {
108    let res = self.inner.difference(&b.inner).copied().collect();
109    Self::new(res)
110  }
111
112  pub fn insert(&mut self, r: Ustr) -> bool {
113    let update = self.inner.insert(r);
114    if update {
115      self.update_key();
116    }
117    update
118  }
119
120  pub fn extend(&mut self, other: &Self) {
121    let prev = self.inner.len();
122    self.inner.extend(other.inner.iter().copied());
123    if prev != self.inner.len() {
124      self.update_key();
125    }
126  }
127
128  fn update_key(&mut self) {
129    if self.inner.is_empty() {
130      if self.key.is_empty() {
131        return;
132      }
133      self.key = String::new();
134      return;
135    }
136    let mut ordered = self.inner.iter().map(|s| s.as_str()).collect::<Vec<_>>();
137    ordered.sort_unstable();
138    self.key = ordered.join("_");
139  }
140
141  pub fn as_str(&self) -> &str {
142    &self.key
143  }
144}
145
146pub type RuntimeKey = String;
147
148pub type RuntimeKeyMap<T> = FxHashMap<RuntimeKey, T>;
149
150#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
151pub enum RuntimeMode {
152  #[default]
153  Empty = 0,
154  SingleEntry = 1,
155  Map = 2,
156}
157
158pub fn is_runtime_equal(a: &RuntimeSpec, b: &RuntimeSpec) -> bool {
159  a.key == b.key
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163#[cfg_attr(allocative, derive(allocative::Allocative))]
164pub enum RuntimeCondition {
165  Boolean(bool),
166  Spec(RuntimeSpec),
167}
168
169impl std::hash::Hash for RuntimeCondition {
170  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
171    match self {
172      Self::Boolean(v) => v.hash(state),
173      Self::Spec(s) => {
174        for i in s.iter() {
175          i.hash(state);
176        }
177      }
178    }
179  }
180}
181
182impl RuntimeCondition {
183  pub fn as_spec(&self) -> Option<&RuntimeSpec> {
184    if let Self::Spec(v) = self {
185      Some(v)
186    } else {
187      None
188    }
189  }
190
191  pub fn as_spec_mut(&mut self) -> Option<&mut RuntimeSpec> {
192    if let Self::Spec(v) = self {
193      Some(v)
194    } else {
195      None
196    }
197  }
198}
199
200pub fn merge_runtime(a: &RuntimeSpec, b: &RuntimeSpec) -> RuntimeSpec {
201  let mut set = a.inner.clone();
202  set.extend(b.inner.iter().copied());
203  RuntimeSpec::new(set)
204}
205
206pub fn filter_runtime(
207  runtime: Option<&RuntimeSpec>,
208  filter: impl Fn(Option<&RuntimeSpec>) -> bool,
209) -> RuntimeCondition {
210  match runtime {
211    None => RuntimeCondition::Boolean(filter(None)),
212    Some(runtime) => {
213      let mut some = false;
214      let mut every = true;
215      let mut result = UstrSet::default();
216
217      for &r in runtime.iter() {
218        let cur = RuntimeSpec::from_iter([r]);
219        let v = filter(Some(&cur));
220        if v {
221          some = true;
222          result.insert(r);
223        } else {
224          every = false;
225        }
226      }
227
228      if !some {
229        RuntimeCondition::Boolean(false)
230      } else if every {
231        RuntimeCondition::Boolean(true)
232      } else {
233        RuntimeCondition::Spec(RuntimeSpec::new(result))
234      }
235    }
236  }
237}
238
239/// assert the runtime condition is not `False`
240pub fn merge_runtime_condition_non_false(
241  a: &RuntimeCondition,
242  b: &RuntimeCondition,
243  runtime: Option<&RuntimeSpec>,
244) -> RuntimeCondition {
245  let merged = match (a, b) {
246    (RuntimeCondition::Boolean(true), _) => return RuntimeCondition::Boolean(true),
247    (RuntimeCondition::Boolean(false), _) => unreachable!(),
248    (_, RuntimeCondition::Boolean(false)) => unreachable!(),
249    (RuntimeCondition::Spec(_), RuntimeCondition::Boolean(true)) => {
250      return RuntimeCondition::Boolean(true);
251    }
252    (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => merge_runtime(a, b),
253  };
254  if runtime.map(|spec| spec.len()).unwrap_or_default() == merged.len() {
255    return RuntimeCondition::Boolean(true);
256  }
257  RuntimeCondition::Spec(merged)
258}
259
260pub fn merge_runtime_condition(
261  a: &RuntimeCondition,
262  b: &RuntimeCondition,
263  runtime: Option<&RuntimeSpec>,
264) -> RuntimeCondition {
265  let merged = match (a, b) {
266    (RuntimeCondition::Boolean(false), _) => return b.clone(),
267    (_, RuntimeCondition::Boolean(false)) => return a.clone(),
268    (_, RuntimeCondition::Boolean(true)) | (RuntimeCondition::Boolean(true), _) => {
269      return RuntimeCondition::Boolean(true);
270    }
271    (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => merge_runtime(a, b),
272  };
273  if runtime.map(|spec| spec.len()).unwrap_or_default() == merged.len() {
274    return RuntimeCondition::Boolean(true);
275  }
276  RuntimeCondition::Spec(merged)
277}
278
279pub fn subtract_runtime_condition(
280  a: &RuntimeCondition,
281  b: &RuntimeCondition,
282  runtime: Option<&RuntimeSpec>,
283) -> RuntimeCondition {
284  let merged = match (a, b) {
285    (_, RuntimeCondition::Boolean(true)) => return RuntimeCondition::Boolean(false),
286    (_, RuntimeCondition::Boolean(false)) => return a.clone(),
287    (RuntimeCondition::Boolean(false), _) => return RuntimeCondition::Boolean(false),
288    (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => a.difference(b).copied().collect(),
289    (RuntimeCondition::Boolean(true), RuntimeCondition::Spec(b)) => {
290      if let Some(a) = runtime {
291        a.difference(b).copied().collect()
292      } else {
293        UstrSet::default()
294      }
295    }
296  };
297  if merged.is_empty() {
298    return RuntimeCondition::Boolean(false);
299  }
300  RuntimeCondition::Spec(merged.into())
301}
302
303pub fn get_runtime_key(runtime: &RuntimeSpec) -> &RuntimeKey {
304  &runtime.key
305}
306
307pub fn compare_runtime(a: &RuntimeSpec, b: &RuntimeSpec) -> Ordering {
308  a.key.cmp(&b.key)
309}
310
311#[derive(Default, Clone, Debug, PartialEq, Eq)]
312pub struct RuntimeSpecMap<T> {
313  pub mode: RuntimeMode,
314  pub map: RuntimeKeyMap<T>,
315
316  pub single_runtime: Option<RuntimeSpec>,
317  pub single_value: Option<T>,
318}
319
320impl<T> RuntimeSpecMap<T> {
321  pub fn new() -> Self {
322    Self {
323      mode: RuntimeMode::Empty,
324      map: Default::default(),
325      single_runtime: None,
326      single_value: None,
327    }
328  }
329
330  pub fn size(&self) -> usize {
331    let mode = self.mode as usize;
332
333    if mode <= 1 { mode } else { self.map.len() }
334  }
335
336  pub fn get(&self, runtime: &RuntimeSpec) -> Option<&T> {
337    match self.mode {
338      RuntimeMode::Empty => None,
339      RuntimeMode::SingleEntry => {
340        if let Some(single_runtime) = self.single_runtime.as_ref()
341          && is_runtime_equal(single_runtime, runtime)
342        {
343          self.single_value.as_ref()
344        } else {
345          None
346        }
347      }
348      RuntimeMode::Map => self.map.get(get_runtime_key(runtime)),
349    }
350  }
351
352  pub fn get_mut(&mut self, runtime: &RuntimeSpec) -> Option<&mut T> {
353    match self.mode {
354      RuntimeMode::Empty => None,
355      RuntimeMode::SingleEntry => {
356        if let Some(single_runtime) = self.single_runtime.as_ref()
357          && is_runtime_equal(single_runtime, runtime)
358        {
359          self.single_value.as_mut()
360        } else {
361          None
362        }
363      }
364      RuntimeMode::Map => self.map.get_mut(get_runtime_key(runtime)),
365    }
366  }
367
368  pub fn set(&mut self, runtime: RuntimeSpec, value: T) {
369    match self.mode {
370      RuntimeMode::Empty => {
371        self.mode = RuntimeMode::SingleEntry;
372        self.single_runtime = Some(runtime);
373        self.single_value = Some(value);
374      }
375      RuntimeMode::SingleEntry => {
376        if let Some(single_runtime) = self.single_runtime.as_ref()
377          && is_runtime_equal(single_runtime, &runtime)
378        {
379          self.single_value = Some(value);
380        } else {
381          self.mode = RuntimeMode::Map;
382
383          let single_runtime = self
384            .single_runtime
385            .take()
386            .expect("Expected single runtime exists");
387          let single_value = self
388            .single_value
389            .take()
390            .expect("Expected single value exists");
391
392          self
393            .map
394            .insert(get_runtime_key(&single_runtime).clone(), single_value);
395          self.map.insert(get_runtime_key(&runtime).clone(), value);
396        }
397      }
398      RuntimeMode::Map => {
399        self.map.insert(get_runtime_key(&runtime).clone(), value);
400      }
401    }
402  }
403
404  pub fn values(&self) -> RuntimeSpecMapValues<'_, T> {
405    match self.mode {
406      RuntimeMode::Empty => RuntimeSpecMapValues::Empty,
407      RuntimeMode::SingleEntry => RuntimeSpecMapValues::SingleEntry(self.single_value.iter()),
408      RuntimeMode::Map => RuntimeSpecMapValues::Map(self.map.values()),
409    }
410  }
411}
412
413pub enum RuntimeSpecMapValues<'a, T> {
414  Empty,
415  SingleEntry(std::option::Iter<'a, T>),
416  Map(hash_map::Values<'a, RuntimeKey, T>),
417}
418
419impl<'a, T> Iterator for RuntimeSpecMapValues<'a, T> {
420  type Item = &'a T;
421
422  fn next(&mut self) -> Option<Self::Item> {
423    match self {
424      RuntimeSpecMapValues::Empty => None,
425      RuntimeSpecMapValues::SingleEntry(i) => i.next(),
426      RuntimeSpecMapValues::Map(i) => i.next(),
427    }
428  }
429
430  fn size_hint(&self) -> (usize, Option<usize>) {
431    match self {
432      RuntimeSpecMapValues::Empty => (0, Some(0)),
433      RuntimeSpecMapValues::SingleEntry(i) => i.size_hint(),
434      RuntimeSpecMapValues::Map(i) => i.size_hint(),
435    }
436  }
437}
438
439#[derive(Default, Debug)]
440pub struct RuntimeSpecSet {
441  map: RuntimeKeyMap<RuntimeSpec>,
442}
443
444impl RuntimeSpecSet {
445  pub fn get(&self, runtime: &RuntimeSpec) -> Option<&RuntimeSpec> {
446    self.map.get(get_runtime_key(runtime))
447  }
448
449  pub fn set(&mut self, runtime: RuntimeSpec) {
450    self.map.insert(get_runtime_key(&runtime).clone(), runtime);
451  }
452
453  pub fn contains(&self, runtime: &RuntimeSpec) -> bool {
454    self.map.contains_key(get_runtime_key(runtime))
455  }
456
457  pub fn values(&self) -> hash_map::Values<'_, RuntimeKey, RuntimeSpec> {
458    self.map.values()
459  }
460
461  pub fn into_values(self) -> hash_map::IntoValues<RuntimeKey, RuntimeSpec> {
462    self.map.into_values()
463  }
464
465  pub fn len(&self) -> usize {
466    self.map.len()
467  }
468
469  pub fn is_empty(&self) -> bool {
470    self.len() == 0
471  }
472}