swc_ecma_transforms_optimization/simplify/inlining/
mod.rs1use std::borrow::Cow;
2
3use swc_common::{
4 pass::{CompilerPass, Repeated},
5 util::take::Take,
6};
7use swc_ecma_ast::*;
8use swc_ecma_transforms_base::scope::IdentType;
9use swc_ecma_utils::{contains_this_expr, find_pat_ids};
10use swc_ecma_visit::{
11 noop_visit_mut_type, noop_visit_type, visit_mut_pass, visit_obj_and_computed, Visit, VisitMut,
12 VisitMutWith, VisitWith,
13};
14use tracing::{span, Level};
15
16use self::scope::{Scope, ScopeKind, VarType};
17
18mod scope;
19
20#[derive(Debug, Default)]
21pub struct Config {}
22
23pub fn inlining(_: Config) -> impl 'static + Repeated + CompilerPass + Pass + VisitMut {
37 visit_mut_pass(Inlining {
38 phase: Phase::Analysis,
39 is_first_run: true,
40 changed: false,
41 scope: Default::default(),
42 var_decl_kind: VarDeclKind::Var,
43 ident_type: IdentType::Ref,
44 in_test: false,
45 pat_mode: PatFoldingMode::VarDecl,
46 pass: Default::default(),
47 })
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51enum Phase {
52 Analysis,
53 Inlining,
54}
55
56impl CompilerPass for Inlining<'_> {
57 fn name(&self) -> Cow<'static, str> {
58 Cow::Borrowed("inlining")
59 }
60}
61
62impl Repeated for Inlining<'_> {
63 fn changed(&self) -> bool {
64 self.changed
65 }
66
67 fn reset(&mut self) {
68 self.changed = false;
69 self.is_first_run = false;
70 self.pass += 1;
71 }
72}
73
74struct Inlining<'a> {
75 phase: Phase,
76 is_first_run: bool,
77 changed: bool,
78 scope: Scope<'a>,
79 var_decl_kind: VarDeclKind,
80 ident_type: IdentType,
81 in_test: bool,
82 pat_mode: PatFoldingMode,
83 pass: usize,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87enum PatFoldingMode {
88 Assign,
89 Param,
90 CatchParam,
91 VarDecl,
92}
93
94impl Inlining<'_> {
95 fn visit_with_child<T>(&mut self, kind: ScopeKind, node: &mut T)
96 where
97 T: 'static + for<'any> VisitMutWith<Inlining<'any>>,
98 {
99 self.with_child(kind, |child| {
100 node.visit_mut_children_with(child);
101 });
102 }
103}
104
105impl VisitMut for Inlining<'_> {
106 noop_visit_mut_type!();
107
108 fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
109 self.visit_with_child(ScopeKind::Fn { named: false }, node)
110 }
111
112 fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) {
113 tracing::trace!("{:?}; Fold<AssignExpr>", self.phase);
114 self.pat_mode = PatFoldingMode::Assign;
115
116 match e.op {
117 op!("=") => {
118 let mut v = WriteVisitor {
119 scope: &mut self.scope,
120 };
121
122 e.left.visit_with(&mut v);
123 e.right.visit_with(&mut v);
124
125 match &mut e.left {
126 AssignTarget::Simple(left) => {
127 if let SimpleAssignTarget::Member(ref left) = &*left {
129 tracing::trace!("Assign to member expression!");
130 let mut v = IdentListVisitor {
131 scope: &mut self.scope,
132 };
133
134 left.visit_with(&mut v);
135 e.right.visit_with(&mut v);
136 }
137 }
138 AssignTarget::Pat(p) => {
139 p.visit_mut_with(self);
140 }
141 #[cfg(swc_ast_unknown)]
142 _ => panic!("unable to access unknown nodes"),
143 }
144 }
145
146 _ => {
147 let mut v = IdentListVisitor {
148 scope: &mut self.scope,
149 };
150
151 e.left.visit_with(&mut v);
152 e.right.visit_with(&mut v)
153 }
154 }
155
156 e.right.visit_mut_with(self);
157
158 if self.scope.is_inline_prevented(&e.right) {
159 let ids: Vec<Id> = find_pat_ids(&e.left);
161 for id in ids {
162 self.scope.prevent_inline(&id);
163 }
164 return;
165 }
166
167 if let Some(i) = e.left.as_ident() {
169 let id = i.to_id();
170 self.scope.add_write(&id, false);
171
172 if let Some(var) = self.scope.find_binding(&id) {
173 if !var.is_inline_prevented() {
174 match *e.right {
175 Expr::Lit(..) | Expr::Ident(..) => {
176 *var.value.borrow_mut() = Some(*e.right.clone());
177 }
178
179 _ => {
180 *var.value.borrow_mut() = None;
181 }
182 }
183 }
184 }
185 }
186 }
187
188 fn visit_mut_block_stmt(&mut self, node: &mut BlockStmt) {
189 self.visit_with_child(ScopeKind::Block, node)
190 }
191
192 fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
193 node.callee.visit_mut_with(self);
194
195 if self.phase == Phase::Analysis {
196 if let Callee::Expr(ref callee) = node.callee {
197 self.scope.mark_this_sensitive(callee);
198 }
199 }
200
201 node.args.visit_children_with(&mut WriteVisitor {
203 scope: &mut self.scope,
204 });
205
206 node.args.visit_mut_with(self);
207
208 self.scope.store_inline_barrier(self.phase);
209 }
210
211 fn visit_mut_catch_clause(&mut self, node: &mut CatchClause) {
212 self.with_child(ScopeKind::Block, move |child| {
213 child.pat_mode = PatFoldingMode::CatchParam;
214 node.param.visit_mut_with(child);
215 match child.phase {
216 Phase::Analysis => {
217 let ids: Vec<Id> = find_pat_ids(&node.param);
218 for id in ids {
219 child.scope.prevent_inline(&id);
220 }
221 }
222 Phase::Inlining => {}
223 }
224
225 node.body.visit_mut_with(child);
226 })
227 }
228
229 fn visit_mut_do_while_stmt(&mut self, node: &mut DoWhileStmt) {
230 {
231 node.test.visit_with(&mut IdentListVisitor {
232 scope: &mut self.scope,
233 });
234 }
235
236 node.test.visit_mut_with(self);
237 self.visit_with_child(ScopeKind::Loop, &mut node.body);
238 }
239
240 fn visit_mut_expr(&mut self, node: &mut Expr) {
241 node.visit_mut_children_with(self);
242
243 if self.phase == Phase::Inlining {
257 if let Expr::Assign(e @ AssignExpr { op: op!("="), .. }) = node {
258 if let Some(i) = e.left.as_ident() {
259 if let Some(var) = self.scope.find_binding_from_current(&i.to_id()) {
260 if var.is_undefined.get()
261 && !var.is_inline_prevented()
262 && !self.scope.is_inline_prevented(&e.right)
263 {
264 *var.value.borrow_mut() = Some(*e.right.clone());
265 var.is_undefined.set(false);
266 *node = *e.right.take();
267 return;
268 }
269 }
270 }
271
272 return;
273 }
274 }
275
276 if let Expr::Ident(ref i) = node {
277 let id = i.to_id();
278 if self.is_first_run {
279 if let Some(expr) = self.scope.find_constant(&id) {
280 self.changed = true;
281 let mut expr = expr.clone();
282 expr.visit_mut_with(self);
283 *node = expr;
284 return;
285 }
286 }
287
288 match self.phase {
289 Phase::Analysis => {
290 if self.in_test {
291 if let Some(var) = self.scope.find_binding(&id) {
292 match &*var.value.borrow() {
293 Some(Expr::Ident(..)) | Some(Expr::Lit(..)) => {}
294 _ => {
295 self.scope.prevent_inline(&id);
296 }
297 }
298 }
299 }
300 self.scope.add_read(&id);
301 }
302 Phase::Inlining => {
303 tracing::trace!("Trying to inline: {:?}", id);
304 let expr = if let Some(var) = self.scope.find_binding(&id) {
305 tracing::trace!("VarInfo: {:?}", var);
306 if !var.is_inline_prevented() {
307 let expr = var.value.borrow();
308
309 if let Some(expr) = &*expr {
310 tracing::debug!("Inlining: {:?}", id);
311
312 if *node != *expr {
313 self.changed = true;
314 }
315
316 Some(expr.clone())
317 } else {
318 tracing::debug!("Inlining: {:?} as undefined", id);
319
320 if var.is_undefined.get() {
321 *node = *Expr::undefined(i.span);
322 return;
323 } else {
324 tracing::trace!("Not a cheap expression");
325 None
326 }
327 }
328 } else {
329 tracing::trace!("Inlining is prevented");
330 None
331 }
332 } else {
333 None
334 };
335
336 if let Some(expr) = expr {
337 *node = expr;
338 }
339 }
340 }
341 }
342
343 if let Expr::Bin(b) = node {
344 match b.op {
345 BinaryOp::LogicalAnd | BinaryOp::LogicalOr => {
346 self.visit_with_child(ScopeKind::Cond, &mut b.right);
347 }
348 _ => {}
349 }
350 }
351 }
352
353 fn visit_mut_fn_decl(&mut self, node: &mut FnDecl) {
354 if self.phase == Phase::Analysis {
355 self.declare(
356 node.ident.to_id(),
357 None,
358 true,
359 VarType::Var(VarDeclKind::Var),
360 );
361 }
362
363 self.with_child(ScopeKind::Fn { named: true }, |child| {
364 child.pat_mode = PatFoldingMode::Param;
365 node.function.params.visit_mut_with(child);
366 match &mut node.function.body {
367 None => {}
368 Some(v) => {
369 v.visit_mut_children_with(child);
370 }
371 };
372 });
373 }
374
375 fn visit_mut_fn_expr(&mut self, node: &mut FnExpr) {
376 if let Some(ref ident) = node.ident {
377 self.scope.add_write(&ident.to_id(), true);
378 }
379
380 node.function.visit_mut_with(self)
381 }
382
383 fn visit_mut_for_in_stmt(&mut self, node: &mut ForInStmt) {
384 self.pat_mode = PatFoldingMode::Param;
385 node.left.visit_mut_with(self);
386
387 {
388 node.left.visit_with(&mut IdentListVisitor {
389 scope: &mut self.scope,
390 });
391 }
392
393 {
394 node.right.visit_with(&mut IdentListVisitor {
395 scope: &mut self.scope,
396 });
397 }
398
399 node.right.visit_mut_with(self);
400 self.visit_with_child(ScopeKind::Loop, &mut node.body);
401 }
402
403 fn visit_mut_for_of_stmt(&mut self, node: &mut ForOfStmt) {
404 self.pat_mode = PatFoldingMode::Param;
405 node.left.visit_mut_with(self);
406
407 {
408 node.left.visit_with(&mut IdentListVisitor {
409 scope: &mut self.scope,
410 });
411 }
412 {
413 node.right.visit_with(&mut IdentListVisitor {
414 scope: &mut self.scope,
415 });
416 }
417
418 node.right.visit_mut_with(self);
419 self.visit_with_child(ScopeKind::Loop, &mut node.body);
420 }
421
422 fn visit_mut_for_stmt(&mut self, node: &mut ForStmt) {
423 {
424 node.init.visit_with(&mut IdentListVisitor {
425 scope: &mut self.scope,
426 });
427 }
428 {
429 node.test.visit_with(&mut IdentListVisitor {
430 scope: &mut self.scope,
431 });
432 }
433 {
434 node.update.visit_with(&mut IdentListVisitor {
435 scope: &mut self.scope,
436 });
437 }
438
439 node.init.visit_mut_with(self);
440 node.test.visit_mut_with(self);
441 node.update.visit_mut_with(self);
442 self.visit_with_child(ScopeKind::Loop, &mut node.body);
443
444 if node.init.is_none() && node.test.is_none() && node.update.is_none() {
445 self.scope.store_inline_barrier(self.phase);
446 }
447 }
448
449 fn visit_mut_function(&mut self, node: &mut Function) {
450 self.with_child(ScopeKind::Fn { named: false }, move |child| {
451 child.pat_mode = PatFoldingMode::Param;
452 node.params.visit_mut_with(child);
453 match &mut node.body {
454 None => None,
455 Some(v) => {
456 v.visit_mut_children_with(child);
457 Some(())
458 }
459 };
460 })
461 }
462
463 fn visit_mut_if_stmt(&mut self, stmt: &mut IfStmt) {
464 let old_in_test = self.in_test;
465 self.in_test = true;
466 stmt.test.visit_mut_with(self);
467 self.in_test = old_in_test;
468
469 self.visit_with_child(ScopeKind::Cond, &mut stmt.cons);
470 self.visit_with_child(ScopeKind::Cond, &mut stmt.alt);
471 }
472
473 fn visit_mut_program(&mut self, program: &mut Program) {
474 let _tracing = span!(Level::ERROR, "inlining", pass = self.pass).entered();
475
476 let old_phase = self.phase;
477
478 self.phase = Phase::Analysis;
479 program.visit_mut_children_with(self);
480
481 tracing::trace!("Switching to Inlining phase");
482
483 self.phase = Phase::Inlining;
485 program.visit_mut_children_with(self);
486
487 self.phase = old_phase;
488 }
489
490 fn visit_mut_new_expr(&mut self, node: &mut NewExpr) {
491 node.callee.visit_mut_with(self);
492 if self.phase == Phase::Analysis {
493 self.scope.mark_this_sensitive(&node.callee);
494 }
495
496 node.args.visit_mut_with(self);
497
498 self.scope.store_inline_barrier(self.phase);
499 }
500
501 fn visit_mut_pat(&mut self, node: &mut Pat) {
502 node.visit_mut_children_with(self);
503
504 if let Pat::Ident(ref i) = node {
505 match self.pat_mode {
506 PatFoldingMode::Param => {
507 self.declare(
508 i.to_id(),
509 Some(Cow::Owned(Ident::from(i).into())),
510 false,
511 VarType::Param,
512 );
513 }
514 PatFoldingMode::CatchParam => {
515 self.declare(
516 i.to_id(),
517 Some(Cow::Owned(Ident::from(i).into())),
518 false,
519 VarType::Var(VarDeclKind::Var),
520 );
521 }
522 PatFoldingMode::VarDecl => {}
523 PatFoldingMode::Assign => {
524 if self.scope.find_binding_from_current(&i.to_id()).is_some() {
525 } else {
526 self.scope.add_write(&i.to_id(), false);
527 }
528 }
529 }
530 }
531 }
532
533 fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
534 let old_phase = self.phase;
535
536 match old_phase {
537 Phase::Analysis => {
538 items.visit_mut_children_with(self);
539 }
540 Phase::Inlining => {
541 self.phase = Phase::Analysis;
542 items.visit_mut_children_with(self);
543
544 self.phase = Phase::Inlining;
546 items.visit_mut_children_with(self);
547
548 self.phase = old_phase
549 }
550 }
551 }
552
553 fn visit_mut_switch_case(&mut self, node: &mut SwitchCase) {
554 self.visit_with_child(ScopeKind::Block, node)
555 }
556
557 fn visit_mut_try_stmt(&mut self, node: &mut TryStmt) {
558 node.block.visit_with(&mut IdentListVisitor {
559 scope: &mut self.scope,
560 });
561
562 node.handler.visit_mut_with(self)
563 }
564
565 fn visit_mut_unary_expr(&mut self, node: &mut UnaryExpr) {
566 if let op!("delete") = node.op {
567 let mut v = IdentListVisitor {
568 scope: &mut self.scope,
569 };
570
571 node.arg.visit_with(&mut v);
572 return;
573 }
574
575 node.visit_mut_children_with(self)
576 }
577
578 fn visit_mut_update_expr(&mut self, node: &mut UpdateExpr) {
579 let mut v = IdentListVisitor {
580 scope: &mut self.scope,
581 };
582
583 node.arg.visit_with(&mut v);
584 }
585
586 fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
587 self.var_decl_kind = decl.kind;
588
589 decl.visit_mut_children_with(self)
590 }
591
592 fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
593 let kind = VarType::Var(self.var_decl_kind);
594 node.init.visit_mut_with(self);
595
596 self.pat_mode = PatFoldingMode::VarDecl;
597
598 match self.phase {
599 Phase::Analysis => {
600 if let Pat::Ident(ref name) = node.name {
601 match &node.init {
603 None => {
604 if self.var_decl_kind != VarDeclKind::Const {
605 self.declare(name.to_id(), None, true, kind);
606 }
607 }
608
609 Some(e)
611 if (e.is_lit() || e.is_ident())
612 && self.var_decl_kind == VarDeclKind::Const =>
613 {
614 if self.is_first_run {
615 self.scope
616 .constants
617 .insert(name.to_id(), Some((**e).clone()));
618 }
619 }
620 Some(..) if self.var_decl_kind == VarDeclKind::Const => {
621 if self.is_first_run {
622 self.scope.constants.insert(name.to_id(), None);
623 }
624 }
625
626 Some(e) | Some(e) if e.is_lit() || e.is_ident() => {
628 self.declare(name.to_id(), Some(Cow::Borrowed(e)), false, kind);
629
630 if self.scope.is_inline_prevented(e) {
631 self.scope.prevent_inline(&name.to_id());
632 }
633 }
634 Some(ref e) => {
635 if self.var_decl_kind != VarDeclKind::Const {
636 self.declare(name.to_id(), Some(Cow::Borrowed(e)), false, kind);
637
638 if contains_this_expr(&node.init) {
639 self.scope.prevent_inline(&name.to_id());
640 return;
641 }
642 }
643 }
644 }
645 }
646 }
647 Phase::Inlining => {
648 if let Pat::Ident(ref name) = node.name {
649 if self.var_decl_kind != VarDeclKind::Const {
650 let id = name.to_id();
651
652 tracing::trace!("Trying to optimize variable declaration: {:?}", id);
653
654 if self.scope.is_inline_prevented(&Ident::from(name).into())
655 || !self.scope.has_same_this(&id, node.init.as_deref())
656 {
657 tracing::trace!("Inline is prevented for {:?}", id);
658 return;
659 }
660
661 let mut init = node.init.take();
662 init.visit_mut_with(self);
663 tracing::trace!("\tInit: {:?}", init);
664
665 if let Some(init) = &init {
666 if let Expr::Ident(ri) = &**init {
667 self.declare(
668 name.to_id(),
669 Some(Cow::Owned(ri.clone().into())),
670 false,
671 kind,
672 );
673 }
674 }
675
676 if let Some(ref e) = init {
677 if self.scope.is_inline_prevented(e) {
678 tracing::trace!(
679 "Inlining is not possible as inline of the initialization was \
680 prevented"
681 );
682 node.init = init;
683 self.scope.prevent_inline(&name.to_id());
684 return;
685 }
686 }
687
688 let e = match init {
689 None => None,
690 Some(e) if e.is_lit() || e.is_ident() => Some(e),
691 Some(e) => {
692 let e = *e;
693 if self.scope.is_inline_prevented(&Ident::from(name).into()) {
694 node.init = Some(Box::new(e));
695 return;
696 }
697
698 if let Some(cnt) = self.scope.read_cnt(&name.to_id()) {
699 if cnt == 1 {
700 Some(Box::new(e))
701 } else {
702 node.init = Some(Box::new(e));
703 return;
704 }
705 } else {
706 node.init = Some(Box::new(e));
707 return;
708 }
709 }
710 };
711
712 self.declare(name.to_id(), e.map(|e| Cow::Owned(*e)), false, kind);
716
717 return;
718 }
719 }
720 }
721 }
722
723 node.name.visit_mut_with(self);
724 }
725
726 fn visit_mut_while_stmt(&mut self, node: &mut WhileStmt) {
727 {
728 node.test.visit_with(&mut IdentListVisitor {
729 scope: &mut self.scope,
730 });
731 }
732
733 node.test.visit_mut_with(self);
734 self.visit_with_child(ScopeKind::Loop, &mut node.body);
735 }
736}
737
738#[derive(Debug)]
739struct IdentListVisitor<'a, 'b> {
740 scope: &'a mut Scope<'b>,
741}
742
743impl Visit for IdentListVisitor<'_, '_> {
744 noop_visit_type!();
745
746 visit_obj_and_computed!();
747
748 fn visit_ident(&mut self, node: &Ident) {
749 self.scope.add_write(&node.to_id(), true);
750 }
751}
752
753struct WriteVisitor<'a, 'b> {
755 scope: &'a mut Scope<'b>,
756}
757
758impl Visit for WriteVisitor<'_, '_> {
759 noop_visit_type!();
760
761 visit_obj_and_computed!();
762
763 fn visit_ident(&mut self, node: &Ident) {
764 self.scope.add_write(&node.to_id(), false);
765 }
766}