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 }
142 }
143
144 _ => {
145 let mut v = IdentListVisitor {
146 scope: &mut self.scope,
147 };
148
149 e.left.visit_with(&mut v);
150 e.right.visit_with(&mut v)
151 }
152 }
153
154 e.right.visit_mut_with(self);
155
156 if self.scope.is_inline_prevented(&e.right) {
157 let ids: Vec<Id> = find_pat_ids(&e.left);
159 for id in ids {
160 self.scope.prevent_inline(&id);
161 }
162 return;
163 }
164
165 if let Some(i) = e.left.as_ident() {
167 let id = i.to_id();
168 self.scope.add_write(&id, false);
169
170 if let Some(var) = self.scope.find_binding(&id) {
171 if !var.is_inline_prevented() {
172 match *e.right {
173 Expr::Lit(..) | Expr::Ident(..) => {
174 *var.value.borrow_mut() = Some(*e.right.clone());
175 }
176
177 _ => {
178 *var.value.borrow_mut() = None;
179 }
180 }
181 }
182 }
183 }
184 }
185
186 fn visit_mut_block_stmt(&mut self, node: &mut BlockStmt) {
187 self.visit_with_child(ScopeKind::Block, node)
188 }
189
190 fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
191 node.callee.visit_mut_with(self);
192
193 if self.phase == Phase::Analysis {
194 if let Callee::Expr(ref callee) = node.callee {
195 self.scope.mark_this_sensitive(callee);
196 }
197 }
198
199 node.args.visit_children_with(&mut WriteVisitor {
201 scope: &mut self.scope,
202 });
203
204 node.args.visit_mut_with(self);
205
206 self.scope.store_inline_barrier(self.phase);
207 }
208
209 fn visit_mut_catch_clause(&mut self, node: &mut CatchClause) {
210 self.with_child(ScopeKind::Block, move |child| {
211 child.pat_mode = PatFoldingMode::CatchParam;
212 node.param.visit_mut_with(child);
213 match child.phase {
214 Phase::Analysis => {
215 let ids: Vec<Id> = find_pat_ids(&node.param);
216 for id in ids {
217 child.scope.prevent_inline(&id);
218 }
219 }
220 Phase::Inlining => {}
221 }
222
223 node.body.visit_mut_with(child);
224 })
225 }
226
227 fn visit_mut_do_while_stmt(&mut self, node: &mut DoWhileStmt) {
228 {
229 node.test.visit_with(&mut IdentListVisitor {
230 scope: &mut self.scope,
231 });
232 }
233
234 node.test.visit_mut_with(self);
235 self.visit_with_child(ScopeKind::Loop, &mut node.body);
236 }
237
238 fn visit_mut_expr(&mut self, node: &mut Expr) {
239 node.visit_mut_children_with(self);
240
241 if self.phase == Phase::Inlining {
255 if let Expr::Assign(e @ AssignExpr { op: op!("="), .. }) = node {
256 if let Some(i) = e.left.as_ident() {
257 if let Some(var) = self.scope.find_binding_from_current(&i.to_id()) {
258 if var.is_undefined.get()
259 && !var.is_inline_prevented()
260 && !self.scope.is_inline_prevented(&e.right)
261 {
262 *var.value.borrow_mut() = Some(*e.right.clone());
263 var.is_undefined.set(false);
264 *node = *e.right.take();
265 return;
266 }
267 }
268 }
269
270 return;
271 }
272 }
273
274 if let Expr::Ident(ref i) = node {
275 let id = i.to_id();
276 if self.is_first_run {
277 if let Some(expr) = self.scope.find_constant(&id) {
278 self.changed = true;
279 let mut expr = expr.clone();
280 expr.visit_mut_with(self);
281 *node = expr;
282 return;
283 }
284 }
285
286 match self.phase {
287 Phase::Analysis => {
288 if self.in_test {
289 if let Some(var) = self.scope.find_binding(&id) {
290 match &*var.value.borrow() {
291 Some(Expr::Ident(..)) | Some(Expr::Lit(..)) => {}
292 _ => {
293 self.scope.prevent_inline(&id);
294 }
295 }
296 }
297 }
298 self.scope.add_read(&id);
299 }
300 Phase::Inlining => {
301 tracing::trace!("Trying to inline: {:?}", id);
302 let expr = if let Some(var) = self.scope.find_binding(&id) {
303 tracing::trace!("VarInfo: {:?}", var);
304 if !var.is_inline_prevented() {
305 let expr = var.value.borrow();
306
307 if let Some(expr) = &*expr {
308 tracing::debug!("Inlining: {:?}", id);
309
310 if *node != *expr {
311 self.changed = true;
312 }
313
314 Some(expr.clone())
315 } else {
316 tracing::debug!("Inlining: {:?} as undefined", id);
317
318 if var.is_undefined.get() {
319 *node = *Expr::undefined(i.span);
320 return;
321 } else {
322 tracing::trace!("Not a cheap expression");
323 None
324 }
325 }
326 } else {
327 tracing::trace!("Inlining is prevented");
328 None
329 }
330 } else {
331 None
332 };
333
334 if let Some(expr) = expr {
335 *node = expr;
336 }
337 }
338 }
339 }
340
341 if let Expr::Bin(b) = node {
342 match b.op {
343 BinaryOp::LogicalAnd | BinaryOp::LogicalOr => {
344 self.visit_with_child(ScopeKind::Cond, &mut b.right);
345 }
346 _ => {}
347 }
348 }
349 }
350
351 fn visit_mut_fn_decl(&mut self, node: &mut FnDecl) {
352 if self.phase == Phase::Analysis {
353 self.declare(
354 node.ident.to_id(),
355 None,
356 true,
357 VarType::Var(VarDeclKind::Var),
358 );
359 }
360
361 self.with_child(ScopeKind::Fn { named: true }, |child| {
362 child.pat_mode = PatFoldingMode::Param;
363 node.function.params.visit_mut_with(child);
364 match &mut node.function.body {
365 None => {}
366 Some(v) => {
367 v.visit_mut_children_with(child);
368 }
369 };
370 });
371 }
372
373 fn visit_mut_fn_expr(&mut self, node: &mut FnExpr) {
374 if let Some(ref ident) = node.ident {
375 self.scope.add_write(&ident.to_id(), true);
376 }
377
378 node.function.visit_mut_with(self)
379 }
380
381 fn visit_mut_for_in_stmt(&mut self, node: &mut ForInStmt) {
382 self.pat_mode = PatFoldingMode::Param;
383 node.left.visit_mut_with(self);
384
385 {
386 node.left.visit_with(&mut IdentListVisitor {
387 scope: &mut self.scope,
388 });
389 }
390
391 {
392 node.right.visit_with(&mut IdentListVisitor {
393 scope: &mut self.scope,
394 });
395 }
396
397 node.right.visit_mut_with(self);
398 self.visit_with_child(ScopeKind::Loop, &mut node.body);
399 }
400
401 fn visit_mut_for_of_stmt(&mut self, node: &mut ForOfStmt) {
402 self.pat_mode = PatFoldingMode::Param;
403 node.left.visit_mut_with(self);
404
405 {
406 node.left.visit_with(&mut IdentListVisitor {
407 scope: &mut self.scope,
408 });
409 }
410 {
411 node.right.visit_with(&mut IdentListVisitor {
412 scope: &mut self.scope,
413 });
414 }
415
416 node.right.visit_mut_with(self);
417 self.visit_with_child(ScopeKind::Loop, &mut node.body);
418 }
419
420 fn visit_mut_for_stmt(&mut self, node: &mut ForStmt) {
421 {
422 node.init.visit_with(&mut IdentListVisitor {
423 scope: &mut self.scope,
424 });
425 }
426 {
427 node.test.visit_with(&mut IdentListVisitor {
428 scope: &mut self.scope,
429 });
430 }
431 {
432 node.update.visit_with(&mut IdentListVisitor {
433 scope: &mut self.scope,
434 });
435 }
436
437 node.init.visit_mut_with(self);
438 node.test.visit_mut_with(self);
439 node.update.visit_mut_with(self);
440 self.visit_with_child(ScopeKind::Loop, &mut node.body);
441
442 if node.init.is_none() && node.test.is_none() && node.update.is_none() {
443 self.scope.store_inline_barrier(self.phase);
444 }
445 }
446
447 fn visit_mut_function(&mut self, node: &mut Function) {
448 self.with_child(ScopeKind::Fn { named: false }, move |child| {
449 child.pat_mode = PatFoldingMode::Param;
450 node.params.visit_mut_with(child);
451 match &mut node.body {
452 None => None,
453 Some(v) => {
454 v.visit_mut_children_with(child);
455 Some(())
456 }
457 };
458 })
459 }
460
461 fn visit_mut_if_stmt(&mut self, stmt: &mut IfStmt) {
462 let old_in_test = self.in_test;
463 self.in_test = true;
464 stmt.test.visit_mut_with(self);
465 self.in_test = old_in_test;
466
467 self.visit_with_child(ScopeKind::Cond, &mut stmt.cons);
468 self.visit_with_child(ScopeKind::Cond, &mut stmt.alt);
469 }
470
471 fn visit_mut_program(&mut self, program: &mut Program) {
472 let _tracing = span!(Level::ERROR, "inlining", pass = self.pass).entered();
473
474 let old_phase = self.phase;
475
476 self.phase = Phase::Analysis;
477 program.visit_mut_children_with(self);
478
479 tracing::trace!("Switching to Inlining phase");
480
481 self.phase = Phase::Inlining;
483 program.visit_mut_children_with(self);
484
485 self.phase = old_phase;
486 }
487
488 fn visit_mut_new_expr(&mut self, node: &mut NewExpr) {
489 node.callee.visit_mut_with(self);
490 if self.phase == Phase::Analysis {
491 self.scope.mark_this_sensitive(&node.callee);
492 }
493
494 node.args.visit_mut_with(self);
495
496 self.scope.store_inline_barrier(self.phase);
497 }
498
499 fn visit_mut_pat(&mut self, node: &mut Pat) {
500 node.visit_mut_children_with(self);
501
502 if let Pat::Ident(ref i) = node {
503 match self.pat_mode {
504 PatFoldingMode::Param => {
505 self.declare(
506 i.to_id(),
507 Some(Cow::Owned(Ident::from(i).into())),
508 false,
509 VarType::Param,
510 );
511 }
512 PatFoldingMode::CatchParam => {
513 self.declare(
514 i.to_id(),
515 Some(Cow::Owned(Ident::from(i).into())),
516 false,
517 VarType::Var(VarDeclKind::Var),
518 );
519 }
520 PatFoldingMode::VarDecl => {}
521 PatFoldingMode::Assign => {
522 if self.scope.find_binding_from_current(&i.to_id()).is_some() {
523 } else {
524 self.scope.add_write(&i.to_id(), false);
525 }
526 }
527 }
528 }
529 }
530
531 fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
532 let old_phase = self.phase;
533
534 match old_phase {
535 Phase::Analysis => {
536 items.visit_mut_children_with(self);
537 }
538 Phase::Inlining => {
539 self.phase = Phase::Analysis;
540 items.visit_mut_children_with(self);
541
542 self.phase = Phase::Inlining;
544 items.visit_mut_children_with(self);
545
546 self.phase = old_phase
547 }
548 }
549 }
550
551 fn visit_mut_switch_case(&mut self, node: &mut SwitchCase) {
552 self.visit_with_child(ScopeKind::Block, node)
553 }
554
555 fn visit_mut_try_stmt(&mut self, node: &mut TryStmt) {
556 node.block.visit_with(&mut IdentListVisitor {
557 scope: &mut self.scope,
558 });
559
560 node.handler.visit_mut_with(self)
561 }
562
563 fn visit_mut_unary_expr(&mut self, node: &mut UnaryExpr) {
564 if let op!("delete") = node.op {
565 let mut v = IdentListVisitor {
566 scope: &mut self.scope,
567 };
568
569 node.arg.visit_with(&mut v);
570 return;
571 }
572
573 node.visit_mut_children_with(self)
574 }
575
576 fn visit_mut_update_expr(&mut self, node: &mut UpdateExpr) {
577 let mut v = IdentListVisitor {
578 scope: &mut self.scope,
579 };
580
581 node.arg.visit_with(&mut v);
582 }
583
584 fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
585 self.var_decl_kind = decl.kind;
586
587 decl.visit_mut_children_with(self)
588 }
589
590 fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
591 let kind = VarType::Var(self.var_decl_kind);
592 node.init.visit_mut_with(self);
593
594 self.pat_mode = PatFoldingMode::VarDecl;
595
596 match self.phase {
597 Phase::Analysis => {
598 if let Pat::Ident(ref name) = node.name {
599 match &node.init {
601 None => {
602 if self.var_decl_kind != VarDeclKind::Const {
603 self.declare(name.to_id(), None, true, kind);
604 }
605 }
606
607 Some(e)
609 if (e.is_lit() || e.is_ident())
610 && self.var_decl_kind == VarDeclKind::Const =>
611 {
612 if self.is_first_run {
613 self.scope
614 .constants
615 .insert(name.to_id(), Some((**e).clone()));
616 }
617 }
618 Some(..) if self.var_decl_kind == VarDeclKind::Const => {
619 if self.is_first_run {
620 self.scope.constants.insert(name.to_id(), None);
621 }
622 }
623
624 Some(e) | Some(e) if e.is_lit() || e.is_ident() => {
626 self.declare(name.to_id(), Some(Cow::Borrowed(e)), false, kind);
627
628 if self.scope.is_inline_prevented(e) {
629 self.scope.prevent_inline(&name.to_id());
630 }
631 }
632 Some(ref e) => {
633 if self.var_decl_kind != VarDeclKind::Const {
634 self.declare(name.to_id(), Some(Cow::Borrowed(e)), false, kind);
635
636 if contains_this_expr(&node.init) {
637 self.scope.prevent_inline(&name.to_id());
638 return;
639 }
640 }
641 }
642 }
643 }
644 }
645 Phase::Inlining => {
646 if let Pat::Ident(ref name) = node.name {
647 if self.var_decl_kind != VarDeclKind::Const {
648 let id = name.to_id();
649
650 tracing::trace!("Trying to optimize variable declaration: {:?}", id);
651
652 if self.scope.is_inline_prevented(&Ident::from(name).into())
653 || !self.scope.has_same_this(&id, node.init.as_deref())
654 {
655 tracing::trace!("Inline is prevented for {:?}", id);
656 return;
657 }
658
659 let mut init = node.init.take();
660 init.visit_mut_with(self);
661 tracing::trace!("\tInit: {:?}", init);
662
663 if let Some(init) = &init {
664 if let Expr::Ident(ri) = &**init {
665 self.declare(
666 name.to_id(),
667 Some(Cow::Owned(ri.clone().into())),
668 false,
669 kind,
670 );
671 }
672 }
673
674 if let Some(ref e) = init {
675 if self.scope.is_inline_prevented(e) {
676 tracing::trace!(
677 "Inlining is not possible as inline of the initialization was \
678 prevented"
679 );
680 node.init = init;
681 self.scope.prevent_inline(&name.to_id());
682 return;
683 }
684 }
685
686 let e = match init {
687 None => None,
688 Some(e) if e.is_lit() || e.is_ident() => Some(e),
689 Some(e) => {
690 let e = *e;
691 if self.scope.is_inline_prevented(&Ident::from(name).into()) {
692 node.init = Some(Box::new(e));
693 return;
694 }
695
696 if let Some(cnt) = self.scope.read_cnt(&name.to_id()) {
697 if cnt == 1 {
698 Some(Box::new(e))
699 } else {
700 node.init = Some(Box::new(e));
701 return;
702 }
703 } else {
704 node.init = Some(Box::new(e));
705 return;
706 }
707 }
708 };
709
710 self.declare(name.to_id(), e.map(|e| Cow::Owned(*e)), false, kind);
714
715 return;
716 }
717 }
718 }
719 }
720
721 node.name.visit_mut_with(self);
722 }
723
724 fn visit_mut_while_stmt(&mut self, node: &mut WhileStmt) {
725 {
726 node.test.visit_with(&mut IdentListVisitor {
727 scope: &mut self.scope,
728 });
729 }
730
731 node.test.visit_mut_with(self);
732 self.visit_with_child(ScopeKind::Loop, &mut node.body);
733 }
734}
735
736#[derive(Debug)]
737struct IdentListVisitor<'a, 'b> {
738 scope: &'a mut Scope<'b>,
739}
740
741impl Visit for IdentListVisitor<'_, '_> {
742 noop_visit_type!();
743
744 visit_obj_and_computed!();
745
746 fn visit_ident(&mut self, node: &Ident) {
747 self.scope.add_write(&node.to_id(), true);
748 }
749}
750
751struct WriteVisitor<'a, 'b> {
753 scope: &'a mut Scope<'b>,
754}
755
756impl Visit for WriteVisitor<'_, '_> {
757 noop_visit_type!();
758
759 visit_obj_and_computed!();
760
761 fn visit_ident(&mut self, node: &Ident) {
762 self.scope.add_write(&node.to_id(), false);
763 }
764}