1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4 BinaryOp, BuiltinValue, HirBlock, HirExpr, HirExprKind, HirFunction, HirIfStmt, HirModule,
5 HirStmt, HirSwitchStmt, LangSpec, Literal, NcsInstruction, NcsOpcode, OptimizationLevel,
6 SemanticType, UnaryOp,
7};
8
9pub(crate) fn optimize_hir(
10 hir: &HirModule,
11 langspec: Option<&LangSpec>,
12 optimization: OptimizationLevel,
13) -> HirModule {
14 let mut optimized = hir.clone();
15
16 if enables_dead_branches(optimization) {
17 optimized = trim_dead_branches(&optimized, langspec);
18 }
19
20 if enables_dead_functions(optimization) {
21 optimized = eliminate_dead_functions(&optimized);
22 }
23
24 optimized
25}
26
27pub(crate) fn meld_instructions(instructions: Vec<NcsInstruction>) -> Vec<NcsInstruction> {
28 let mut optimized: Vec<NcsInstruction> = Vec::with_capacity(instructions.len());
29
30 for instruction in instructions {
31 if instruction.opcode == NcsOpcode::ModifyStackPointer
32 && let [.., runstack_add, constant, assignment] = optimized.as_slice()
33 && runstack_add.opcode == NcsOpcode::RunstackAdd
34 && constant.opcode == NcsOpcode::Constant
35 && assignment.opcode == NcsOpcode::Assignment
36 && runstack_add.auxcode == constant.auxcode
37 && assignment_stack_offset(assignment) == Some(-8)
38 {
39 let constant = constant.clone();
40 optimized.pop();
41 optimized.pop();
42 optimized.pop();
43 optimized.push(constant);
44 continue;
45 }
46
47 optimized.push(instruction);
48 }
49
50 optimized
51}
52
53fn enables_dead_functions(optimization: OptimizationLevel) -> bool {
54 #[allow(non_exhaustive_omitted_patterns)] match optimization {
OptimizationLevel::O1 | OptimizationLevel::O2 | OptimizationLevel::O3 =>
true,
_ => false,
}matches!(
55 optimization,
56 OptimizationLevel::O1 | OptimizationLevel::O2 | OptimizationLevel::O3
57 )
58}
59
60fn enables_dead_branches(optimization: OptimizationLevel) -> bool {
61 #[allow(non_exhaustive_omitted_patterns)] match optimization {
OptimizationLevel::O2 | OptimizationLevel::O3 => true,
_ => false,
}matches!(optimization, OptimizationLevel::O2 | OptimizationLevel::O3)
62}
63
64fn enables_instruction_melding(optimization: OptimizationLevel) -> bool {
65 optimization == OptimizationLevel::O3
66}
67
68pub(crate) fn optimization_needs_post_codegen_passes(optimization: OptimizationLevel) -> bool {
69 enables_instruction_melding(optimization)
70}
71
72pub(crate) fn optimization_needs_hir_passes(optimization: OptimizationLevel) -> bool {
73 enables_dead_functions(optimization) || enables_dead_branches(optimization)
74}
75
76#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ConstValue {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
ConstValue::Int(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Int",
&__self_0),
ConstValue::Float(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Float",
&__self_0),
ConstValue::String(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "String",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ConstValue {
#[inline]
fn clone(&self) -> ConstValue {
match self {
ConstValue::Int(__self_0) =>
ConstValue::Int(::core::clone::Clone::clone(__self_0)),
ConstValue::Float(__self_0) =>
ConstValue::Float(::core::clone::Clone::clone(__self_0)),
ConstValue::String(__self_0) =>
ConstValue::String(::core::clone::Clone::clone(__self_0)),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for ConstValue {
#[inline]
fn eq(&self, other: &ConstValue) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(ConstValue::Int(__self_0), ConstValue::Int(__arg1_0)) =>
__self_0 == __arg1_0,
(ConstValue::Float(__self_0), ConstValue::Float(__arg1_0)) =>
__self_0 == __arg1_0,
(ConstValue::String(__self_0), ConstValue::String(__arg1_0))
=> __self_0 == __arg1_0,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq)]
77pub(crate) enum ConstValue {
78 Int(i32),
79 Float(f32),
80 String(String),
81}
82
83fn trim_dead_branches(hir: &HirModule, langspec: Option<&LangSpec>) -> HirModule {
84 let constants = build_constant_env(hir, langspec);
85 let mut optimized = hir.clone();
86 for function in &mut optimized.functions {
87 if let Some(body) = function.body.take() {
88 function.body = Some(optimize_block(body, &constants));
89 }
90 }
91 optimized
92}
93
94fn optimize_block(block: HirBlock, constants: &BTreeMap<String, ConstValue>) -> HirBlock {
95 let mut statements = Vec::with_capacity(block.statements.len());
96 for statement in block.statements {
97 if let Some(statement) = optimize_stmt(statement, constants) {
98 statements.push(statement);
99 }
100 }
101 HirBlock {
102 span: block.span,
103 statements,
104 }
105}
106
107fn optimize_stmt(statement: HirStmt, constants: &BTreeMap<String, ConstValue>) -> Option<HirStmt> {
108 match statement {
109 HirStmt::Block(block) => Some(HirStmt::Block(Box::new(optimize_block(*block, constants)))),
110 HirStmt::If(boxed) => {
111 let HirIfStmt {
112 span,
113 condition,
114 then_branch,
115 else_branch,
116 } = *boxed;
117 if let Some(ConstValue::Int(value)) = evaluate_const_expr(&condition, constants) {
118 if value != 0 {
119 return optimize_stmt(*then_branch, constants);
120 }
121 return else_branch.and_then(|branch| optimize_stmt(*branch, constants));
122 }
123
124 Some(HirStmt::If(Box::new(HirIfStmt {
125 span,
126 condition,
127 then_branch: Box::new(
128 optimize_stmt(*then_branch, constants).unwrap_or(HirStmt::Empty(span)),
129 ),
130 else_branch: else_branch
131 .and_then(|branch| optimize_stmt(*branch, constants))
132 .map(Box::new),
133 })))
134 }
135 HirStmt::Switch(boxed) => {
136 let HirSwitchStmt {
137 span,
138 condition,
139 body,
140 } = *boxed;
141 Some(HirStmt::Switch(Box::new(HirSwitchStmt {
142 span,
143 condition,
144 body: Box::new(optimize_stmt(*body, constants).unwrap_or(HirStmt::Empty(span))),
145 })))
146 }
147 HirStmt::While(mut statement) => {
148 statement.body = Box::new(
149 optimize_stmt(*statement.body, constants).unwrap_or(HirStmt::Empty(statement.span)),
150 );
151 Some(HirStmt::While(statement))
152 }
153 HirStmt::DoWhile(mut statement) => {
154 statement.body = Box::new(
155 optimize_stmt(*statement.body, constants).unwrap_or(HirStmt::Empty(statement.span)),
156 );
157 Some(HirStmt::DoWhile(statement))
158 }
159 HirStmt::For(mut statement) => {
160 statement.body = Box::new(
161 optimize_stmt(*statement.body, constants).unwrap_or(HirStmt::Empty(statement.span)),
162 );
163 Some(HirStmt::For(statement))
164 }
165 other => Some(other),
166 }
167}
168
169fn eliminate_dead_functions(hir: &HirModule) -> HirModule {
170 let function_map = hir
171 .functions
172 .iter()
173 .enumerate()
174 .map(|(index, function)| (function.name.clone(), (index, function)))
175 .collect::<BTreeMap<_, _>>();
176
177 let entry_name = function_map.get("main").map(|_| "main").or_else(|| {
178 function_map
179 .get("StartingConditional")
180 .map(|_| "StartingConditional")
181 });
182
183 let mut visited = BTreeSet::new();
184 let mut ordered = Vec::new();
185
186 if !hir.globals.is_empty() {
187 for global in &hir.globals {
188 if let Some(initializer) = &global.initializer {
189 collect_user_calls_expr(initializer, &mut ordered, &mut visited, &function_map);
190 }
191 }
192 }
193
194 if let Some(entry_name) = entry_name {
195 visit_function(entry_name, &mut ordered, &mut visited, &function_map);
196 }
197
198 let mut functions = Vec::with_capacity(ordered.len());
199 for name in ordered {
200 if let Some((_, function)) = function_map.get(&name) {
201 functions.push((*function).clone());
202 }
203 }
204
205 HirModule {
206 includes: hir.includes.clone(),
207 structs: hir.structs.clone(),
208 globals: hir.globals.clone(),
209 functions,
210 }
211}
212
213fn visit_function(
214 name: &str,
215 ordered: &mut Vec<String>,
216 visited: &mut BTreeSet<String>,
217 function_map: &BTreeMap<String, (usize, &HirFunction)>,
218) {
219 if !visited.insert(name.to_string()) {
220 return;
221 }
222
223 let Some((_, function)) = function_map.get(name) else {
224 return;
225 };
226 ordered.push(name.to_string());
227
228 if let Some(body) = &function.body {
229 collect_user_calls_block(body, ordered, visited, function_map);
230 }
231}
232
233fn collect_user_calls_block(
234 block: &HirBlock,
235 ordered: &mut Vec<String>,
236 visited: &mut BTreeSet<String>,
237 function_map: &BTreeMap<String, (usize, &HirFunction)>,
238) {
239 for statement in &block.statements {
240 collect_user_calls_stmt(statement, ordered, visited, function_map);
241 }
242}
243
244fn collect_user_calls_stmt(
245 statement: &HirStmt,
246 ordered: &mut Vec<String>,
247 visited: &mut BTreeSet<String>,
248 function_map: &BTreeMap<String, (usize, &HirFunction)>,
249) {
250 match statement {
251 HirStmt::Block(block) => collect_user_calls_block(block, ordered, visited, function_map),
252 HirStmt::Declare(statement) => {
253 for declarator in &statement.declarators {
254 if let Some(initializer) = &declarator.initializer {
255 collect_user_calls_expr(initializer, ordered, visited, function_map);
256 }
257 }
258 }
259 HirStmt::Expr(expr) | HirStmt::Case(expr) => {
260 collect_user_calls_expr(expr, ordered, visited, function_map);
261 }
262 HirStmt::If(statement) => {
263 collect_user_calls_expr(&statement.condition, ordered, visited, function_map);
264 collect_user_calls_stmt(&statement.then_branch, ordered, visited, function_map);
265 if let Some(else_branch) = &statement.else_branch {
266 collect_user_calls_stmt(else_branch, ordered, visited, function_map);
267 }
268 }
269 HirStmt::Switch(statement) => {
270 collect_user_calls_expr(&statement.condition, ordered, visited, function_map);
271 collect_user_calls_stmt(&statement.body, ordered, visited, function_map);
272 }
273 HirStmt::Return(statement) => {
274 if let Some(value) = &statement.value {
275 collect_user_calls_expr(value, ordered, visited, function_map);
276 }
277 }
278 HirStmt::While(statement) => {
279 collect_user_calls_expr(&statement.condition, ordered, visited, function_map);
280 collect_user_calls_stmt(&statement.body, ordered, visited, function_map);
281 }
282 HirStmt::DoWhile(statement) => {
283 collect_user_calls_stmt(&statement.body, ordered, visited, function_map);
284 collect_user_calls_expr(&statement.condition, ordered, visited, function_map);
285 }
286 HirStmt::For(statement) => {
287 if let Some(initializer) = &statement.initializer {
288 collect_user_calls_expr(initializer, ordered, visited, function_map);
289 }
290 if let Some(condition) = &statement.condition {
291 collect_user_calls_expr(condition, ordered, visited, function_map);
292 }
293 collect_user_calls_stmt(&statement.body, ordered, visited, function_map);
294 if let Some(update) = &statement.update {
295 collect_user_calls_expr(update, ordered, visited, function_map);
296 }
297 }
298 HirStmt::Default(_) | HirStmt::Break(_) | HirStmt::Continue(_) | HirStmt::Empty(_) => {}
299 }
300}
301
302fn collect_user_calls_expr(
303 expr: &HirExpr,
304 ordered: &mut Vec<String>,
305 visited: &mut BTreeSet<String>,
306 function_map: &BTreeMap<String, (usize, &HirFunction)>,
307) {
308 match &expr.kind {
309 HirExprKind::Literal(_) | HirExprKind::Value(_) => {}
310 HirExprKind::Call {
311 target,
312 arguments,
313 } => {
314 for argument in arguments {
315 collect_user_calls_expr(argument, ordered, visited, function_map);
316 }
317 if let crate::HirCallTarget::Function(name) = target {
318 visit_function(name, ordered, visited, function_map);
319 }
320 }
321 HirExprKind::FieldAccess {
322 base, ..
323 } => {
324 collect_user_calls_expr(base, ordered, visited, function_map);
325 }
326 HirExprKind::Unary {
327 expr, ..
328 } => {
329 collect_user_calls_expr(expr, ordered, visited, function_map);
330 }
331 HirExprKind::Binary {
332 left,
333 right,
334 ..
335 } => {
336 collect_user_calls_expr(left, ordered, visited, function_map);
337 collect_user_calls_expr(right, ordered, visited, function_map);
338 }
339 HirExprKind::Conditional {
340 condition,
341 when_true,
342 when_false,
343 } => {
344 collect_user_calls_expr(condition, ordered, visited, function_map);
345 collect_user_calls_expr(when_true, ordered, visited, function_map);
346 collect_user_calls_expr(when_false, ordered, visited, function_map);
347 }
348 HirExprKind::Assignment {
349 op,
350 left,
351 right,
352 } => {
353 if *op != crate::AssignmentOp::Assign {
354 collect_user_calls_expr(left, ordered, visited, function_map);
355 }
356 collect_user_calls_expr(right, ordered, visited, function_map);
357 }
358 }
359}
360
361pub(crate) fn build_constant_env(
362 hir: &HirModule,
363 langspec: Option<&LangSpec>,
364) -> BTreeMap<String, ConstValue> {
365 let mut constants = BTreeMap::new();
366 if let Some(langspec) = langspec {
367 for constant in &langspec.constants {
368 if let Some(value) = const_from_builtin_value(&constant.value) {
369 constants.insert(constant.name.clone(), value);
370 }
371 }
372 }
373
374 for global in &hir.globals {
375 if !global.is_const {
376 continue;
377 }
378 let value = global
379 .initializer
380 .as_ref()
381 .and_then(|initializer| evaluate_const_expr(initializer, &constants))
382 .or_else(|| default_const_value(&global.ty));
383 if let Some(value) = value {
384 constants.insert(global.name.clone(), value);
385 }
386 }
387
388 constants
389}
390
391pub(crate) fn evaluate_const_expr(
392 expr: &HirExpr,
393 constants: &BTreeMap<String, ConstValue>,
394) -> Option<ConstValue> {
395 match &expr.kind {
396 HirExprKind::Literal(literal) => const_from_literal(literal),
397 HirExprKind::Value(
398 crate::HirValueRef::ConstGlobal(name) | crate::HirValueRef::BuiltinConstant(name),
399 ) => constants.get(name).cloned(),
400 HirExprKind::Unary {
401 op,
402 expr,
403 } => {
404 let value = evaluate_const_expr(expr, constants)?;
405 match (op, value) {
406 (UnaryOp::Negate, ConstValue::Int(value)) => Some(ConstValue::Int(-value)),
407 (UnaryOp::Negate, ConstValue::Float(value)) => Some(ConstValue::Float(-value)),
408 (UnaryOp::BooleanNot, ConstValue::Int(value)) => {
409 Some(ConstValue::Int(i32::from(value == 0)))
410 }
411 (UnaryOp::OnesComplement, ConstValue::Int(value)) => Some(ConstValue::Int(!value)),
412 _ => None,
413 }
414 }
415 HirExprKind::Binary {
416 op,
417 left,
418 right,
419 } => {
420 let left = evaluate_const_expr(left, constants)?;
421 let right = evaluate_const_expr(right, constants)?;
422 match (left, right) {
423 (ConstValue::Int(left), ConstValue::Int(right)) => {
424 evaluate_int_binary(*op, left, right).map(ConstValue::Int)
425 }
426 (ConstValue::Float(left), ConstValue::Float(right)) => {
427 evaluate_float_binary(*op, left, right)
428 }
429 (ConstValue::String(left), ConstValue::String(right)) => {
430 evaluate_string_binary(*op, &left, &right)
431 }
432 _ => None,
433 }
434 }
435 HirExprKind::Conditional {
436 condition,
437 when_true,
438 when_false,
439 } => match evaluate_const_expr(condition, constants)? {
440 ConstValue::Int(value) if value != 0 => evaluate_const_expr(when_true, constants),
441 ConstValue::Int(_) => evaluate_const_expr(when_false, constants),
442 _ => None,
443 },
444 HirExprKind::Value(_)
445 | HirExprKind::Call {
446 ..
447 }
448 | HirExprKind::FieldAccess {
449 ..
450 }
451 | HirExprKind::Assignment {
452 ..
453 } => None,
454 }
455}
456
457fn const_from_builtin_value(value: &BuiltinValue) -> Option<ConstValue> {
458 match value {
459 BuiltinValue::Int(value) | BuiltinValue::ObjectId(value) => Some(ConstValue::Int(*value)),
460 BuiltinValue::Float(value) => Some(ConstValue::Float(*value)),
461 BuiltinValue::String(value) => Some(ConstValue::String(value.clone())),
462 BuiltinValue::ObjectSelf => Some(ConstValue::Int(0)),
463 BuiltinValue::ObjectInvalid => Some(ConstValue::Int(1)),
464 BuiltinValue::LocationInvalid
465 | BuiltinValue::Json(_)
466 | BuiltinValue::Vector(_)
467 | BuiltinValue::Raw(_) => None,
468 }
469}
470
471fn const_from_literal(literal: &Literal) -> Option<ConstValue> {
472 match literal {
473 Literal::Integer(value) => Some(ConstValue::Int(*value)),
474 Literal::Float(value) => Some(ConstValue::Float(*value)),
475 Literal::String(value) => Some(ConstValue::String(value.clone())),
476 Literal::ObjectSelf => Some(ConstValue::Int(0)),
477 Literal::ObjectInvalid => Some(ConstValue::Int(1)),
478 Literal::LocationInvalid | Literal::Json(_) | Literal::Vector(_) | Literal::Magic(_) => {
479 None
480 }
481 }
482}
483
484fn default_const_value(ty: &SemanticType) -> Option<ConstValue> {
485 match ty {
486 SemanticType::Int => Some(ConstValue::Int(0)),
487 SemanticType::Float => Some(ConstValue::Float(0.0)),
488 SemanticType::String => Some(ConstValue::String(String::new())),
489 _ => None,
490 }
491}
492
493fn evaluate_int_binary(op: BinaryOp, left: i32, right: i32) -> Option<i32> {
494 match op {
495 BinaryOp::Multiply => Some(left.wrapping_mul(right)),
496 BinaryOp::Divide => (right != 0).then_some(left.wrapping_div(right)),
497 BinaryOp::Modulus => (right != 0).then_some(left.wrapping_rem(right)),
498 BinaryOp::Add => Some(left.wrapping_add(right)),
499 BinaryOp::Subtract => Some(left.wrapping_sub(right)),
500 BinaryOp::ShiftLeft => Some(left.wrapping_shl(right.cast_unsigned())),
501 BinaryOp::ShiftRight => Some(left.wrapping_shr(right.cast_unsigned())),
502 BinaryOp::UnsignedShiftRight => {
503 Some(((left.cast_unsigned()).wrapping_shr(right.cast_unsigned())).cast_signed())
504 }
505 BinaryOp::GreaterEqual => Some(i32::from(left >= right)),
506 BinaryOp::GreaterThan => Some(i32::from(left > right)),
507 BinaryOp::LessThan => Some(i32::from(left < right)),
508 BinaryOp::LessEqual => Some(i32::from(left <= right)),
509 BinaryOp::NotEqual => Some(i32::from(left != right)),
510 BinaryOp::EqualEqual => Some(i32::from(left == right)),
511 BinaryOp::BooleanAnd => Some(left & right),
512 BinaryOp::ExclusiveOr => Some(left ^ right),
513 BinaryOp::InclusiveOr => Some(left | right),
514 BinaryOp::LogicalAnd => Some(i32::from((left != 0) && (right != 0))),
515 BinaryOp::LogicalOr => Some(i32::from((left != 0) || (right != 0))),
516 }
517}
518
519#[allow(clippy::float_cmp)]
520fn evaluate_float_binary(op: BinaryOp, left: f32, right: f32) -> Option<ConstValue> {
521 match op {
522 BinaryOp::Multiply => Some(ConstValue::Float(left * right)),
523 BinaryOp::Divide => Some(ConstValue::Float(left / right)),
524 BinaryOp::Add => Some(ConstValue::Float(left + right)),
525 BinaryOp::Subtract => Some(ConstValue::Float(left - right)),
526 BinaryOp::GreaterEqual => Some(ConstValue::Int(i32::from(left >= right))),
527 BinaryOp::GreaterThan => Some(ConstValue::Int(i32::from(left > right))),
528 BinaryOp::LessThan => Some(ConstValue::Int(i32::from(left < right))),
529 BinaryOp::LessEqual => Some(ConstValue::Int(i32::from(left <= right))),
530 BinaryOp::NotEqual => Some(ConstValue::Int(i32::from(left != right))),
531 BinaryOp::EqualEqual => Some(ConstValue::Int(i32::from(left == right))),
532 _ => None,
533 }
534}
535
536fn evaluate_string_binary(op: BinaryOp, left: &str, right: &str) -> Option<ConstValue> {
537 match op {
538 BinaryOp::Add => Some(ConstValue::String(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", left, right))
})format!("{left}{right}"))),
539 BinaryOp::EqualEqual => Some(ConstValue::Int(i32::from(left == right))),
540 BinaryOp::NotEqual => Some(ConstValue::Int(i32::from(left != right))),
541 _ => None,
542 }
543}
544
545fn assignment_stack_offset(instruction: &NcsInstruction) -> Option<i32> {
546 let bytes = instruction.extra.get(0..4)?;
547 let mut offset = [0u8; 4];
548 offset.copy_from_slice(bytes);
549 Some(i32::from_be_bytes(offset))
550}
551
552#[cfg(test)]
553mod tests {
554 use super::{OptimizationLevel, meld_instructions, optimize_hir};
555 use crate::{
556 BuiltinConstant, BuiltinFunction, BuiltinParameter, BuiltinType, BuiltinValue, LangSpec,
557 NcsAuxCode, NcsInstruction, NcsOpcode, SourceId, compile_hir_to_ncs,
558 decode_ncs_instructions, lower_to_hir, parse_text,
559 };
560
561 fn test_langspec() -> LangSpec {
562 LangSpec {
563 engine_num_structures: 0,
564 engine_structures: vec![],
565 constants: vec![
566 BuiltinConstant {
567 name: "TRUE".to_string(),
568 ty: BuiltinType::Int,
569 value: BuiltinValue::Int(1),
570 },
571 BuiltinConstant {
572 name: "FALSE".to_string(),
573 ty: BuiltinType::Int,
574 value: BuiltinValue::Int(0),
575 },
576 ],
577 functions: vec![BuiltinFunction {
578 name: "GetValue".to_string(),
579 return_type: BuiltinType::Int,
580 parameters: vec![BuiltinParameter {
581 name: "nValue".to_string(),
582 ty: BuiltinType::Int,
583 default: None,
584 }],
585 }],
586 }
587 }
588
589 #[test]
590 fn o1_eliminates_dead_functions_in_loader_discovery_order() {
591 let script = parse_text(
592 SourceId::new(1),
593 r#"
594 int NeverUsed() { return 9; }
595 int Leaf() { return 7; }
596 int Mid() { return Leaf(); }
597 int StartingConditional() { return Mid(); }
598 "#,
599 Some(&test_langspec()),
600 )
601 .expect("script should parse");
602 let semantic =
603 crate::analyze_script(&script, Some(&test_langspec())).expect("script should analyze");
604 let hir = lower_to_hir(&script, &semantic, Some(&test_langspec()))
605 .expect("HIR lowering should succeed");
606
607 let optimized = optimize_hir(&hir, Some(&test_langspec()), OptimizationLevel::O1);
608 let names = optimized
609 .functions
610 .iter()
611 .map(|function| function.name.as_str())
612 .collect::<Vec<_>>();
613
614 assert_eq!(names, vec!["StartingConditional", "Mid", "Leaf"]);
615 }
616
617 #[test]
618 fn o2_dead_branch_trimming_removes_calls_from_constant_false_if_branches() {
619 let script = parse_text(
620 SourceId::new(2),
621 r#"
622 int Dead() { return 0; }
623 int Live() { return 1; }
624 int StartingConditional() {
625 if (FALSE) {
626 return Dead();
627 } else {
628 return Live();
629 }
630 }
631 "#,
632 Some(&test_langspec()),
633 )
634 .expect("script should parse");
635 let semantic =
636 crate::analyze_script(&script, Some(&test_langspec())).expect("script should analyze");
637 let hir = lower_to_hir(&script, &semantic, Some(&test_langspec()))
638 .expect("HIR lowering should succeed");
639
640 let optimized = optimize_hir(&hir, Some(&test_langspec()), OptimizationLevel::O2);
641 let names = optimized
642 .functions
643 .iter()
644 .map(|function| function.name.as_str())
645 .collect::<Vec<_>>();
646
647 assert_eq!(names, vec!["StartingConditional", "Live"]);
648 }
649
650 #[test]
651 fn o3_melds_local_constant_initializer_pattern() {
652 let script = parse_text(
653 SourceId::new(3),
654 r#"
655 void main() {
656 int nValue = 3;
657 }
658 "#,
659 Some(&test_langspec()),
660 )
661 .expect("script should parse");
662 let semantic =
663 crate::analyze_script(&script, Some(&test_langspec())).expect("script should analyze");
664 let hir = lower_to_hir(&script, &semantic, Some(&test_langspec()))
665 .expect("HIR lowering should succeed");
666
667 let o0 = decode_ncs_instructions(
668 &compile_hir_to_ncs(&hir, Some(&test_langspec()), OptimizationLevel::O0)
669 .expect("O0 compile should succeed"),
670 )
671 .expect("O0 output should decode");
672 let o3 = decode_ncs_instructions(
673 &compile_hir_to_ncs(&hir, Some(&test_langspec()), OptimizationLevel::O3)
674 .expect("O3 compile should succeed"),
675 )
676 .expect("O3 output should decode");
677
678 assert!(
679 o0.iter()
680 .any(|instruction| instruction.opcode == NcsOpcode::RunstackAdd),
681 "O0 should preserve the local initializer stack dance",
682 );
683 assert!(
684 !o3.iter().any(|instruction| {
685 instruction.opcode == NcsOpcode::Assignment
686 && instruction.auxcode == NcsAuxCode::TypeVoid
687 }),
688 "O3 should meld the local constant initializer pattern",
689 );
690 }
691
692 #[test]
693 fn meld_instructions_only_rewrites_the_upstream_active_pattern() {
694 let instructions = vec![
695 NcsInstruction {
696 opcode: NcsOpcode::RunstackAdd,
697 auxcode: NcsAuxCode::TypeInteger,
698 extra: Vec::new(),
699 },
700 NcsInstruction {
701 opcode: NcsOpcode::Constant,
702 auxcode: NcsAuxCode::TypeInteger,
703 extra: 3_i32.to_be_bytes().to_vec(),
704 },
705 NcsInstruction {
706 opcode: NcsOpcode::Assignment,
707 auxcode: NcsAuxCode::TypeVoid,
708 extra: {
709 let mut bytes = Vec::new();
710 bytes.extend_from_slice(&(-8_i32).to_be_bytes());
711 bytes.extend_from_slice(&(4_u16).to_be_bytes());
712 bytes
713 },
714 },
715 NcsInstruction {
716 opcode: NcsOpcode::ModifyStackPointer,
717 auxcode: NcsAuxCode::None,
718 extra: (-4_i32).to_be_bytes().to_vec(),
719 },
720 ];
721
722 let optimized = meld_instructions(instructions);
723 assert_eq!(optimized.len(), 1);
724 assert_eq!(
725 optimized.first().map(|instruction| instruction.opcode),
726 Some(NcsOpcode::Constant)
727 );
728 }
729}