1#![allow(dead_code)]
2
3use std::{
4 borrow::Cow,
5 cell::{Cell, RefCell},
6 collections::VecDeque,
7};
8
9use indexmap::map::{Entry, IndexMap};
10use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
11use swc_ecma_ast::*;
12use swc_ecma_transforms_base::ext::ExprRefExt;
13use tracing::{span, Level};
14
15use super::{Inlining, Phase};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ScopeKind {
19 Cond,
21 Loop,
22 Block,
23 Fn {
24 named: bool,
25 },
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub(super) enum VarType {
30 Param,
31 Var(VarDeclKind),
32}
33
34impl Default for ScopeKind {
35 fn default() -> Self {
36 Self::Fn { named: false }
37 }
38}
39
40impl Inlining<'_> {
41 pub(super) fn with_child<F>(&mut self, kind: ScopeKind, op: F)
42 where
43 F: for<'any> FnOnce(&mut Inlining<'any>),
44 {
45 let (unresolved_usages, bindings) = {
46 let mut child = Inlining {
47 phase: self.phase,
48 is_first_run: self.is_first_run,
49 changed: false,
50 scope: Scope::new(Some(&self.scope), kind),
51 var_decl_kind: VarDeclKind::Var,
52 ident_type: self.ident_type,
53 pat_mode: self.pat_mode,
54 in_test: self.in_test,
55 pass: self.pass,
56 };
57
58 op(&mut child);
59
60 self.changed |= child.changed;
61
62 (child.scope.unresolved_usages, child.scope.bindings)
63 };
64
65 tracing::trace!("propagating variables");
66
67 self.scope.unresolved_usages.extend(unresolved_usages);
68
69 if !matches!(kind, ScopeKind::Fn { .. }) {
70 let v = bindings;
71
72 for (id, v) in v.into_iter().filter_map(|(id, v)| {
73 if v.kind == VarType::Var(VarDeclKind::Var) {
74 Some((id, v))
75 } else {
76 None
77 }
78 }) {
79 let v: VarInfo = v;
80
81 tracing::trace!("Hoisting a variable {:?}", id);
82
83 if self.scope.unresolved_usages.contains(&id) {
84 v.inline_prevented.set(true)
85 }
86
87 v.hoisted.set(true);
88
89 *v.value.borrow_mut() = None;
90 v.is_undefined.set(false);
91 self.scope.bindings.insert(id, v);
92 }
93 }
94 }
95
96 pub(super) fn declare(
99 &mut self,
100 id: Id,
101 init: Option<Cow<Expr>>,
102 is_change: bool,
103 kind: VarType,
104 ) {
105 tracing::trace!(
106 "({}, {:?}) declare({})",
107 self.scope.depth(),
108 self.phase,
109 id.0
110 );
111
112 let init = init.map(|cow| match cow {
113 Cow::Owned(v) => Cow::Owned(v),
114 Cow::Borrowed(b) => match b {
115 Expr::Ident(..) | Expr::Lit(..) => Cow::Owned(b.clone()),
116 _ => Cow::Borrowed(b),
117 },
118 });
119
120 let is_undefined = self.var_decl_kind == VarDeclKind::Var
121 && !is_change
122 && init.is_none()
123 && self.phase == Phase::Inlining;
124
125 let mut alias_of = None;
126
127 let value_idx = match init.as_deref() {
128 Some(Expr::Ident(vi)) => {
129 if let Some((value_idx, value_var)) = self.scope.idx_val(&vi.to_id()) {
130 alias_of = Some(value_var.kind);
131 Some((value_idx, vi.to_id()))
132 } else {
133 None
134 }
135 }
136 _ => None,
137 };
138
139 let is_inline_prevented = self.scope.should_prevent_inline_because_of_scope(&id)
140 || match init {
141 Some(ref e) => self.scope.is_inline_prevented(e),
142 _ => false,
143 };
144
145 if is_inline_prevented {
146 tracing::trace!("\tdeclare: Inline prevented: {:?}", id)
147 }
148 if is_undefined {
149 tracing::trace!("\tdeclare: {:?} is undefined", id);
150 }
151
152 let idx = match self.scope.bindings.entry(id.clone()) {
153 Entry::Occupied(mut e) => {
154 e.get().is_undefined.set(is_undefined);
155 e.get().read_cnt.set(0);
156 e.get().read_from_nested_scope.set(false);
157
158 match init {
159 Some(Cow::Owned(v)) => e.get_mut().value = RefCell::new(Some(v)),
160 None => e.get_mut().value = RefCell::new(None),
161 _ => {}
162 }
163
164 e.get().inline_prevented.set(is_inline_prevented);
165 e.index()
166 }
167 Entry::Vacant(e) => {
168 let idx = e.index();
169 e.insert(VarInfo {
170 kind,
171 alias_of,
172 read_from_nested_scope: Cell::new(false),
173 read_cnt: Cell::new(0),
174 inline_prevented: Cell::new(is_inline_prevented),
175 is_undefined: Cell::new(is_undefined),
176 value: RefCell::new(match init {
177 Some(Cow::Owned(v)) => Some(v),
178 _ => None,
179 }),
180 this_sensitive: Cell::new(false),
181 hoisted: Cell::new(false),
182 });
183 idx
184 }
185 };
186
187 {
188 let mut cur = Some(&self.scope);
189 while let Some(scope) = cur {
190 if let ScopeKind::Fn { .. } = scope.kind {
191 break;
192 }
193
194 if scope.kind == ScopeKind::Loop {
195 tracing::trace!("preventing inline as it's declared in a loop");
196 self.scope.prevent_inline(&id);
197 break;
198 }
199
200 cur = scope.parent;
201 }
202 }
203
204 let barrier_works = match kind {
206 VarType::Param => false,
207 _ if alias_of == Some(VarType::Param) => false,
208 _ => true,
209 };
210
211 if barrier_works {
212 if let Some((value_idx, vi)) = value_idx {
213 tracing::trace!("\tdeclare: {} -> {}", idx, value_idx);
214
215 let barrier_exists = (|| {
216 for &blocker in self.scope.inline_barriers.borrow().iter() {
217 if (value_idx <= blocker && blocker <= idx)
218 || (idx <= blocker && blocker <= value_idx)
219 {
220 return true;
221 }
222 }
223
224 false
225 })();
226
227 if value_idx > idx || barrier_exists {
228 tracing::trace!("Variable use before declaration: {:?}", id);
229 self.scope.prevent_inline(&id);
230 self.scope.prevent_inline(&vi)
231 }
232 } else {
233 tracing::trace!("\tdeclare: value idx is none");
234 }
235 }
236 }
237}
238
239#[derive(Debug, Default)]
240pub(super) struct Scope<'a> {
241 pub parent: Option<&'a Scope<'a>>,
242 pub kind: ScopeKind,
243
244 inline_barriers: RefCell<VecDeque<usize>>,
245 bindings: IndexMap<Id, VarInfo, FxBuildHasher>,
246 unresolved_usages: FxHashSet<Id>,
247
248 pub constants: FxHashMap<Id, Option<Expr>>,
250}
251
252impl<'a> Scope<'a> {
253 pub fn new(parent: Option<&'a Scope<'a>>, kind: ScopeKind) -> Self {
254 Self {
255 parent,
256 kind,
257 ..Default::default()
258 }
259 }
260
261 pub fn depth(&self) -> usize {
262 match self.parent {
263 None => 0,
264 Some(p) => p.depth() + 1,
265 }
266 }
267
268 fn should_prevent_inline_because_of_scope(&self, id: &Id) -> bool {
269 if self.unresolved_usages.contains(id) {
270 return true;
271 }
272
273 match self.parent {
274 None => false,
275 Some(p) => {
276 if p.should_prevent_inline_because_of_scope(id) {
277 return true;
278 }
279
280 if let Some(v) = p.find_binding(id) {
281 if v.hoisted.get() && v.is_inline_prevented() {
282 return true;
283 }
284 }
285
286 false
287 }
288 }
289 }
290
291 fn scope_for(&self, id: &Id) -> (&Scope, bool) {
293 if self.constants.contains_key(id) {
294 return (self, true);
295 }
296 if self.find_binding_from_current(id).is_some() {
297 return (self, true);
298 }
299
300 match self.parent {
301 None => (self, true),
302 Some(p) => {
303 let (s, _) = p.scope_for(id);
304 (s, false)
305 }
306 }
307 }
308
309 pub fn read_cnt(&self, id: &Id) -> Option<usize> {
310 if let Some(var) = self.find_binding(id) {
311 return Some(var.read_cnt.get());
312 }
313
314 None
315 }
316
317 fn read_prevents_inlining(&self, id: &Id) -> bool {
318 tracing::trace!("read_prevents_inlining({:?})", id);
319
320 if let Some(v) = self.find_binding(id) {
321 match v.kind {
322 VarType::Param => return false,
324 VarType::Var(VarDeclKind::Let) | VarType::Var(VarDeclKind::Const) => return false,
325 _ => {}
326 }
327
328 if v.hoisted.get() {
331 return false;
332 }
333 }
334
335 {
336 let mut cur = Some(self);
337
338 while let Some(scope) = cur {
339 let found = scope.find_binding_from_current(id).is_some();
340
341 if found {
342 tracing::trace!("found");
343 break;
344 }
345 tracing::trace!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind);
346
347 match scope.kind {
348 ScopeKind::Fn { .. } => {
349 tracing::trace!(
350 "{}: variable access from a nested function detected",
351 id.0
352 );
353 return true;
354 }
355 ScopeKind::Loop | ScopeKind::Cond => {
356 return true;
357 }
358 _ => {}
359 }
360 cur = scope.parent;
361 }
362 }
363
364 false
365 }
366
367 pub fn add_read(&mut self, id: &Id) {
368 if self.read_prevents_inlining(id) {
369 tracing::trace!("prevent inlining because of read: {}", id.0);
370
371 self.prevent_inline(id)
372 }
373
374 if id.0 == "arguments" {
375 self.prevent_inline_of_params();
376 }
377
378 if let Some(var_info) = self.find_binding(id) {
379 var_info.read_cnt.set(var_info.read_cnt.get() + 1);
380 if var_info.hoisted.get() {
381 var_info.inline_prevented.set(true);
382 }
383 } else {
384 tracing::trace!("({}): Unresolved usage.: {:?}", self.depth(), id);
385 self.unresolved_usages.insert(id.clone());
386 }
387
388 let (scope, is_self) = self.scope_for(id);
389 if !is_self {
390 if let Some(var_info) = scope.find_binding_from_current(id) {
391 var_info.read_from_nested_scope.set(true);
392 }
393 }
394 }
395
396 fn write_prevents_inline(&self, id: &Id) -> bool {
397 tracing::trace!("write_prevents_inline({})", id.0);
398
399 {
400 let mut cur = Some(self);
401
402 while let Some(scope) = cur {
403 let found = scope.find_binding_from_current(id).is_some();
404
405 if found {
406 break;
407 }
408 tracing::trace!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind);
409
410 match scope.kind {
411 ScopeKind::Fn { .. } => {
412 tracing::trace!(
413 "{}: variable access from a nested function detected",
414 id.0
415 );
416 return true;
417 }
418 ScopeKind::Loop | ScopeKind::Cond => {
419 return true;
420 }
421 _ => {}
422 }
423 cur = scope.parent;
424 }
425 }
426
427 false
428 }
429
430 pub fn add_write(&mut self, id: &Id, force_no_inline: bool) {
431 let _tracing = span!(
432 Level::DEBUG,
433 "add_write",
434 force_no_inline = force_no_inline,
435 id = &*format!("{id:?}")
436 )
437 .entered();
438
439 if self.write_prevents_inline(id) {
440 tracing::trace!("prevent inlining because of write: {}", id.0);
441
442 self.prevent_inline(id)
443 }
444
445 if id.0 == "arguments" {
446 self.prevent_inline_of_params();
447 }
448
449 let (scope, is_self) = self.scope_for(id);
450
451 if let Some(var_info) = scope.find_binding_from_current(id) {
452 var_info.is_undefined.set(false);
453
454 if !is_self || force_no_inline {
455 self.prevent_inline(id)
456 }
457 } else if self.has_constant(id) {
458 } else {
460 tracing::trace!(
461 "({}): Unresolved. (scope = ({})): {:?}",
462 self.depth(),
463 scope.depth(),
464 id
465 );
466 self.bindings.insert(
467 id.clone(),
468 VarInfo {
469 kind: VarType::Var(VarDeclKind::Var),
470 alias_of: None,
471 read_from_nested_scope: Cell::new(false),
472 read_cnt: Cell::new(0),
473 inline_prevented: Cell::new(force_no_inline),
474 value: RefCell::new(None),
475 is_undefined: Cell::new(false),
476 this_sensitive: Cell::new(false),
477 hoisted: Cell::new(false),
478 },
479 );
480 }
481 }
482
483 pub fn find_binding(&self, id: &Id) -> Option<&VarInfo> {
484 if let Some(e) = self.find_binding_from_current(id) {
485 return Some(e);
486 }
487
488 self.parent.and_then(|parent| parent.find_binding(id))
489 }
490
491 fn idx_val(&self, id: &Id) -> Option<(usize, &VarInfo)> {
493 self.bindings.iter().enumerate().find_map(
494 |(idx, (k, v))| {
495 if k == id {
496 Some((idx, v))
497 } else {
498 None
499 }
500 },
501 )
502 }
503
504 pub fn find_binding_from_current(&self, id: &Id) -> Option<&VarInfo> {
505 let (_, v) = self.idx_val(id)?;
506
507 Some(v)
508 }
509
510 fn has_constant(&self, id: &Id) -> bool {
511 if self.constants.contains_key(id) {
512 return true;
513 }
514
515 self.parent
516 .map(|parent| parent.has_constant(id))
517 .unwrap_or(false)
518 }
519
520 pub fn find_constant(&self, id: &Id) -> Option<&Expr> {
521 if let Some(Some(e)) = self.constants.get(id) {
522 return Some(e);
523 }
524
525 self.parent.and_then(|parent| parent.find_constant(id))
526 }
527
528 pub fn mark_this_sensitive(&self, callee: &Expr) {
529 if let Expr::Ident(ref i) = callee {
530 if let Some(v) = self.find_binding(&i.to_id()) {
531 v.this_sensitive.set(true);
532 }
533 }
534 }
535
536 pub fn store_inline_barrier(&self, phase: Phase) {
537 tracing::trace!("store_inline_barrier({:?})", phase);
538
539 match phase {
540 Phase::Analysis => {
541 let idx = self.bindings.len();
542 self.inline_barriers.borrow_mut().push_back(idx);
543 }
544 Phase::Inlining => {
545 }
554 }
555
556 match self.parent {
557 None => {}
558 Some(p) => p.store_inline_barrier(phase),
559 }
560 }
561
562 fn prevent_inline_of_params(&self) {
563 for (_, v) in self.bindings.iter() {
564 let v: &VarInfo = v;
565
566 if v.is_param() {
567 v.inline_prevented.set(true);
568 }
569 }
570
571 if let ScopeKind::Fn { .. } = self.kind {
572 return;
573 }
574
575 if let Some(p) = self.parent {
576 p.prevent_inline_of_params()
577 }
578 }
579
580 pub fn prevent_inline(&self, id: &Id) {
581 tracing::trace!("({}) Prevent inlining: {:?}", self.depth(), id);
582
583 if let Some(v) = self.find_binding_from_current(id) {
584 v.inline_prevented.set(true);
585 }
586
587 for (_, v) in self.bindings.iter() {
588 if let Some(Expr::Ident(i)) = v.value.borrow().as_ref() {
589 if i.sym == id.0 && i.ctxt == id.1 {
590 v.inline_prevented.set(true);
591 }
592 }
593 }
594
595 match self.parent {
596 None => {}
597 Some(p) => p.prevent_inline(id),
598 }
599 }
600
601 pub fn is_inline_prevented(&self, e: &Expr) -> bool {
602 match e {
603 Expr::Ident(ref ri) => {
604 if let Some(v) = self.find_binding_from_current(&ri.to_id()) {
605 return v.inline_prevented.get();
606 }
607 }
608 Expr::Member(MemberExpr {
609 obj: right_expr, ..
610 }) if right_expr.is_ident() => {
611 let ri = right_expr.as_ident().unwrap();
612
613 if let Some(v) = self.find_binding_from_current(&ri.to_id()) {
614 return v.inline_prevented.get();
615 }
616 }
617 Expr::Update(..) => return true,
618
619 Expr::Paren(..) | Expr::Call(..) | Expr::New(..) => return true,
621
622 _ => {}
623 }
624
625 match self.parent {
626 None => false,
627 Some(p) => p.is_inline_prevented(e),
628 }
629 }
630
631 pub fn has_same_this(&self, id: &Id, init: Option<&Expr>) -> bool {
632 if let Some(v) = self.find_binding(id) {
633 if v.this_sensitive.get() {
634 if let Some(&Expr::Member(..)) = init {
635 return false;
636 }
637 }
638 }
639
640 true
641 }
642}
643
644#[derive(Debug)]
645pub(super) struct VarInfo {
646 pub kind: VarType,
647
648 alias_of: Option<VarType>,
649
650 read_from_nested_scope: Cell<bool>,
651 read_cnt: Cell<usize>,
652
653 inline_prevented: Cell<bool>,
654 this_sensitive: Cell<bool>,
655
656 pub value: RefCell<Option<Expr>>,
657 pub is_undefined: Cell<bool>,
658
659 hoisted: Cell<bool>,
660}
661
662impl VarInfo {
663 pub fn is_param(&self) -> bool {
664 self.kind == VarType::Param
665 }
666
667 pub fn is_inline_prevented(&self) -> bool {
668 if self.inline_prevented.get() {
669 return true;
670 }
671
672 if self.this_sensitive.get() {
673 if let Some(Expr::Member(..)) = *self.value.borrow() {
674 return true;
675 }
676 }
677
678 false
679 }
680}