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::tokenizer::Token;
16use crate::{
17 calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position,
18 Scope, AST,
19};
20#[cfg(feature = "no_std")]
21use std::prelude::v1::*;
22use std::{
23 any::TypeId,
24 borrow::Cow,
25 convert::TryFrom,
26 hash::{Hash, Hasher},
27 mem,
28};
29
30#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)]
34#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
35#[non_exhaustive]
36pub enum OptimizationLevel {
37 None,
39 #[default]
41 Simple,
42 Full,
45}
46
47#[derive(Debug, Clone)]
49struct OptimizerState<'a> {
50 is_dirty: bool,
52 variables: Vec<(ImmutableString, Option<Cow<'a, Dynamic>>)>,
54 propagate_constants: bool,
56 engine: &'a Engine,
58 scope: Option<&'a Scope<'a>>,
60 global: GlobalRuntimeState,
62 caches: Caches,
64 optimization_level: OptimizationLevel,
66}
67
68impl<'a> OptimizerState<'a> {
69 #[inline(always)]
71 pub fn new(
72 engine: &'a Engine,
73 lib: &'a [crate::SharedModule],
74 scope: Option<&'a Scope<'a>>,
75 optimization_level: OptimizationLevel,
76 ) -> Self {
77 let mut _global = engine.new_global_runtime_state();
78 let _lib = lib;
79
80 #[cfg(not(feature = "no_function"))]
81 {
82 _global.lib = _lib.into();
83 }
84
85 Self {
86 is_dirty: false,
87 variables: Vec::new(),
88 propagate_constants: true,
89 engine,
90 scope,
91 global: _global,
92 caches: Caches::new(),
93 optimization_level,
94 }
95 }
96 #[inline(always)]
98 pub fn set_dirty(&mut self) {
99 self.is_dirty = true;
100 }
101 #[inline(always)]
103 pub fn clear_dirty(&mut self) {
104 self.is_dirty = false;
105 }
106 #[inline(always)]
108 pub const fn is_dirty(&self) -> bool {
109 self.is_dirty
110 }
111 #[inline(always)]
113 pub fn rewind_var(&mut self, len: usize) {
114 self.variables.truncate(len);
115 }
116 #[inline(always)]
120 pub fn push_var<'x: 'a>(&mut self, name: ImmutableString, value: Option<Cow<'x, Dynamic>>) {
121 self.variables.push((name, value));
122 }
123 #[inline]
125 pub fn find_literal_constant(&self, name: &str) -> Option<&Dynamic> {
126 self.variables
127 .iter()
128 .rev()
129 .find(|(n, _)| n == name)
130 .and_then(|(_, value)| value.as_deref())
131 }
132 #[inline]
134 pub fn call_fn_with_const_args(
135 &mut self,
136 fn_name: &str,
137 op_token: Option<&Token>,
138 arg_values: &mut [Dynamic],
139 ) -> Option<Dynamic> {
140 self.engine
141 .exec_native_fn_call(
142 &mut self.global,
143 &mut self.caches,
144 fn_name,
145 op_token,
146 calc_fn_hash(None, fn_name, arg_values.len()),
147 &mut arg_values.iter_mut().collect::<FnArgsVec<_>>(),
148 false,
149 true,
150 Position::NONE,
151 )
152 .ok()
153 .map(|(v, ..)| v)
154 }
155}
156
157fn optimize_stmt_block(
159 mut statements: StmtBlockContainer,
160 state: &mut OptimizerState,
161 preserve_result: bool,
162 is_internal: bool,
163 reduce_return: bool,
164) -> StmtBlockContainer {
165 if statements.is_empty() {
166 return statements;
167 }
168
169 let mut is_dirty = state.is_dirty();
170
171 let is_pure = if is_internal {
172 Stmt::is_internally_pure
173 } else {
174 Stmt::is_pure
175 };
176
177 while let Some(n) = statements.iter().position(
179 |s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)),
180 ) {
181 let (first, second) = statements.split_at_mut(n);
182 let mut stmt = second[0].take();
183 let stmts = match stmt {
184 Stmt::Block(ref mut block, ..) => block.statements_mut(),
185 _ => unreachable!("Stmt::Block expected but gets {:?}", stmt),
186 };
187 statements = first
188 .iter_mut()
189 .map(mem::take)
190 .chain(stmts.iter_mut().map(mem::take))
191 .chain(second.iter_mut().skip(1).map(mem::take))
192 .collect();
193
194 is_dirty = true;
195 }
196
197 loop {
199 state.clear_dirty();
200
201 let orig_constants_len = state.variables.len(); let orig_propagate_constants = state.propagate_constants;
203
204 let mut dead_code = false;
206
207 statements.retain(|stmt| {
208 if dead_code {
209 state.set_dirty();
210 false
211 } else if stmt.is_control_flow_break() {
212 dead_code = true;
213 true
214 } else {
215 true
216 }
217 });
218
219 statements.iter_mut().for_each(|stmt| {
221 match stmt {
222 Stmt::Var(x, options, ..) => {
223 optimize_expr(&mut x.1, state, false);
224
225 let value = if options.intersects(ASTFlags::CONSTANT) && x.1.is_constant() {
226 Some(Cow::Owned(x.1.get_literal_value(None).unwrap()))
228 } else {
229 None
231 };
232 state.push_var(x.0.name.clone(), value);
233 }
234 _ => optimize_stmt(stmt, state, preserve_result),
236 }
237 });
238
239 let mut index = 0;
241 let mut first_non_constant = statements
242 .iter()
243 .rev()
244 .position(|stmt| match stmt {
245 stmt if !is_pure(stmt) => true,
246
247 Stmt::Var(x, ..) if x.1.is_constant() => true,
248 Stmt::Expr(e) if !e.is_constant() => true,
249
250 #[cfg(not(feature = "no_module"))]
251 Stmt::Import(x, ..) if !x.0.is_constant() => true,
252
253 _ => false,
254 })
255 .map_or(0, |n| statements.len() - n - 1);
256
257 while index < statements.len() {
258 if preserve_result && index >= statements.len() - 1 {
259 break;
260 }
261 match statements[index] {
262 ref stmt if is_pure(stmt) && index >= first_non_constant => {
263 state.set_dirty();
264 statements.remove(index);
265 }
266 ref stmt if stmt.is_pure() => {
267 state.set_dirty();
268 if index < first_non_constant {
269 first_non_constant -= 1;
270 }
271 statements.remove(index);
272 }
273 _ => index += 1,
274 }
275 }
276
277 if preserve_result {
280 loop {
281 match statements[..] {
282 [Stmt::Return(None, options, ..)]
284 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
285 {
286 state.set_dirty();
287 statements.clear();
288 }
289 [ref stmt] if !stmt.returns_value() && is_pure(stmt) => {
290 state.set_dirty();
291 statements.clear();
292 }
293 [.., ref last_stmt, Stmt::Return(None, options, ..)]
295 if reduce_return
296 && !options.intersects(ASTFlags::BREAK)
297 && !last_stmt.returns_value() =>
298 {
299 state.set_dirty();
300 statements.pop().unwrap();
301 }
302 [.., Stmt::Return(ref mut expr, options, pos)]
304 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
305 {
306 state.set_dirty();
307 *statements.last_mut().unwrap() = expr
308 .as_mut()
309 .map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e)));
310 }
311 [.., ref second_last_stmt, Stmt::Noop(..)]
313 if second_last_stmt.returns_value() =>
314 {
315 break
316 }
317 [.., ref second_last_stmt, ref last_stmt]
320 if !last_stmt.returns_value() && is_pure(last_stmt) =>
321 {
322 state.set_dirty();
323 if second_last_stmt.returns_value() {
324 *statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
325 } else {
326 statements.pop().unwrap();
327 }
328 }
329 _ => break,
330 }
331 }
332 } else {
333 loop {
334 match statements[..] {
335 [ref stmt] if is_pure(stmt) => {
336 state.set_dirty();
337 statements.clear();
338 }
339 [.., Stmt::Return(None, options, ..)]
341 if reduce_return && !options.intersects(ASTFlags::BREAK) =>
342 {
343 state.set_dirty();
344 statements.pop().unwrap();
345 }
346 [.., Stmt::Return(Some(ref expr), options, ..)]
348 if reduce_return
349 && !options.intersects(ASTFlags::BREAK)
350 && expr.is_pure() =>
351 {
352 state.set_dirty();
353 statements.pop().unwrap();
354 }
355 [.., ref last_stmt] if is_pure(last_stmt) => {
356 state.set_dirty();
357 statements.pop().unwrap();
358 }
359 _ => break,
360 }
361 }
362 }
363
364 state.rewind_var(orig_constants_len);
366 state.propagate_constants = orig_propagate_constants;
367
368 if !state.is_dirty() {
369 break;
370 }
371
372 is_dirty = true;
373 }
374
375 if is_dirty {
376 state.set_dirty();
377 }
378
379 statements.shrink_to_fit();
380 statements
381}
382
383impl StmtBlock {
384 #[inline(always)]
385 #[must_use]
386 fn take_statements(&mut self) -> StmtBlockContainer {
387 mem::take(self.statements_mut())
388 }
389}
390
391#[inline(always)]
393fn is_hashable_constant(expr: &Expr) -> bool {
394 match expr {
395 _ if !expr.is_constant() => false,
396 Expr::DynamicConstant(v, ..) => v.is_hashable(),
397 _ => false,
398 }
399}
400
401fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) {
403 #[inline(always)]
404 #[must_use]
405 fn is_variable_access(expr: &Expr, _non_qualified: bool) -> bool {
406 match expr {
407 #[cfg(not(feature = "no_module"))]
408 Expr::Variable(x, ..) if _non_qualified && !x.2.is_empty() => false,
409 Expr::Variable(..) => true,
410 _ => false,
411 }
412 }
413
414 match stmt {
415 Stmt::Assignment(x, ..)
417 if !x.0.is_op_assignment()
418 && is_variable_access(&x.1.lhs, true)
419 && matches!(&x.1.rhs, Expr::FnCall(x2, ..)
420 if Token::lookup_symbol_from_syntax(&x2.name).map_or(false, |t| t.has_op_assignment())
421 && x2.args.len() == 2
422 && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
423 ) =>
424 {
425 match x.1.rhs {
426 Expr::FnCall(ref mut x2, pos) => {
427 state.set_dirty();
428 x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, pos);
429 x.1.rhs = x2.args[1].take();
430 }
431 ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
432 }
433 }
434
435 Stmt::Assignment(x, ..) => {
437 if !is_variable_access(&x.1.lhs, false) {
438 optimize_expr(&mut x.1.lhs, state, false);
439 }
440 optimize_expr(&mut x.1.rhs, state, false);
441 }
442
443 Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => {
445 let condition = &mut x.expr;
446 state.set_dirty();
447
448 let pos = condition.start_position();
449 let mut expr = condition.take();
450 optimize_expr(&mut expr, state, false);
451
452 *stmt = if preserve_result {
453 Stmt::Block(
455 StmtBlock::new(
456 [Stmt::Expr(expr.into()), Stmt::Noop(pos)],
457 pos,
458 Position::NONE,
459 )
460 .into(),
461 )
462 } else {
463 Stmt::Expr(expr.into())
465 };
466 }
467 Stmt::If(x, ..)
469 if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() =>
470 {
471 match x.expr {
472 Expr::BoolConstant(false, pos) => {
473 state.set_dirty();
474 *stmt = Stmt::Noop(pos);
475 }
476 _ => unreachable!("`Expr::BoolConstant`"),
477 }
478 }
479 Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => {
481 state.set_dirty();
482 let body = x.branch.take_statements();
483 *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
484 statements if statements.is_empty() => Stmt::Noop(x.branch.position()),
485 statements => {
486 Stmt::Block(StmtBlock::new_with_span(statements, x.branch.span()).into())
487 }
488 }
489 }
490 Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => {
492 state.set_dirty();
493 let body = x.body.take_statements();
494 *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) {
495 statements if statements.is_empty() => Stmt::Noop(x.body.position()),
496 statements => {
497 Stmt::Block(StmtBlock::new_with_span(statements, x.body.span()).into())
498 }
499 }
500 }
501 Stmt::If(x, ..) => {
503 let FlowControl { expr, body, branch } = &mut **x;
504 optimize_expr(expr, state, false);
505 *body.statements_mut() =
506 optimize_stmt_block(body.take_statements(), state, preserve_result, true, false);
507 *branch.statements_mut() = optimize_stmt_block(
508 branch.take_statements(),
509 state,
510 preserve_result,
511 true,
512 false,
513 );
514 }
515
516 Stmt::Switch(x, pos) if is_hashable_constant(&x.0) => {
518 let (
519 match_expr,
520 SwitchCasesCollection {
521 expressions,
522 cases,
523 ranges,
524 def_case,
525 },
526 ) = &mut **x;
527
528 let value = match_expr.get_literal_value(None).unwrap();
529 let hasher = &mut get_hasher();
530 value.hash(hasher);
531 let hash = hasher.finish();
532
533 if let Some(case_blocks_list) = cases.get(&hash) {
535 match &case_blocks_list[..] {
536 [] => (),
537 [index] => {
538 let mut b = mem::take(&mut expressions[*index]);
539 cases.clear();
540
541 if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
542 let mut statements = Stmt::Expr(b.rhs.take().into());
544 optimize_stmt(&mut statements, state, true);
545 *stmt = statements;
546 } else {
547 optimize_expr(&mut b.lhs, state, false);
549
550 let branch = def_case.map_or(StmtBlock::NONE, |index| {
551 let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
552 optimize_stmt(&mut def_stmt, state, true);
553 def_stmt.into()
554 });
555 let body = Stmt::Expr(b.rhs.take().into()).into();
556 let expr = b.lhs.take();
557
558 *stmt = Stmt::If(
559 FlowControl { expr, body, branch }.into(),
560 match_expr.start_position(),
561 );
562 }
563
564 state.set_dirty();
565 return;
566 }
567 _ => {
568 for &index in case_blocks_list {
569 let mut b = mem::take(&mut expressions[index]);
570
571 if matches!(b.lhs, Expr::BoolConstant(true, ..)) {
572 let mut statements = Stmt::Expr(b.rhs.take().into());
574 optimize_stmt(&mut statements, state, true);
575 *stmt = statements;
576 state.set_dirty();
577 return;
578 }
579 }
580 }
581 }
582 }
583
584 if !ranges.is_empty() {
586 if ranges.len() == 1
588 || ranges
589 .iter()
590 .all(|r| matches!(expressions[r.index()].lhs, Expr::BoolConstant(true, ..)))
591 {
592 if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
593 let range_block = &mut expressions[r.index()];
594
595 if matches!(range_block.lhs, Expr::BoolConstant(true, ..)) {
596 let block = &mut expressions[r.index()];
598 let mut statements = Stmt::Expr(block.rhs.take().into());
599 optimize_stmt(&mut statements, state, true);
600 *stmt = statements;
601 } else {
602 let mut expr = range_block.lhs.take();
603
604 optimize_expr(&mut expr, state, false);
606
607 let branch = def_case.map_or(StmtBlock::NONE, |index| {
608 let mut def_stmt = Stmt::Expr(expressions[index].rhs.take().into());
609 optimize_stmt(&mut def_stmt, state, true);
610 def_stmt.into()
611 });
612
613 let body = Stmt::Expr(expressions[r.index()].rhs.take().into()).into();
614
615 *stmt = Stmt::If(
616 FlowControl { expr, body, branch }.into(),
617 match_expr.start_position(),
618 );
619 }
620
621 state.set_dirty();
622 return;
623 }
624 } else {
625 if !cases.is_empty() {
627 state.set_dirty();
628 cases.clear();
629 }
630
631 let old_ranges_len = ranges.len();
632
633 ranges.retain(|r| r.contains(&value));
634
635 if ranges.len() != old_ranges_len {
636 state.set_dirty();
637 }
638
639 for r in ranges {
640 let b = &mut expressions[r.index()];
641 optimize_expr(&mut b.lhs, state, false);
642 optimize_expr(&mut b.rhs, state, false);
643 }
644 return;
645 }
646 }
647
648 state.set_dirty();
650
651 match def_case {
652 Some(index) => {
653 let mut def_stmt = Stmt::Expr(expressions[*index].rhs.take().into());
654 optimize_stmt(&mut def_stmt, state, true);
655 *stmt = def_stmt;
656 }
657 _ => *stmt = Stmt::Block(StmtBlock::empty(*pos).into()),
658 }
659 }
660 Stmt::Switch(x, ..) => {
662 let (
663 match_expr,
664 SwitchCasesCollection {
665 expressions,
666 cases,
667 ranges,
668 def_case,
669 ..
670 },
671 ) = &mut **x;
672
673 optimize_expr(match_expr, state, false);
674
675 for b in &mut *expressions {
677 optimize_expr(&mut b.lhs, state, false);
678 optimize_expr(&mut b.rhs, state, false);
679
680 if matches!(b.lhs, Expr::BoolConstant(false, ..)) && !b.rhs.is_unit() {
681 b.rhs = Expr::Unit(b.rhs.position());
682 state.set_dirty();
683 }
684 }
685
686 cases.retain(|_, list| {
688 list.retain(|index| {
690 if matches!(expressions[*index].lhs, Expr::BoolConstant(false, ..)) {
691 state.set_dirty();
692 false
693 } else {
694 true
695 }
696 });
697 if let Some(n) = list.iter().position(|&index| {
699 matches!(expressions[index].lhs, Expr::BoolConstant(true, ..))
700 }) {
701 if n + 1 < list.len() {
702 state.set_dirty();
703 list.truncate(n + 1);
704 }
705 }
706 if list.is_empty() {
708 state.set_dirty();
709 false
710 } else {
711 true
712 }
713 });
714
715 ranges.retain(|r| {
717 if matches!(expressions[r.index()].lhs, Expr::BoolConstant(false, ..)) {
718 state.set_dirty();
719 false
720 } else {
721 true
722 }
723 });
724
725 if let Some(index) = def_case {
726 optimize_expr(&mut expressions[*index].rhs, state, false);
727 }
728
729 expressions.iter_mut().enumerate().for_each(|(index, b)| {
731 if *def_case != Some(index)
732 && cases.values().flat_map(|c| c.iter()).all(|&n| n != index)
733 && ranges.iter().all(|r| r.index() != index)
734 && !b.rhs.is_unit()
735 {
736 b.rhs = Expr::Unit(b.rhs.position());
737 state.set_dirty();
738 }
739 });
740 }
741
742 Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr {
744 Expr::BoolConstant(false, pos) => {
745 state.set_dirty();
746 *stmt = Stmt::Noop(pos);
747 }
748 _ => unreachable!("`Expr::BoolConstant"),
749 },
750 Stmt::While(x, ..) => {
752 let FlowControl { expr, body, .. } = &mut **x;
753 optimize_expr(expr, state, false);
754 if let Expr::BoolConstant(true, pos) = expr {
755 *expr = Expr::Unit(*pos);
756 }
757 *body.statements_mut() =
758 optimize_stmt_block(body.take_statements(), state, false, true, false);
759 }
760 Stmt::Do(x, ..) => {
762 optimize_expr(&mut x.expr, state, false);
763 *x.body.statements_mut() =
764 optimize_stmt_block(x.body.take_statements(), state, false, true, false);
765 }
766 Stmt::For(x, ..) => {
768 optimize_expr(&mut x.2.expr, state, false);
769 *x.2.body.statements_mut() =
770 optimize_stmt_block(x.2.body.take_statements(), state, false, true, false);
771 }
772 Stmt::Var(x, options, ..) if !options.intersects(ASTFlags::CONSTANT) => {
774 optimize_expr(&mut x.1, state, false);
775 }
776 #[cfg(not(feature = "no_module"))]
778 Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
779 Stmt::Block(block) => {
781 let mut stmts =
782 optimize_stmt_block(block.take_statements(), state, preserve_result, true, false);
783
784 match stmts.as_mut_slice() {
785 [] => {
786 state.set_dirty();
787 *stmt = Stmt::Noop(block.span().start());
788 }
789 [s] if !s.is_block_dependent() => {
791 state.set_dirty();
792 *stmt = s.take();
793 }
794 _ => *block.statements_mut() = stmts,
795 }
796 }
797 Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => {
799 state.set_dirty();
801 let statements = x.body.take_statements();
802 let block = StmtBlock::new_with_span(
803 optimize_stmt_block(statements, state, false, true, false),
804 x.body.span(),
805 );
806 *stmt = Stmt::Block(block.into());
807 }
808 Stmt::TryCatch(x, ..) => {
810 *x.body.statements_mut() =
811 optimize_stmt_block(x.body.take_statements(), state, false, true, false);
812 *x.branch.statements_mut() =
813 optimize_stmt_block(x.branch.take_statements(), state, false, true, false);
814 }
815
816 Stmt::Expr(expr) if matches!(**expr, Expr::Stmt(..)) => {
818 state.set_dirty();
819 match expr.as_mut() {
820 Expr::Stmt(block) if !block.is_empty() => {
821 let mut stmts_blk = mem::take(block.as_mut());
822 *stmts_blk.statements_mut() =
823 optimize_stmt_block(stmts_blk.take_statements(), state, true, true, false);
824 *stmt = Stmt::Block(stmts_blk.into());
825 }
826 Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()),
827 _ => unreachable!("`Expr::Stmt`"),
828 }
829 }
830
831 Stmt::Expr(expr) if matches!(**expr, Expr::FnCall(..)) => {
833 state.set_dirty();
834 match expr.take() {
835 Expr::FnCall(x, pos) => *stmt = Stmt::FnCall(x, pos),
836 _ => unreachable!(),
837 }
838 }
839
840 Stmt::Expr(expr) => optimize_expr(expr, state, false),
841
842 Stmt::FnCall(..) => match stmt.take() {
844 Stmt::FnCall(x, pos) => {
845 let mut expr = Expr::FnCall(x, pos);
846 optimize_expr(&mut expr, state, false);
847 *stmt = match expr {
848 Expr::FnCall(x, pos) => Stmt::FnCall(x, pos),
849 _ => Stmt::Expr(expr.into()),
850 }
851 }
852 _ => unreachable!(),
853 },
854
855 Stmt::BreakLoop(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
857
858 Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
860
861 #[cfg(not(feature = "no_closure"))]
863 Stmt::Share(x) if x.is_empty() => {
864 state.set_dirty();
865 *stmt = Stmt::Noop(Position::NONE);
866 }
867 #[cfg(not(feature = "no_closure"))]
869 Stmt::Share(x) => {
870 let orig_len = x.len();
871
872 if state.propagate_constants {
873 x.retain(|(v, _)| state.find_literal_constant(v.as_str()).is_none());
874
875 if x.len() != orig_len {
876 state.set_dirty();
877 }
878 }
879 }
880
881 _ => (),
883 }
884}
885
886fn move_constant_arg(arg_expr: &mut Expr) -> bool {
888 match arg_expr {
889 Expr::DynamicConstant(..)
890 | Expr::Unit(..)
891 | Expr::StringConstant(..)
892 | Expr::CharConstant(..)
893 | Expr::BoolConstant(..)
894 | Expr::IntegerConstant(..) => false,
895 #[cfg(not(feature = "no_float"))]
896 Expr::FloatConstant(..) => false,
897
898 _ => {
899 if let Some(value) = arg_expr.get_literal_value(None) {
900 *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position());
901 true
902 } else {
903 false
904 }
905 }
906 }
907}
908
909fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
911 const DONT_EVAL_KEYWORDS: &[&str] = &[
913 KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL, ];
917
918 let start_pos = expr.position();
919
920 match expr {
921 Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
923 Expr::Stmt(x) if x.len() == 1 && matches!(x.statements()[0], Stmt::Expr(..)) => {
924 state.set_dirty();
925 match x.take_statements().remove(0) {
926 Stmt::Expr(mut e) => {
927 optimize_expr(&mut e, state, false);
928 *expr = *e;
929 }
930 _ => unreachable!("`Expr::Stmt`")
931 }
932 }
933 Expr::Stmt(x) => {
935 *x.statements_mut() = optimize_stmt_block(x.take_statements(), state, true, true, false);
936
937 if let [ Stmt::Expr(e) ] = x.statements_mut().as_mut() { state.set_dirty(); *expr = e.take(); }
939 }
940 #[cfg(not(feature = "no_object"))]
942 Expr::Dot(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
943 state.set_dirty();
944 *expr = x.lhs.take();
945 }
946 #[cfg(not(feature = "no_object"))]
948 Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
949 (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
951 state.set_dirty();
954 *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == p.2)
955 .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
956 }
957 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
959 (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == KEYWORD_TYPE_OF && x.args.is_empty() => {
961 if let Some(value) = lhs.get_literal_value(None) {
962 state.set_dirty();
963 let typ = state.engine.map_type_name(value.type_name()).into();
964 *expr = Expr::from_dynamic(typ, *pos);
965 }
966 }
967 #[cfg(not(feature = "no_closure"))]
969 (lhs, Expr::MethodCall(x, pos)) if lhs.is_constant() && x.name == crate::engine::KEYWORD_IS_SHARED && x.args.is_empty() => {
970 if lhs.get_literal_value(None).is_some() {
971 state.set_dirty();
972 *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
973 }
974 }
975 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
977 }
978 #[cfg(not(feature = "no_object"))]
980 Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
981
982 #[cfg(not(feature = "no_index"))]
984 Expr::Index(x, options, ..) if options.intersects(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => {
985 state.set_dirty();
986 *expr = x.lhs.take();
987 }
988 #[cfg(not(feature = "no_index"))]
990 Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
991 (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => {
993 state.set_dirty();
996 let mut result = a[usize::try_from(*i).unwrap()].take();
997 result.set_position(*pos);
998 *expr = result;
999 }
1000 (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => {
1002 state.set_dirty();
1005 let index = a.len() - usize::try_from(i.unsigned_abs()).unwrap();
1006 let mut result = a[index].take();
1007 result.set_position(*pos);
1008 *expr = result;
1009 }
1010 (Expr::Map(m, pos), Expr::StringConstant(s, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => {
1012 state.set_dirty();
1015 *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == s)
1016 .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr });
1017 }
1018 (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < crate::INT_BITS).unwrap_or(false) => {
1020 state.set_dirty();
1022 *expr = Expr::BoolConstant((*n & (1 << usize::try_from(*i).unwrap())) != 0, *pos);
1023 }
1024 (Expr::IntegerConstant(n, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= crate::INT_BITS).unwrap_or(false) => {
1026 state.set_dirty();
1028 *expr = Expr::BoolConstant((*n & (1 << (crate::INT_BITS - usize::try_from(i.unsigned_abs()).unwrap()))) != 0, *pos);
1029 }
1030 (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if usize::try_from(*i).map(|x| x < s.chars().count()).unwrap_or(false) => {
1032 state.set_dirty();
1034 *expr = Expr::CharConstant(s.chars().nth(usize::try_from(*i).unwrap()).unwrap(), *pos);
1035 }
1036 (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, ..)) if *i < 0 && usize::try_from(i.unsigned_abs()).map(|x| x <= s.chars().count()).unwrap_or(false) => {
1038 state.set_dirty();
1040 *expr = Expr::CharConstant(s.chars().rev().nth(usize::try_from(i.unsigned_abs()).unwrap() - 1).unwrap(), *pos);
1041 }
1042 (Expr::Variable(..) | Expr::ThisPtr(..), rhs) => optimize_expr(rhs, state, true),
1044 (lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, true); }
1046 },
1047 #[cfg(not(feature = "no_index"))]
1049 Expr::Index(x, ..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
1050 Expr::InterpolatedString(x, pos) if x.is_empty() => {
1052 state.set_dirty();
1053 *expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
1054 }
1055 Expr::InterpolatedString(..) if expr.is_constant() => {
1057 state.set_dirty();
1058 *expr = Expr::StringConstant(expr.get_literal_value(None).unwrap().cast::<ImmutableString>(), expr.position());
1059 }
1060 Expr::InterpolatedString(x, ..) => {
1062 x.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
1063
1064 let mut n = 0;
1065
1066 while n < x.len() - 1 {
1068 match (x[n].take(),x[n+1].take()) {
1069 (Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, ..)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); }
1070 (expr1, Expr::Unit(..)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1071 (Expr::Unit(..), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1072 (expr1, Expr::StringConstant(s, ..)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
1073 (Expr::StringConstant(s, ..), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
1074 (expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; }
1075 }
1076 }
1077
1078 x.shrink_to_fit();
1079 }
1080 #[cfg(not(feature = "no_index"))]
1082 Expr::Array(..) if expr.is_constant() => {
1083 state.set_dirty();
1084 *expr = Expr::DynamicConstant(expr.get_literal_value(None).unwrap().into(), expr.position());
1085 }
1086 #[cfg(not(feature = "no_index"))]
1088 Expr::Array(x, ..) => x.iter_mut().for_each(|expr| optimize_expr(expr, state, false)),
1089 #[cfg(not(feature = "no_object"))]
1091 Expr::Map(..) if expr.is_constant() => {
1092 state.set_dirty();
1093 *expr = Expr::DynamicConstant(expr.get_literal_value(None).unwrap().into(), expr.position());
1094 }
1095 #[cfg(not(feature = "no_object"))]
1097 Expr::Map(x, ..) => x.0.iter_mut().for_each(|(.., expr)| optimize_expr(expr, state, false)),
1098 Expr::And(x, ..) => {
1100 let mut is_false = None;
1101 let mut n = 0 ;
1102
1103 while n < x.len() {
1104 match x[n] {
1105 Expr::BoolConstant(true, ..) => { state.set_dirty(); x.remove(n); }
1107 Expr::BoolConstant(false, pos) => {
1109 if x.iter().take(n).all(Expr::is_pure) {
1110 is_false = Some(pos);
1111 state.set_dirty();
1112 }
1113 if x.len() > n + 1 {
1114 x.truncate(n + 1);
1115 state.set_dirty();
1116 }
1117 break;
1118 }
1119 _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1120 }
1121 }
1122
1123 if let Some(pos) = is_false {
1124 state.set_dirty();
1125 *expr = Expr::BoolConstant(false, pos);
1126 } else if x.is_empty() {
1127 state.set_dirty();
1128 *expr = Expr::BoolConstant(true, start_pos);
1129 } else if x.len() == 1 {
1130 state.set_dirty();
1131 *expr = x[0].take();
1132 }
1133 },
1134 Expr::Or(ref mut x, ..) => {
1136 let mut is_true = None;
1137 let mut n = 0 ;
1138
1139 while n < x.len() {
1140 match x[n] {
1141 Expr::BoolConstant(false, ..) => { state.set_dirty(); x.remove(n); }
1143 Expr::BoolConstant(true, pos) => {
1145 if x.iter().take(n).all(Expr::is_pure) {
1146 is_true = Some(pos);
1147 state.set_dirty();
1148 }
1149 if x.len() > n + 1 {
1150 x.truncate(n + 1);
1151 state.set_dirty();
1152 }
1153 break;
1154 }
1155 _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1156 }
1157 }
1158
1159 if let Some(pos) = is_true {
1160 state.set_dirty();
1161 *expr = Expr::BoolConstant(true, pos);
1162 } else if x.is_empty() {
1163 state.set_dirty();
1164 *expr = Expr::BoolConstant(false, start_pos);
1165 } else if x.len() == 1 {
1166 state.set_dirty();
1167 *expr = x[0].take();
1168 }
1169 },
1170 Expr::Coalesce(x, ..) => {
1172 let mut n = 0 ;
1173
1174 while n < x.len() {
1175 match &x[n] {
1176 Expr::Unit(..) => { state.set_dirty(); x.remove(n); }
1178 e if e.is_constant() => {
1179 if x.len() > n + 1 {
1180 x.truncate(n + 1);
1181 state.set_dirty();
1182 }
1183 break;
1184 }
1185 _ => { optimize_expr(&mut x[n], state, false); n += 1; }
1186 }
1187 }
1188
1189 if x.is_empty() {
1190 state.set_dirty();
1191 *expr = Expr::BoolConstant(false, start_pos);
1192 } else if x.len() == 1 {
1193 state.set_dirty();
1194 *expr = x[0].take();
1195 }
1196 },
1197
1198 Expr::FnCall(x,..)
1200 if x.name == OP_NOT
1201 && x.args.len() == 1
1202 && matches!(x.args[0], Expr::BoolConstant(..))
1203 => {
1204 state.set_dirty();
1205 match x.args[0] {
1206 Expr::BoolConstant(b, pos) => *expr = Expr::BoolConstant(!b, pos),
1207 _ => unreachable!(),
1208 }
1209 }
1210
1211 #[cfg(not(feature = "no_module"))]
1213 Expr::FnCall(x, ..) if x.is_qualified() => x.args.iter_mut().for_each(|arg_expr| {
1214 optimize_expr(arg_expr, state, false);
1215
1216 if move_constant_arg(arg_expr) {
1217 state.set_dirty();
1218 }
1219 }),
1220 Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => {
1222 state.propagate_constants = false;
1223 }
1224 Expr::FnCall(x, pos) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR && x.constant_args() => {
1226 let fn_name = match x.args[0] {
1227 Expr::StringConstant(ref s, ..) => s.clone().into(),
1228 _ => Dynamic::UNIT
1229 };
1230
1231 if let Ok(fn_ptr) = fn_name.into_immutable_string().map_err(Into::into).and_then(FnPtr::try_from) {
1232 state.set_dirty();
1233 *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1234 } else {
1235 optimize_expr(&mut x.args[0], state, false);
1236 }
1237 }
1238 Expr::FnCall(x, pos) if x.args.len() >= 2
1240 && x.name == KEYWORD_FN_PTR_CURRY
1241 && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr())
1242 && x.constant_args()
1243 => {
1244 let mut fn_ptr = x.args[0].get_literal_value(None).unwrap().cast::<FnPtr>();
1245 fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value(None).unwrap()));
1246 state.set_dirty();
1247 *expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
1248 }
1249
1250 Expr::FnCall(x, ..) if DONT_EVAL_KEYWORDS.contains(&x.name.as_str()) => {
1252 x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false));
1253 }
1254
1255 Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Simple && x.constant_args() => {
1259 let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value(None).unwrap()).collect::<FnArgsVec<_>>();
1260 let arg_types = arg_values.iter().map(Dynamic::type_id).collect::<FnArgsVec<_>>();
1261
1262 match x.name.as_str() {
1263 KEYWORD_TYPE_OF if arg_values.len() == 1 => {
1264 state.set_dirty();
1265 let typ = state.engine.map_type_name(arg_values[0].type_name()).into();
1266 *expr = Expr::from_dynamic(typ, *pos);
1267 return;
1268 }
1269 #[cfg(not(feature = "no_closure"))]
1270 crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => {
1271 state.set_dirty();
1272 *expr = Expr::from_dynamic(Dynamic::FALSE, *pos);
1273 return;
1274 }
1275 _ if x.args.len() == 2 && x.is_operator_call() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
1277 if let Some((f, ctx)) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) {
1278 let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into());
1279 let (first, second) = arg_values.split_first_mut().unwrap();
1280
1281 if let Ok(result) = f(context, &mut [ first, &mut second[0] ]) {
1282 state.set_dirty();
1283 *expr = Expr::from_dynamic(result, *pos);
1284 return;
1285 }
1286 }
1287 }
1288 _ => ()
1289 }
1290
1291 x.args.iter_mut().for_each(|arg_expr| {
1292 optimize_expr(arg_expr, state, false);
1293 if move_constant_arg(arg_expr) {
1294 state.set_dirty();
1295 }
1296 });
1297 }
1298
1299 Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Full && x.constant_args() => {
1303 let _has_script_fn = false;
1305 #[cfg(not(feature = "no_function"))]
1306 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();
1307
1308 if !_has_script_fn {
1309 let arg_values = &mut x.args.iter().map(|a| a.get_literal_value(None)).collect::<Option<FnArgsVec<_>>>().unwrap();
1310
1311 let result = match x.name.as_str() {
1312 KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
1313 #[cfg(not(feature = "no_closure"))]
1314 crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
1315 _ => state.call_fn_with_const_args(&x.name, x.op_token.as_ref(), arg_values)
1316 };
1317
1318 if let Some(r) = result {
1319 state.set_dirty();
1320 *expr = Expr::from_dynamic(r, *pos);
1321 return;
1322 }
1323 }
1324
1325 x.args.iter_mut().for_each(|a| optimize_expr(a, state, false));
1326 }
1327
1328 Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => x.args.iter_mut().for_each(|arg_expr| {
1330 optimize_expr(arg_expr, state, false);
1331 if move_constant_arg(arg_expr) {
1332 state.set_dirty();
1333 }
1334 }),
1335
1336 #[cfg(not(feature = "no_module"))]
1338 Expr::Variable(x, ..) if !x.2.is_empty() => (),
1339 Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.1).is_some() => {
1340 *expr = Expr::from_dynamic(state.find_literal_constant(&x.1).unwrap().clone(), *pos);
1342 state.set_dirty();
1343 }
1344
1345 #[cfg(not(feature = "no_custom_syntax"))]
1347 Expr::Custom(x, ..) => {
1348 if x.scope_may_be_changed {
1349 state.propagate_constants = false;
1350 }
1351 }
1353
1354 _ => (),
1356 }
1357}
1358
1359impl Engine {
1360 fn has_native_fn_override(&self, hash_script: u64, arg_types: impl AsRef<[TypeId]>) -> bool {
1362 let hash = calc_fn_hash_full(hash_script, arg_types.as_ref().iter().copied());
1363
1364 if self
1367 .global_modules
1368 .iter()
1369 .filter(|m| !m.is_standard_lib())
1370 .any(|m| m.contains_fn(hash))
1371 {
1372 return true;
1373 }
1374
1375 #[cfg(not(feature = "no_module"))]
1377 if self
1378 .global_sub_modules
1379 .values()
1380 .any(|m| m.contains_qualified_fn(hash))
1381 {
1382 return true;
1383 }
1384
1385 false
1386 }
1387
1388 fn optimize_top_level(
1392 &self,
1393 statements: StmtBlockContainer,
1394 scope: Option<&Scope>,
1395 lib: &[crate::SharedModule],
1396 optimization_level: OptimizationLevel,
1397 ) -> StmtBlockContainer {
1398 let mut statements = statements;
1399
1400 if optimization_level == OptimizationLevel::None {
1402 statements.shrink_to_fit();
1403 return statements;
1404 }
1405
1406 let mut state = OptimizerState::new(self, lib, scope, optimization_level);
1408
1409 self.global_modules
1411 .iter()
1412 .rev()
1413 .flat_map(|m| m.iter_var())
1414 .for_each(|(name, value)| state.push_var(name.into(), Some(Cow::Borrowed(value))));
1415
1416 state
1418 .scope
1419 .into_iter()
1420 .flat_map(Scope::iter_inner)
1421 .for_each(|(name, constant, value)| {
1422 state.push_var(
1423 name.into(),
1424 if constant {
1425 Some(Cow::Borrowed(value))
1426 } else {
1427 None
1428 },
1429 );
1430 });
1431
1432 optimize_stmt_block(statements, &mut state, true, false, true)
1433 }
1434
1435 pub(crate) fn optimize_into_ast(
1437 &self,
1438 scope: Option<&Scope>,
1439 statements: StmtBlockContainer,
1440 #[cfg(not(feature = "no_function"))] functions: impl IntoIterator<Item = crate::Shared<crate::ast::ScriptFuncDef>>
1441 + AsRef<[crate::Shared<crate::ast::ScriptFuncDef>]>,
1442 optimization_level: OptimizationLevel,
1443 ) -> AST {
1444 let mut statements = statements;
1445
1446 #[cfg(not(feature = "no_function"))]
1447 let lib: crate::Shared<_> = if optimization_level == OptimizationLevel::None {
1448 crate::Module::from(functions).into()
1449 } else {
1450 let lib2 = crate::Module::from(
1452 functions
1453 .as_ref()
1454 .iter()
1455 .map(|fn_def| fn_def.clone_function_signatures().into()),
1456 );
1457
1458 let lib2 = &[lib2.into()];
1459
1460 crate::Module::from(functions.into_iter().map(|fn_def| {
1461 let mut fn_def = crate::func::shared_take_or_clone(fn_def);
1463 let statements = fn_def.body.take_statements();
1464 *fn_def.body.statements_mut() =
1465 self.optimize_top_level(statements, scope, lib2, optimization_level);
1466 fn_def.into()
1467 }))
1468 .into()
1469 };
1470 #[cfg(feature = "no_function")]
1471 let lib: crate::Shared<_> = crate::Module::new().into();
1472
1473 statements.shrink_to_fit();
1474
1475 AST::new(
1476 match optimization_level {
1477 OptimizationLevel::None => statements,
1478 OptimizationLevel::Simple | OptimizationLevel::Full => self.optimize_top_level(
1479 statements,
1480 scope,
1481 std::slice::from_ref(&lib),
1482 optimization_level,
1483 ),
1484 },
1485 #[cfg(not(feature = "no_function"))]
1486 lib,
1487 )
1488 }
1489}