swc_ecma_transforms_optimization/simplify/inlining/
mod.rs

1use 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
23/// Note: this pass assumes that resolver is invoked before the pass.
24///
25/// As swc focuses on reducing gzipped file size, all strings are inlined.
26///
27///
28/// # TODOs
29///
30///  - Handling of `void 0`
31///  - Properly handle binary expressions.
32///  - Track variables access by a function
33///
34/// Currently all functions are treated as a black box, and all the pass gives
35/// up inlining variables across a function call or a constructor call.
36pub 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                        //
128                        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            // Prevent inline for lhd
160            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        //
168        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        // args should not be inlined
202        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        // Codes like
244        //
245        //      var y;
246        //      y = x;
247        //      use(y)
248        //
249        //  should be transformed to
250        //
251        //      var y;
252        //      x;
253        //      use(x)
254        //
255        // We cannot know if this is possible while analysis phase
256        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        // Inline
484        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                // Inline
545                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                    //
602                    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                        // Constants
610                        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                        // Bindings
627                        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                        // tracing::trace!("({}): Inserting {:?}", self.scope.depth(),
713                        // name.to_id());
714
715                        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
753/// Mark idents as `written`.
754struct 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}