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                }
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            // Prevent inline for lhd
158            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        //
166        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        // args should not be inlined
200        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        // Codes like
242        //
243        //      var y;
244        //      y = x;
245        //      use(y)
246        //
247        //  should be transformed to
248        //
249        //      var y;
250        //      x;
251        //      use(x)
252        //
253        // We cannot know if this is possible while analysis phase
254        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        // Inline
482        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                // Inline
543                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                    //
600                    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                        // Constants
608                        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                        // Bindings
625                        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                        // tracing::trace!("({}): Inserting {:?}", self.scope.depth(),
711                        // name.to_id());
712
713                        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
751/// Mark idents as `written`.
752struct 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}