swc_ecma_minifier/compress/hoist_decls.rs
1use par_iter::prelude::*;
2use rustc_hash::FxHashSet;
3use swc_common::{pass::Repeated, util::take::Take, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_usage_analyzer::analyzer::UsageAnalyzer;
6use swc_ecma_utils::{find_pat_ids, StmtLike};
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
8
9use super::util::drop_invalid_stmts;
10use crate::{
11 program_data::{ProgramData, VarUsageInfoFlags},
12 util::{is_hoisted_var_decl_without_init, sort::is_sorted_by, IsModuleItem, ModuleItemExt},
13};
14
15pub(super) struct DeclHoisterConfig {
16 pub hoist_fns: bool,
17 pub hoist_vars: bool,
18 pub _top_level: bool,
19}
20
21pub(super) fn decl_hoister(config: DeclHoisterConfig, data: &ProgramData) -> Hoister {
22 Hoister {
23 config,
24 changed: false,
25 data,
26 }
27}
28
29pub(super) struct Hoister<'a> {
30 config: DeclHoisterConfig,
31 changed: bool,
32 data: &'a ProgramData,
33}
34
35impl Repeated for Hoister<'_> {
36 fn changed(&self) -> bool {
37 self.changed
38 }
39
40 fn reset(&mut self) {
41 self.changed = false;
42 }
43}
44
45impl Hoister<'_> {
46 fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
47 where
48 T: StmtLike + IsModuleItem + ModuleItemExt,
49 Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer<ProgramData>>,
50 {
51 stmts.visit_mut_children_with(self);
52 let len = stmts.len();
53 let should_hoist = !is_sorted_by(
54 stmts.iter().map(|stmt| match stmt.as_stmt() {
55 Some(stmt) => match stmt {
56 Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
57 Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => {
58 let ids: Vec<Id> = find_pat_ids(&var.decls);
59
60 if ids.iter().any(|id| {
61 self.data
62 .vars
63 .get(id)
64 .map(|v| !v.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL))
65 .unwrap_or(false)
66 }) {
67 2
68 } else {
69 3
70 }
71 }
72 _ => 3,
73 },
74 None => 3,
75 }),
76 PartialOrd::partial_cmp,
77 ) || (self.config.hoist_vars
78 && if len >= *crate::LIGHT_TASK_PARALLELS {
79 stmts.par_chunks(2).any(|stmts| {
80 is_hoisted_var_decl_without_init(&stmts[0])
81 && is_hoisted_var_decl_without_init(&stmts[1])
82 })
83 } else {
84 stmts.windows(2).any(|stmts| {
85 is_hoisted_var_decl_without_init(&stmts[0])
86 && is_hoisted_var_decl_without_init(&stmts[1])
87 })
88 });
89
90 if !should_hoist {
91 return;
92 }
93 self.changed = true;
94
95 let mut var_decls = Vec::new();
96 let mut fn_decls = Vec::with_capacity(stmts.len());
97 let mut new_stmts = Vec::with_capacity(stmts.len());
98 let mut done = FxHashSet::default();
99
100 let mut found_non_var_decl = false;
101 for stmt in stmts.take() {
102 match stmt.try_into_stmt() {
103 Ok(stmt) => {
104 // Seaarch for variable declarations.
105 match stmt {
106 Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => {
107 // Move functions to top.
108 fn_decls.push(T::from(stmt))
109 }
110
111 Stmt::Decl(Decl::Var(var))
112 if matches!(
113 &*var,
114 VarDecl {
115 kind: VarDeclKind::Var,
116 ..
117 }
118 ) && found_non_var_decl =>
119 {
120 let mut exprs = Vec::new();
121 for decl in var.decls {
122 let ids: Vec<Ident> = find_pat_ids(&decl.name);
123
124 for id in ids {
125 if done.insert(id.to_id()) {
126 // If the enclosing function declares parameter with same
127 // name, we can drop a varaible.
128 if decl.init.is_none()
129 && self
130 .data
131 .vars
132 .get(&id.to_id())
133 .map(|v| {
134 v.flags.contains(
135 VarUsageInfoFlags::DECLARED_AS_FN_PARAM,
136 )
137 })
138 .unwrap_or(false)
139 {
140 continue;
141 }
142
143 var_decls.push(VarDeclarator {
144 span: DUMMY_SP,
145 name: id.into(),
146 init: None,
147 definite: false,
148 })
149 }
150 }
151
152 if let Some(init) = decl.init {
153 //
154 exprs.push(
155 AssignExpr {
156 span: decl.span,
157 left: decl.name.try_into().unwrap(),
158 op: op!("="),
159 right: init,
160 }
161 .into(),
162 );
163 }
164 }
165
166 if exprs.is_empty() {
167 continue;
168 }
169 new_stmts.push(T::from(
170 ExprStmt {
171 span: var.span,
172 expr: if exprs.len() == 1 {
173 exprs.into_iter().next().unwrap()
174 } else {
175 SeqExpr {
176 span: DUMMY_SP,
177 exprs,
178 }
179 .into()
180 },
181 }
182 .into(),
183 ))
184 }
185
186 Stmt::Decl(Decl::Var(v))
187 if matches!(
188 &*v,
189 VarDecl {
190 kind: VarDeclKind::Var,
191 ..
192 }
193 ) =>
194 {
195 // It can be merged because we didn't found normal statement.
196 //
197 // Code like
198 //
199 // var a = 1;
200 // var b = 3;
201 //
202 // will be merged.
203 var_decls.extend(v.decls.into_iter().filter(|decl| {
204 // We should preserve if init exists because
205 //
206 // var a = 2, a = 3;
207 //
208 // is valid javascript code.
209
210 let preserve = match &decl.name {
211 Pat::Ident(name) => {
212 // If the enclosing function declares parameter with same
213 // name, we can drop a varaible. (If it's side-effect free).
214 if decl.init.is_none()
215 && self
216 .data
217 .vars
218 .get(&name.to_id())
219 .map(|v| {
220 v.flags.contains(
221 VarUsageInfoFlags::DECLARED_AS_FN_PARAM,
222 )
223 })
224 .unwrap_or(false)
225 {
226 return false;
227 }
228
229 done.insert(name.to_id())
230 }
231 _ => true,
232 };
233
234 preserve || decl.init.is_some()
235 }));
236 }
237
238 Stmt::Decl(Decl::Var(..)) => new_stmts.push(T::from(stmt)),
239 _ => {
240 if let Stmt::Throw(..) = stmt {
241 fn_decls.push(T::from(
242 VarDecl {
243 span: DUMMY_SP,
244 kind: VarDeclKind::Var,
245 declare: false,
246 decls: var_decls.take(),
247 ..Default::default()
248 }
249 .into(),
250 ));
251 }
252 found_non_var_decl = true;
253 new_stmts.push(T::from(stmt))
254 }
255 }
256 }
257 Err(stmt) => new_stmts.push(stmt),
258 }
259 }
260
261 fn_decls.push(T::from(
262 VarDecl {
263 span: DUMMY_SP,
264 kind: VarDeclKind::Var,
265 declare: false,
266 decls: var_decls,
267 ..Default::default()
268 }
269 .into(),
270 ));
271 fn_decls.extend(new_stmts);
272
273 drop_invalid_stmts(&mut fn_decls);
274
275 *stmts = fn_decls;
276 }
277}
278
279impl VisitMut for Hoister<'_> {
280 noop_visit_mut_type!(fail);
281
282 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
283 self.handle_stmt_likes(stmts);
284 }
285
286 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
287 self.handle_stmt_likes(stmts);
288 }
289}