1#![cfg(not(feature = "no_optimize"))]
3
4use crate::ast::{
5 ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer,
6 SwitchCasesCollection,
7};
8use crate::engine::{
9 KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT,
10 KEYWORD_TYPE_OF, OP_NOT,
11};
12use crate::eval::{Caches, GlobalRuntimeState};
13use crate::func::builtin::get_builtin_binary_op_fn;
14use crate::func::hashing::get_hasher;
15use crate::module::ModuleFlags;
16use crate::tokenizer::Token;
17use crate::{
18 calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position,
19 Scope, AST,
20};
21#[cfg(feature = "no_std")]
22use std::prelude::v1::*;
23use std::{
24 any::TypeId,
25 borrow::Cow,
26 convert::TryFrom,
27 hash::{Hash, Hasher},
28 mem,
29};
30
31#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)]
35#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
36#[non_exhaustive]
37pub enum OptimizationLevel {
38 None,
40 #[default]
42 Simple,
43 Full,
46}
47
48#[derive(Debug, Clone)]
50struct OptimizerState<'a> {
51 is_dirty: bool,
53 variables: Vec<(ImmutableString, Option<Cow<'a, Dynamic>>)>,
55 propagate_constants: bool,
57 engine: &'a Engine,
59 scope: Option<&'a Scope<'a>>,
61 global: GlobalRuntimeState,
63 caches: Caches,
65 optimization_level: OptimizationLevel,
67}
68
69impl<'a> OptimizerState<'a> {
70 #[inline(always)]
72 pub fn new(
73 engine: &'a Engine,
74 lib: &'a [crate::SharedModule],
75 scope: Option<&'a Scope<'a>>,
76 optimization_level: OptimizationLevel,
77 ) -> Self {
78 let mut _global = GlobalRuntimeState::new(engine);
79 let _lib = lib;
80
81 #[cfg(not(feature = "no_function"))]
82 {
83 _global.lib = _lib.into();
84 }
85
86 Self {
87 is_dirty: false,
88 variables: Vec::new(),
89 propagate_constants: true,
90 engine,
91 scope,
92 global: _global,
93 caches: Caches::new(),
94 optimization_level,
95 }
96 }
97 #[inline(always)]
99 pub fn set_dirty(&mut self) {
100 self.is_dirty = true;
101 }
102 #[inline(always)]
104 pub fn clear_dirty(&mut self) {
105 self.is_dirty = false;
106 }
107 #[inline(always)]
109 pub const fn is_dirty(&self) -> bool {
110 self.is_dirty
111 }
112 #[inline(always)]
114 pub fn rewind_var(&mut self, len: usize) {
115 self.variables.truncate(len);
116 }
117 #[inline(always)]
121 pub fn push_var<'x: 'a>(&mut self, name: ImmutableString, value: Option<Cow<'x, Dynamic>>) {
122 self.variables.push((name, value));
123 }
124 #[inline]
126 pub fn find_literal_constant(&self, name: &str) -> Option<&Dynamic> {
127 self.variables
128 .iter()
129 .rev()
130 .find(|(n, _)| n == name)
131 .and_then(|(_, value)| value.as_deref())
132 }
133 #[inline]
135 pub fn call_fn_with_const_args(
136 &mut self,
137 fn_name: &str,
138 op_token: Option<&Token>,
139 arg_values: &mut [Dynamic],
140 ) -> Option<Dynamic> {
141 self.engine
142 .exec_native_fn_call(
143 &mut self.global,
144 &mut self.caches,
145 fn_name,
146 op_token,
147 calc_fn_hash(None, fn_name, arg_values.len()),
148 &mut arg_values.iter_mut().collect::<FnArgsVec<_>>(),
149 false,
150 true,
151 Position::NONE,
152 )
153 .ok()
154 .map(|(v, ..)| v)
155 }
156}
157
158fn optimize_stmt_block(
160 mut statements: StmtBlockContainer,
161 state: &mut OptimizerState,
162 preserve_result: bool,
163 is_internal: bool,
164 reduce_return: bool,
165) -> StmtBlockContainer {
166 if statements.is_empty() {
167 return statements;
168 }
169
170 let mut is_dirty = state.is_dirty();
171
172 let is_pure = if is_internal {
173 Stmt::is_internally_pure
174 } else {
175 Stmt::is_pure
176 };
177
178 while let Some(n) = statements.iter().position(
180 |s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)),
181 ) {
182 let (first, second) = statements.split_at_mut(n);
183 let mut stmt = second[0].take();
184 let stmts = match stmt {
185 Stmt::Block(ref mut block, ..) => block.statements_mut(),
186 _ => unreachable!("Stmt::Block expected but gets {:?}", stmt),
187 };
188 statements = first
189 .iter_mut()
190 .map(mem::take)
191 .chain(stmts.iter_mut().map(mem::take))
192 .chain(second.iter_mut().skip(1).map(mem::take))
193 .collect();
194
195 is_dirty = true;
196 }
197
198 loop {
200 state.clear_dirty();
201
202 let orig_constants_len = state.variables.len(); let orig_propagate_constants = state.propagate_constants;
204
205 let mut dead_code = false;
207
208 statements.retain(|stmt| {
209 if dead_code {
210 state.set_dirty();
211 false
212 } else if stmt.is_control_flow_break() {
213 dead_code = true;
214 true
215 } else {
216 true
217 }
218 });
219
220 statements.iter_mut().for_each(|stmt| {
222 match stmt {
223 Stmt::Var(x, options, ..) => {
224 optimize_expr(&mut x.1, state, false);
225
226 let value = if options.intersects(ASTFlags::CONSTANT) && x.1.is_constant() {
227 Some(Cow::Owned(x.1.get_literal_value().unwrap()))
229 } else {
230 None
232 };
233 state.push_var(x.0.name.clone(), value);
234 }
235 _ => optimize_stmt(stmt, state, preserve_result),
237 }
238 });
239
240 let mut index = 0;
242 let mut first_non_constant = statements
243 .iter()
244 .rev()
245 .position(|stmt| match stmt {
246 stmt if !is_pure(stmt) => true,
247
248 Stmt::Var(x, ..) if x.1.is_constant() => true,
249 Stmt::Expr(e) if !e.is_constant() => true,
250
251 #[cfg(not(feature = "no_module"))]
252 Stmt::Import(x, ..) if !x.0.is_constant() => true,
253
254 _ => false,
255 })
256 .map_or(0, |n| statements.len() - n - 1);
257
258 while index < statements.len() {
259 if preserve_result && index >= statements.len() - 1 {
260 break;
261 }
262 match statements[index] {
263 ref stmt if is_pure(stmt) && index >= first_non_constant => {
264 state.set_dirty();
265 statements.remove(index);
266 }
267 ref stmt if stmt.is_pure() => {
268 state.set_dirty();
269 if index < first_non_constant {
270 first_non_constant -= 1;
271 }
272 statements.remove(index);
273 }
274 _ => index += 1,
275 }
276 }
277
278 if preserve_result {
281 loop {
282 match statements[..] {
283 [Stmt::Return(None, options, ..)]
285 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
286 {
287 state.set_dirty();
288 statements.clear();
289 }
290 [ref stmt] if !stmt.returns_value() && is_pure(stmt) => {
291 state.set_dirty();
292 statements.clear();
293 }
294 [.., ref last_stmt, Stmt::Return(None, options, ..)]
296 if reduce_return
297 && !options.intersects(ASTFlags::BREAK)
298 && !last_stmt.returns_value() =>
299 {
300 state.set_dirty();
301 statements.pop().unwrap();
302 }
303 [.., Stmt::Return(ref mut expr, options, pos)]
305 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
306 {
307 state.set_dirty();
308 *statements.last_mut().unwrap() = expr
309 .as_mut()
310 .map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e)));
311 }
312 [.., ref second_last_stmt, Stmt::Noop(..)]
314 if second_last_stmt.returns_value() =>
315 {
316 break
317 }
318 [.., ref second_last_stmt, ref last_stmt]
321 if !last_stmt.returns_value() && is_pure(last_stmt) =>
322 {
323 state.set_dirty();
324 if second_last_stmt.returns_value() {
325 *statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
326 } else {
327 statements.pop().unwrap();
328 }
329 }
330 _ => break,
331 }
332 }
333 } else {
334 loop {
335 match statements[..] {
336 [ref stmt] if is_pure(stmt) => {
337 state.set_dirty();
338 statements.clear();
339 }
340 [.., Stmt::Return(None, options, ..)]
342 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
343 {
344 state.set_dirty();
345 statements.pop().unwrap();
346 }
347 [.., Stmt::Return(Some(ref expr), options, ..)]
349 if reduce_return
350 && !options.intersects(ASTFlags::BREAK)
351 && expr.is_pure() =>
352 {
353 state.set_dirty();
354 statements.pop().unwrap();
355 }
356 [.., ref last_stmt] if is_pure(last_stmt) => {
357 state.set_dirty();
358 statements.pop().unwrap();
359 }
360 _ => break,
361 }
362 }
363 }
364
365 state.rewind_var(orig_constants_len);
367 state.propagate_constants = orig_propagate_constants;
368
369 if !state.is_dirty() {
370 break;
371 }
372
373 is_dirty = true;
374 }
375
376 if is_dirty {
377 state.set_dirty();
378 }
379
380 statements.shrink_to_fit();
381 statements
382}
383
384impl StmtBlock {
385 #[inline(always)]
386 #[must_use]
387 fn take_statements(&mut self) -> StmtBlockContainer {
388 mem::take(self.statements_mut())
389 }
390}
391
392#[inline(always)]
394fn is_hashable_constant(expr: &Expr) -> bool {
395 match expr {
396 _ if !expr.is_constant() => false,
397 Expr::DynamicConstant(v, ..) => v.is_hashable(),
398 _ => false,
399 }
400}
401
402fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) {
404 #[inline(always)]
405 #[must_use]
406 fn is_variable_access(expr: &Expr, _non_qualified: bool) -> bool {
407 match expr {
408 #[cfg(not(feature = "no_module"))]
409 Expr::Variable(x, ..) if _non_qualified && !x.1.is_empty() => false,
410 Expr::Variable(..) => true,
411 _ => false,
412 }
413 }
414
415 match stmt {
416 Stmt::Assignment(x, ..)
418 if !x.0.is_op_assignment()
419 && is_variable_access(&x.1.lhs, true)
420 && matches!(&x.1.rhs, Expr::FnCall(x2, ..)
421 if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment())
422 && x2.args.len() == 2
423 && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
424 ) =>
425 {
426 match x.1.rhs {
427 Expr::FnCall(ref mut x2, pos) => {
428 state.set_dirty();
429 x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos);
430 x.1.rhs = x2.args[1].take();
431 }
432 ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
433 }
434 }
435
436 Stmt::Assignment(x, ..) => {
438 if !is_variable_access(&x.1.lhs, false) {
439 optimize_expr(&mut x.1.lhs, state, false);
440 }
441 optimize_expr(&mut x.1.rhs, state, false);
442 }
443
444 Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => {
446 let condition = &mut x.expr;
447 state.set_dirty();
448
449 let pos = condition.start_position();
450 let mut expr = condition.take();
451 optimize_expr(&mut expr, state, false);
452
453 *stmt = if preserve_result {
454 Stmt::Block(
456 StmtBlock::new(
457 [Stmt::Expr(expr.into()), Stmt::Noop(pos)],
458 pos,
459 Position::NONE,
460 )
461 .into(),
462 )
463 } else {
464 Stmt::Expr(expr.into())
466 };
467 }
468 Stmt::If(x, ..)
470 if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() =>
471 {
472 match x.expr {
473 Expr::BoolConstant(false, pos) => {
474 state.set_dirty();
475 *stmt = Stmt::Noop(pos);
476 }
477 _ => unreachable!("`Expr::BoolConstant`"),
478 }
479 }
480 Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
482 state.set_dirty();
483 let body = x.branch.take_statements();
484 *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
485 statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
486 statements => {
487 Stmt::Block(StmtBlock::new_with_span(statements, x.branch.span()).into())
488 }
489 }
490 }
491 Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
493 state.set_dirty();
494 let body = x.body.take_statements();
495 *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
496 statements if statements.is_empty() => Stmt::Noop(x.body.position()),
497 statements => {
498 Stmt::Block(StmtBlock::new_with_span(statements, x.body.span()).into())
499 }
500 }
501 }
502 Stmt::If(x, ..) => {
504 let FlowControl { expr, body, branch } = &mut **x;
505 optimize_expr(expr, state, false);
506 *body.statements_mut() =
507 optimize_stmt_block(body.take_statements(), state, preserve_result, true, false);
508 *branch.statements_mut() = optimize_stmt_block(
509 branch.take_statements(),
510 state,
511 preserve_result,
512 true,
513 false,
514 );
515 }
516
517 Stmt::Switch(x, pos) if is_hashable_constant(&x.0) => {
519 let (
520 match_expr,
521 SwitchCasesCollection {
522 expressions,
523 cases,
524 ranges,
525 def_case,
526 },
527 ) = &mut **x;
528
529 let value = match_expr.get_literal_value().unwrap();
530 let hasher = &mut get_hasher();
531 value.hash(hasher);
532 let hash = hasher.finish();
533
534 if let Some(case_blocks_list) = cases.get(&hash) {
536 match &case_blocks_list[..] {
537 [] => (),
538 [index] => {
539 let mut b = mem::take(&mut expressions[*index]);
540 cases.clear();
541
542 if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
543 let mut statements = Stmt::Expr(b.rhs.take().into());
545 optimize_stmt(&mut statements, state, true);
546 *stmt = statements;
547 } else {
548 optimize_expr(&mut b.lhs, state, false);
550
551 let branch = def_case.map_or(StmtBlock::NONE, |index| {
552 let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
553 optimize_stmt(&mut def_stmt, state, true);
554 def_stmt.into()
555 });
556 let body = Stmt::Expr(b.rhs.take().into()).into();
557 let expr = b.lhs.take();
558
559 *stmt = Stmt::If(
560 FlowControl { expr, body, branch }.into(),
561 match_expr.start_position(),
562 );
563 }
564
565 state.set_dirty();
566 return;
567 }
568 _ => {
569 for &index in case_blocks_list {
570 let mut b = mem::take(&mut expressions[index]);
571
572 if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
573 let mut statements = Stmt::Expr(b.rhs.take().into());
575 optimize_stmt(&mut statements, state, true);
576 *stmt = statements;
577 state.set_dirty();
578 return;
579 }
580 }
581 }
582 }
583 }
584
585 if !ranges.is_empty() {
587 if ranges.len() == 1
589 || ranges
590 .iter()
591 .all(|r| matches!(expressions[r.index()].lhs, Expr::BoolConstant(true, ..)))
592 {
593 if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
594 let range_block = &mut expressions[r.index()];
595
596 if matches!(range_block.lhs, Expr::BoolConstant(true, ..)) {
597 let block = &mut expressions[r.index()];
599 let mut statements = Stmt::Expr(block.rhs.take().into());
600 optimize_stmt(&mut statements, state, true);
601 *stmt = statements;
602 } else {
603 let mut expr = range_block.lhs.take();
604
605 optimize_expr(&mut expr, state, false);
607
608 let branch = def_case.map_or(StmtBlock::NONE, |index| {
609 let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
610 optimize_stmt(&mut def_stmt, state, true);
611 def_stmt.into()
612 });
613
614 let body = Stmt::Expr(expressions[r.index()].rhs.take().into()).into();
615
616 *stmt = Stmt::If(
617 FlowControl { expr, body, branch }.into(),
618 match_expr.start_position(),
619 );
620 }
621
622 state.set_dirty();
623 return;
624 }
625 } else {
626 if !cases.is_empty() {
628 state.set_dirty();
629 cases.clear();
630 }
631
632 let old_ranges_len = ranges.len();
633
634 ranges.retain(|r| r.contains(&value));
635
636 if ranges.len() != old_ranges_len {
637 state.set_dirty();
638 }
639
640 for r in ranges {
641 let b = &mut expressions[r.index()];
642 optimize_expr(&mut b.lhs, state, false);
643 optimize_expr(&mut b.rhs, state, false);
644 }
645 return;
646 }
647 }
648
649 state.set_dirty();
651
652 match def_case {
653 Some(index) => {
654 let mut def_stmt = Stmt::Expr(expressions[*index].rhs.take().into());
655 optimize_stmt(&mut def_stmt, state, true);
656 *stmt = def_stmt;
657 }
658 _ => *stmt = Stmt::Block(StmtBlock::empty(*pos).into()),
659 }
660 }
661 Stmt::Switch(x, ..) => {
663 let (
664 match_expr,
665 SwitchCasesCollection {
666 expressions,
667 cases,
668 ranges,
669 def_case,
670 ..
671 },
672 ) = &mut **x;
673
674 optimize_expr(match_expr, state, false);
675
676 for b in &mut *expressions {
678 optimize_expr(&mut b.lhs, state, false);
679 optimize_expr(&mut b.rhs, state, false);
680
681 if matches!(b.lhs, Expr::BoolConstant(false, ..)) && !b.rhs.is_unit() {
682 b.rhs = Expr::Unit(b.rhs.position());
683 state.set_dirty();
684 }
685 }
686
687 cases.retain(|_, list| {
689 list.retain(|index| {
691 if matches!(expressions[*index].lhs, Expr::BoolConstant(false, ..)) {
692 state.set_dirty();
693 false
694 } else {
695 true
696 }
697 });
698 if let Some(n) = list.iter().position(|&index| {
700 matches!(expressions[index].lhs, Expr::BoolConstant(true, ..))
701 }) {
702 if n + 1 < list.len() {
703 state.set_dirty();
704 list.truncate(n + 1);
705 }
706 }
707 if list.is_empty() {
709 state.set_dirty();
710 false
711 } else {
712 true
713 }
714 });
715
716 ranges.retain(|r| {
718 if matches!(expressions[r.index()].lhs, Expr::BoolConstant(false, ..)) {
719 state.set_dirty();
720 false
721 } else {
722 true
723 }
724 });
725
726 if let Some(index) = def_case {
727 optimize_expr(&mut expressions[*index].rhs, state, false);
728 }
729
730 expressions.iter_mut().enumerate().for_each(|(index, b)| {
732 if *def_case != Some(index)
733 && cases.values().flat_map(|c| c.iter()).all(|&n| n != index)
734 && ranges.iter().all(|r| r.index() != index)
735 && !b.rhs.is_unit()
736 {
737 b.rhs = Expr::Unit(b.rhs.position());
738 state.set_dirty();
739 }
740 });
741 }
742
743 Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr {
745 Expr::BoolConstant(false, pos) => {
746 state.set_dirty();
747 *stmt = Stmt::Noop(pos);
748 }
749 _ => unreachable!("`Expr::BoolConstant"),
750 },
751 Stmt::While(x, ..) => {
753 let FlowControl { expr, body, .. } = &mut **x;
754 optimize_expr(expr, state, false);
755 if let Expr::BoolConstant(true, pos) = expr {
756 *expr = Expr::Unit(*pos);
757 }
758 *body.statements_mut() =
759 optimize_stmt_block(body.take_statements(), state, false, true, false);
760 }
761 Stmt::Do(x, ..) => {
763 optimize_expr(&mut x.expr, state, false);
764 *x.body.statements_mut() =
765 optimize_stmt_block(x.body.take_statements(), state, false, true, false);
766 }
767 Stmt::For(x, ..) => {
769 optimize_expr(&mut x.2.expr, state, false);
770 *x.2.body.statements_mut() =
771 optimize_stmt_block(x.2.body.take_statements(), state, false, true, false);
772 }
773 Stmt::Var(x, options, ..) if !options.intersects(ASTFlags::CONSTANT) => {
775 optimize_expr(&mut x.1, state, false);
776 }
777 #[cfg(not(feature = "no_module"))]
779 Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
780 Stmt::Block(block) => {
782 let mut stmts =
783 optimize_stmt_block(block.take_statements(), state, preserve_result, true, false);
784
785 match stmts.as_mut_slice() {
786 [] => {
787 state.set_dirty();
788 *stmt = Stmt::Noop(block.span().start());
789 }
790 [s] if !s.is_block_dependent() => {
792 state.set_dirty();
793 *stmt = s.take();
794 }
795 _ => *block.statements_mut() = stmts,
796 }
797 }
798 Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
800 state.set_dirty();
802 *x.body.statements_mut() =
803 optimize_stmt_block(x.body.take_statements(), state, false, true, false);
804 }
805 Stmt::TryCatch(x, ..) => {
807 *x.body.statements_mut() =
808 optimize_stmt_block(x.body.take_statements(), state, false, true, false);
809 *x.branch.statements_mut() =
810 optimize_stmt_block(x.branch.take_statements(), state, false, true, false);
811 }
812
813 Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
815 state.set_dirty();
816 match expr.as_mut() {
817 Expr::Stmt(block) if !block.is_empty() => {
818 let mut stmts_blk = mem::take(block.as_mut());
819 *stmts_blk.statements_mut() =
820 optimize_stmt_block(stmts_blk.take_statements(), state, true, true, false);
821 *stmt = Stmt::Block(stmts_blk.into());
822 }
823 Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
824 _ => unreachable!("`Expr::Stmt`"),
825 }
826 }
827
828 Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
830 state.set_dirty();
831 match expr.take() {
832 Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
833 _ => unreachable!(),
834 }
835 }
836
837 Stmt::Expr(expr) => optimize_expr(expr, state, false),
838
839 Stmt::FnCall(..) => match stmt.take() {
841 Stmt::FnCall(x, pos) => {
842 let mut expr = Expr::FnCall(x, pos);
843 optimize_expr(&mut expr, state, false);
844 *stmt = match expr {
845 Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
846 _ => Stmt::Expr(expr.into()),
847 }
848 }
849 _ => unreachable!(),
850 },
851
852 Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
854
855 Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
857
858 #[cfg(not(feature = "no_closure"))]
860 Stmt::Share(x) if x.is_empty() => {
861 state.set_dirty();
862 *stmt = Stmt::Noop(Position::NONE);
863 }
864 #[cfg(not(feature = "no_closure"))]
866 Stmt::Share(x) => {
867 let orig_len = x.len();
868
869 if state.propagate_constants {
870 x.retain(|(v, _)| state.find_literal_constant(v.as_str()).is_none());
871
872 if x.len() != orig_len {
873 state.set_dirty();
874 }
875 }
876 }
877
878 _ => (),
880 }
881}
882
883fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
885 const DONT_EVAL_KEYWORDS: &[&str] = &[
887 KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL, ];
891
892 match expr {
893 Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
895 Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => {
896 state.set_dirty();
897 match x.take_statements().remove(0) {
898 Stmt::Expr(mut e) => {
899 optimize_expr(&mut e, state, false);
900 *expr = *e;
901 }
902 _ => unreachable!("`Expr::Stmt`")
903 }
904 }
905 Expr::Stmt(x) => {
907 *x.statements_mut() = optimize_stmt_block(x.take_statements(), state, true, true, false);
908
909 if let [ Stmt::Expr(e) ] = x.statements_mut().as_mut() { state.set_dirty(); *expr = e.take(); }
911 }
912 #[cfg(not(feature = "no_object"))]
914 Expr::Dot(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
915 state.set_dirty();
916 *expr = x.lhs.take();
917 }
918 #[cfg(not(feature = "no_object"))]
920 Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
921 (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
923 state.set_dirty();
926 *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == p.2)
927 .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
928 }
929 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
931 (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == KEYWORD_TYPE_OF && x.args.is_empty() => {
933 if let Some(value) = lhs.get_literal_value() {
934 state.set_dirty();
935 let typ = state.engine.map_type_name(value.type_name()).into();
936 *expr = Expr::from_dynamic(typ, *pos);
937 }
938 }
939 #[cfg(not(feature = "no_closure"))]
941 (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == crate::engine::KEYWORD_IS_SHARED && x.args.is_empty() => {
942 if lhs.get_literal_value().is_some() {
943 state.set_dirty();
944 *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
945 }
946 }
947 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
949 }
950 #[cfg(not(feature = "no_object"))]
952 Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
953
954 #[cfg(not(feature = "no_index"))]
956 Expr::Index(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
957 state.set_dirty();
958 *expr = x.lhs.take();
959 }
960 #[cfg(not(feature = "no_index"))]
962 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
963 Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
964 (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => {
966 state.set_dirty();
969 let mut result = a[*i as usize].take();
970 result.set_position(*pos);
971 *expr = result;
972 }
973 #[allow(clippy::unnecessary_cast)]
975 (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= a.len() as u64 && a.iter().all(Expr::is_pure) => {
976 state.set_dirty();
979 let index = a.len() - i.unsigned_abs() as usize;
980 let mut result = a[index].take();
981 result.set_position(*pos);
982 *expr = result;
983 }
984 (Expr::Map(m, pos), Expr::StringConstant(s, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
986 state.set_dirty();
989 *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == s)
990 .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
991 }
992 (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < crate::INT_BITS => {
994 state.set_dirty();
996 *expr = Expr::BoolConstant((*n & (1 << (*i as usize))) != 0, *pos);
997 }
998 #[allow(clippy::unnecessary_cast)]
1000 (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= crate::INT_BITS as u64 => {
1001 state.set_dirty();
1003 *expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - i.unsigned_abs() as usize))) != 0, *pos);
1004 }
1005 #[allow(clippy::unnecessary_cast)]
1007 (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < s.chars().count() => {
1008 state.set_dirty();
1010 *expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
1011 }
1012 #[allow(clippy::unnecessary_cast)]
1014 (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && i.unsigned_abs() as u64 <= s.chars().count() as u64 => {
1015 state.set_dirty();
1017 *expr = Expr::CharConstant(s.chars().rev().nth(i.unsigned_abs() as usize - 1).unwrap(), *pos);
1018 }
1019 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
1021 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1023 },
1024 #[cfg(not(feature = "no_index"))]
1026 Expr::Index(x, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
1027 Expr::InterpolatedString(x, pos) if x.is_empty() => {
1029 state.set_dirty();
1030 *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
1031 }
1032 Expr::InterpolatedString(..) if expr.is_constant() => {
1034 state.set_dirty();
1035 *expr = Expr::StringConstant(expr.get_literal_value().unwrap().cast::<ImmutableString>(), expr.position());
1036 }
1037 Expr::InterpolatedString(x, ..) => {
1039 x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
1040
1041 let mut n = 0;
1042
1043 while n < x.len() - 1 {
1045 match (x[n].take(),x[n+1].take()) {
1046 (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); }
1047 (expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1048 (Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1049 (expr1, Expr::StringConstant(s, ..)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1050 (Expr::StringConstant(s, ..), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1051 (expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; }
1052 }
1053 }
1054
1055 x.shrink_to_fit();
1056 }
1057 #[cfg(not(feature = "no_index"))]
1059 Expr::Array(..) if expr.is_constant() => {
1060 state.set_dirty();
1061 *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
1062 }
1063 #[cfg(not(feature = "no_index"))]
1065 Expr::Array(x, ..) => x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)),
1066 #[cfg(not(feature = "no_object"))]
1068 Expr::Map(..) if expr.is_constant() => {
1069 state.set_dirty();
1070 *expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
1071 }
1072 #[cfg(not(feature = "no_object"))]
1074 Expr::Map(x, ..) => x.0.iter_mut().for_each(|(.., expr)| optimize_expr(expr, state, false)),
1075 Expr::And(x, ..) => match (&mut x.lhs, &mut x.rhs) {
1077 (Expr::BoolConstant(true, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); }
1079 (Expr::BoolConstant(false, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); }
1081 (lhs, Expr::BoolConstant(true, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); }
1083 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
1085 },
1086 Expr::Or(ref mut x, ..) => match (&mut x.lhs, &mut x.rhs) {
1088 (Expr::BoolConstant(false, ..), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = rhs.take(); }
1090 (Expr::BoolConstant(true, pos), ..) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); }
1092 (lhs, Expr::BoolConstant(false, ..)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = lhs.take(); }
1094 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
1096 },
1097 Expr::Coalesce(x, ..) if matches!(x.lhs, Expr::Unit(..)) => {
1099 state.set_dirty();
1100 *expr = x.rhs.take();
1101 },
1102 Expr::Coalesce(x, ..) if x.lhs.is_constant() => {
1104 state.set_dirty();
1105 *expr = x.lhs.take();
1106 },
1107
1108 Expr::FnCall(x,..)
1110 if x.name == OP_NOT
1111 && x.args.len() == 1
1112 && matches!(x.args[0], Expr::BoolConstant(..))
1113 => {
1114 state.set_dirty();
1115 match x.args[0] {
1116 Expr::BoolConstant(b, pos) => *expr = Expr::BoolConstant(!b, pos),
1117 _ => unreachable!(),
1118 }
1119 }
1120
1121 Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
1123 state.propagate_constants = false;
1124 }
1125 Expr::FnCall(x, pos)
1127 if !x.is_qualified() && x.args.len() == 1
1129 && x.name == KEYWORD_FN_PTR
1130 && x.constant_args()
1131 => {
1132 let fn_name = match x.args[0] {
1133 Expr::StringConstant(ref s, ..) => s.clone().into(),
1134 _ => Dynamic::UNIT
1135 };
1136
1137 if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(Into::into).and_then(FnPtr::try_from) {
1138 state.set_dirty();
1139 *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1140 } else {
1141 optimize_expr(&mut x.args[0], state, false);
1142 }
1143 }
1144 Expr::FnCall(x, pos)
1146 if !x.is_qualified() && x.args.len() >= 2
1148 && x.name == KEYWORD_FN_PTR_CURRY
1149 && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr())
1150 && x.constant_args()
1151 => {
1152 let mut fn_ptr = x.args[0].get_literal_value().unwrap().cast::<FnPtr>();
1153 fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value().unwrap()));
1154 state.set_dirty();
1155 *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1156 }
1157
1158 Expr::FnCall(x, ..) if DONT_EVAL_KEYWORDS.contains(&x.name.as_str()) => {
1160 x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
1161 }
1162
1163 Expr::FnCall(x, pos)
1165 if !x.is_qualified() && state.optimization_level == OptimizationLevel::Simple && x.constant_args() => {
1169 let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::<FnArgsVec<_>>();
1170 let arg_types = arg_values.iter().map(Dynamic::type_id).collect::<FnArgsVec<_>>();
1171
1172 match x.name.as_str() {
1173 KEYWORD_TYPE_OF if arg_values.len() == 1 => {
1174 state.set_dirty();
1175 let typ = state.engine.map_type_name(arg_values[0].type_name()).into();
1176 *expr = Expr::from_dynamic(typ, *pos);
1177 return;
1178 }
1179 #[cfg(not(feature = "no_closure"))]
1180 crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => {
1181 state.set_dirty();
1182 *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
1183 return;
1184 }
1185 _ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
1187 if let Some((f, ctx)) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) {
1188 let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into());
1189 let (first, second) = arg_values.split_first_mut().unwrap();
1190
1191 if let Ok(result) = f(context, &mut [ first, &mut second[0] ]) {
1192 state.set_dirty();
1193 *expr = Expr::from_dynamic(result, *pos);
1194 return;
1195 }
1196 }
1197 }
1198 _ => ()
1199 }
1200
1201 x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
1202
1203 x.args.iter_mut().for_each(|arg_expr| match arg_expr {
1205 Expr::DynamicConstant(..) | Expr::Unit(..)
1206 | Expr::StringConstant(..) | Expr::CharConstant(..)
1207 | Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (),
1208
1209 #[cfg(not(feature = "no_float"))]
1210 Expr:: FloatConstant(..) => (),
1211
1212 _ => if let Some(value) = arg_expr.get_literal_value() {
1213 state.set_dirty();
1214 *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
1215 },
1216 });
1217 }
1218
1219 Expr::FnCall(x, pos)
1221 if !x.is_qualified() && state.optimization_level == OptimizationLevel::Full && x.constant_args() => {
1225 let _has_script_fn = false;
1227 #[cfg(not(feature = "no_function"))]
1228 let _has_script_fn = !x.hashes.is_native_only() && state.global.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some();
1229
1230 if !_has_script_fn {
1231 let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::<Option<FnArgsVec<_>>>().unwrap();
1232
1233 let result = match x.name.as_str() {
1234 KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
1235 #[cfg(not(feature = "no_closure"))]
1236 crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
1237 _ => state.call_fn_with_const_args(&x.name, x.op_token.as_ref(), arg_values)
1238 };
1239
1240 if let Some(r) = result {
1241 state.set_dirty();
1242 *expr = Expr::from_dynamic(r, *pos);
1243 return;
1244 }
1245 }
1246
1247 x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
1248 }
1249
1250 Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg_expr| {
1252 optimize_expr(arg_expr, state, false);
1253
1254 match arg_expr {
1256 Expr::DynamicConstant(..) | Expr::Unit(..)
1257 | Expr::StringConstant(..) | Expr::CharConstant(..)
1258 | Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (),
1259
1260 #[cfg(not(feature = "no_float"))]
1261 Expr:: FloatConstant(..) => (),
1262
1263 _ => if let Some(value) = arg_expr.get_literal_value() {
1264 state.set_dirty();
1265 *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
1266 },
1267 }
1268 }),
1269
1270 #[cfg(not(feature = "no_module"))]
1272 Expr::Variable(x, ..) if !x.1.is_empty() => (),
1273 Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.3).is_some() => {
1274 *expr = Expr::from_dynamic(state.find_literal_constant(&x.3).unwrap().clone(), *pos);
1276 state.set_dirty();
1277 }
1278
1279 #[cfg(not(feature = "no_custom_syntax"))]
1281 Expr::Custom(x, ..) => {
1282 if x.scope_may_be_changed {
1283 state.propagate_constants = false;
1284 }
1285 }
1287
1288 _ => (),
1290 }
1291}
1292
1293impl Engine {
1294 fn has_native_fn_override(&self, hash_script: u64, arg_types: impl AsRef<[TypeId]>) -> bool {
1296 let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied());
1297
1298 if self
1301 .global_modules
1302 .iter()
1303 .filter(|m| !m.flags.intersects(ModuleFlags::STANDARD_LIB))
1304 .any(|m| m.contains_fn(hash))
1305 {
1306 return true;
1307 }
1308
1309 #[cfg(not(feature = "no_module"))]
1311 if self
1312 .global_sub_modules
1313 .values()
1314 .any(|m| m.contains_qualified_fn(hash))
1315 {
1316 return true;
1317 }
1318
1319 false
1320 }
1321
1322 fn optimize_top_level(
1326 &self,
1327 statements: StmtBlockContainer,
1328 scope: Option<&Scope>,
1329 lib: &[crate::SharedModule],
1330 optimization_level: OptimizationLevel,
1331 ) -> StmtBlockContainer {
1332 let mut statements = statements;
1333
1334 if optimization_level == OptimizationLevel::None {
1336 statements.shrink_to_fit();
1337 return statements;
1338 }
1339
1340 let mut state = OptimizerState::new(self, lib, scope, optimization_level);
1342
1343 self.global_modules
1345 .iter()
1346 .rev()
1347 .flat_map(|m| m.iter_var())
1348 .for_each(|(name, value)| state.push_var(name.into(), Some(Cow::Borrowed(value))));
1349
1350 state
1352 .scope
1353 .into_iter()
1354 .flat_map(Scope::iter_inner)
1355 .for_each(|(name, constant, value)| {
1356 state.push_var(
1357 name.into(),
1358 if constant {
1359 Some(Cow::Borrowed(value))
1360 } else {
1361 None
1362 },
1363 );
1364 });
1365
1366 optimize_stmt_block(statements, &mut state, true, false, true)
1367 }
1368}
1369
1370pub fn optimize_into_ast(
1372 engine: &Engine,
1373 scope: Option<&Scope>,
1374 statements: StmtBlockContainer,
1375 #[cfg(not(feature = "no_function"))] functions: Vec<crate::Shared<crate::ast::ScriptFuncDef>>,
1376 optimization_level: OptimizationLevel,
1377) -> AST {
1378 let mut statements = statements;
1379
1380 #[cfg(not(feature = "no_function"))]
1381 let lib: crate::Shared<_> = {
1382 let mut module = crate::Module::new();
1383
1384 if optimization_level == OptimizationLevel::None {
1385 for fn_def in functions {
1386 module.set_script_fn(fn_def);
1387 }
1388 } else {
1389 let mut lib2 = crate::Module::new();
1391
1392 functions
1393 .iter()
1394 .map(|fn_def| crate::ast::ScriptFuncDef {
1395 name: fn_def.name.clone(),
1396 access: fn_def.access,
1397 body: crate::ast::StmtBlock::NONE,
1398 #[cfg(not(feature = "no_object"))]
1399 this_type: fn_def.this_type.clone(),
1400 params: fn_def.params.clone(),
1401 #[cfg(feature = "metadata")]
1402 comments: <_>::default(),
1403 })
1404 .for_each(|script_def| {
1405 lib2.set_script_fn(script_def);
1406 });
1407
1408 let lib2 = &[lib2.into()];
1409
1410 for fn_def in functions {
1411 let mut fn_def = crate::func::shared_take_or_clone(fn_def);
1412
1413 *fn_def.body.statements_mut() = engine.optimize_top_level(
1415 fn_def.body.take_statements(),
1416 scope,
1417 lib2,
1418 optimization_level,
1419 );
1420
1421 module.set_script_fn(fn_def);
1422 }
1423 }
1424
1425 module.into()
1426 };
1427 #[cfg(feature = "no_function")]
1428 let lib: crate::Shared<_> = crate::Module::new().into();
1429
1430 statements.shrink_to_fit();
1431
1432 AST::new(
1433 match optimization_level {
1434 OptimizationLevel::None => statements,
1435 OptimizationLevel::Simple | OptimizationLevel::Full => {
1436 engine.optimize_top_level(statements, scope, &[lib.clone()], optimization_level)
1437 }
1438 },
1439 #[cfg(not(feature = "no_function"))]
1440 lib,
1441 )
1442}