1use crate::lcnf::*;
6use std::collections::HashMap;
7
8use super::functions::{JS_KEYWORDS, JS_RUNTIME};
9
10use std::collections::{HashSet, VecDeque};
11
12#[allow(dead_code)]
14pub struct JsPeephole;
15impl JsPeephole {
16 #[allow(dead_code)]
18 pub fn fold_arith(expr: &JsExpr) -> JsExpr {
19 if let JsExpr::BinOp(op, lhs, rhs) = expr {
20 if let (JsExpr::Lit(JsLit::Num(a)), JsExpr::Lit(JsLit::Num(b))) =
21 (lhs.as_ref(), rhs.as_ref())
22 {
23 let folded = match op.as_str() {
24 "+" => Some(a + b),
25 "-" => Some(a - b),
26 "*" => Some(a * b),
27 "/" if *b != 0.0 => Some(a / b),
28 _ => None,
29 };
30 if let Some(v) = folded {
31 return JsExpr::Lit(JsLit::Num(v));
32 }
33 }
34 }
35 expr.clone()
36 }
37 #[allow(dead_code)]
39 pub fn simplify_identity(expr: &JsExpr) -> JsExpr {
40 if let JsExpr::BinOp(op, lhs, rhs) = expr {
41 if op == "===" && lhs == rhs {
42 return JsExpr::Lit(JsLit::Bool(true));
43 }
44 if op == "!==" && lhs == rhs {
45 return JsExpr::Lit(JsLit::Bool(false));
46 }
47 }
48 expr.clone()
49 }
50 #[allow(dead_code)]
52 pub fn simplify_not(expr: &JsExpr) -> JsExpr {
53 if let JsExpr::UnOp(op, inner) = expr {
54 if op == "!" {
55 if let JsExpr::Lit(JsLit::Bool(b)) = inner.as_ref() {
56 return JsExpr::Lit(JsLit::Bool(!b));
57 }
58 }
59 }
60 expr.clone()
61 }
62 #[allow(dead_code)]
64 pub fn optimize(expr: &JsExpr) -> JsExpr {
65 let e = Self::fold_arith(expr);
66 let e = Self::simplify_identity(&e);
67 Self::simplify_not(&e)
68 }
69}
70#[allow(dead_code)]
72pub struct JsSizeEstimator;
73impl JsSizeEstimator {
74 #[allow(dead_code)]
76 pub fn estimate_function(func: &JsFunction) -> usize {
77 func.to_string().len()
78 }
79 #[allow(dead_code)]
81 pub fn estimate_module(module: &JsModule) -> usize {
82 module.emit().len()
83 }
84 #[allow(dead_code)]
86 pub fn estimate_expr(expr: &JsExpr) -> usize {
87 expr.to_string().len()
88 }
89 #[allow(dead_code)]
91 pub fn estimate_stmt(stmt: &JsStmt) -> usize {
92 stmt.to_string().len()
93 }
94}
95#[allow(dead_code)]
96pub struct JSPassRegistry {
97 pub(super) configs: Vec<JSPassConfig>,
98 pub(super) stats: std::collections::HashMap<String, JSPassStats>,
99}
100impl JSPassRegistry {
101 #[allow(dead_code)]
102 pub fn new() -> Self {
103 JSPassRegistry {
104 configs: Vec::new(),
105 stats: std::collections::HashMap::new(),
106 }
107 }
108 #[allow(dead_code)]
109 pub fn register(&mut self, config: JSPassConfig) {
110 self.stats
111 .insert(config.pass_name.clone(), JSPassStats::new());
112 self.configs.push(config);
113 }
114 #[allow(dead_code)]
115 pub fn enabled_passes(&self) -> Vec<&JSPassConfig> {
116 self.configs.iter().filter(|c| c.enabled).collect()
117 }
118 #[allow(dead_code)]
119 pub fn get_stats(&self, name: &str) -> Option<&JSPassStats> {
120 self.stats.get(name)
121 }
122 #[allow(dead_code)]
123 pub fn total_passes(&self) -> usize {
124 self.configs.len()
125 }
126 #[allow(dead_code)]
127 pub fn enabled_count(&self) -> usize {
128 self.enabled_passes().len()
129 }
130 #[allow(dead_code)]
131 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
132 if let Some(stats) = self.stats.get_mut(name) {
133 stats.record_run(changes, time_ms, iter);
134 }
135 }
136}
137#[allow(dead_code)]
140pub struct JsMinifier;
141impl JsMinifier {
142 #[allow(dead_code)]
149 pub fn minify(source: &str) -> std::string::String {
150 let mut out = std::string::String::new();
151 for line in source.lines() {
152 let line = if let Some(pos) = line.find("//") {
153 &line[..pos]
154 } else {
155 line
156 };
157 let trimmed = line.trim();
158 if !trimmed.is_empty() {
159 let collapsed: std::string::String =
160 trimmed.split_whitespace().collect::<Vec<_>>().join(" ");
161 out.push_str(&collapsed);
162 out.push('\n');
163 }
164 }
165 out
166 }
167 #[allow(dead_code)]
169 pub fn strip_block_comments(source: &str) -> std::string::String {
170 let mut out = std::string::String::new();
171 let mut in_comment = false;
172 let bytes = source.as_bytes();
173 let mut i = 0;
174 while i < bytes.len() {
175 if !in_comment && i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'*' {
176 in_comment = true;
177 i += 2;
178 } else if in_comment && i + 1 < bytes.len() && bytes[i] == b'*' && bytes[i + 1] == b'/'
179 {
180 in_comment = false;
181 i += 2;
182 } else if !in_comment {
183 out.push(bytes[i] as char);
184 i += 1;
185 } else {
186 i += 1;
187 }
188 }
189 out
190 }
191}
192#[allow(dead_code)]
196pub struct JsTypeChecker;
197impl JsTypeChecker {
198 #[allow(dead_code)]
200 pub fn infer_lit(lit: &JsLit) -> JsType {
201 match lit {
202 JsLit::Num(_) => JsType::Number,
203 JsLit::BigInt(_) => JsType::BigInt,
204 JsLit::Str(_) => JsType::String,
205 JsLit::Bool(_) => JsType::Boolean,
206 JsLit::Null => JsType::Null,
207 JsLit::Undefined => JsType::Undefined,
208 }
209 }
210 #[allow(dead_code)]
212 pub fn infer_expr(expr: &JsExpr) -> JsType {
213 match expr {
214 JsExpr::Lit(lit) => Self::infer_lit(lit),
215 JsExpr::BinOp(op, lhs, _) => match op.as_str() {
216 "===" | "!==" | "==" | "!=" | "<" | ">" | "<=" | ">=" => JsType::Boolean,
217 "+" => {
218 let lhs_ty = Self::infer_expr(lhs);
219 if lhs_ty == JsType::String {
220 JsType::String
221 } else if lhs_ty == JsType::BigInt {
222 JsType::BigInt
223 } else {
224 JsType::Number
225 }
226 }
227 "-" | "*" | "/" | "%" => {
228 if Self::infer_expr(lhs) == JsType::BigInt {
229 JsType::BigInt
230 } else {
231 JsType::Number
232 }
233 }
234 _ => JsType::Unknown,
235 },
236 JsExpr::UnOp(op, _) => match op.as_str() {
237 "!" => JsType::Boolean,
238 "-" | "+" => JsType::Number,
239 "typeof" => JsType::String,
240 _ => JsType::Unknown,
241 },
242 JsExpr::Object(_) => JsType::Object,
243 JsExpr::Array(_) => JsType::Array,
244 JsExpr::Arrow(_, _) => JsType::Function,
245 JsExpr::New(_, _) => JsType::Object,
246 _ => JsType::Unknown,
247 }
248 }
249}
250#[derive(Debug, Clone)]
252pub struct JsModule {
253 pub functions: Vec<JsFunction>,
255 pub preamble: Vec<std::string::String>,
257 pub exports: Vec<std::string::String>,
259}
260impl JsModule {
261 pub fn new() -> Self {
263 JsModule {
264 functions: Vec::new(),
265 preamble: Vec::new(),
266 exports: Vec::new(),
267 }
268 }
269 pub fn add_function(&mut self, f: JsFunction) {
271 self.functions.push(f);
272 }
273 pub fn add_preamble(&mut self, line: std::string::String) {
275 self.preamble.push(line);
276 }
277 pub fn add_export(&mut self, name: std::string::String) {
279 self.exports.push(name);
280 }
281 pub fn emit(&self) -> std::string::String {
289 let mut out = std::string::String::new();
290 out.push_str(JS_RUNTIME);
291 out.push('\n');
292 for line in &self.preamble {
293 out.push_str(line);
294 out.push('\n');
295 }
296 if !self.preamble.is_empty() {
297 out.push('\n');
298 }
299 for func in &self.functions {
300 out.push_str(&func.to_string());
301 out.push_str("\n\n");
302 }
303 if !self.exports.is_empty() {
304 out.push_str("export { ");
305 for (i, name) in self.exports.iter().enumerate() {
306 if i > 0 {
307 out.push_str(", ");
308 }
309 out.push_str(name);
310 }
311 out.push_str(" };\n");
312 }
313 out
314 }
315}
316#[allow(dead_code)]
317#[derive(Debug, Clone)]
318pub struct JSDominatorTree {
319 pub idom: Vec<Option<u32>>,
320 pub dom_children: Vec<Vec<u32>>,
321 pub dom_depth: Vec<u32>,
322}
323impl JSDominatorTree {
324 #[allow(dead_code)]
325 pub fn new(size: usize) -> Self {
326 JSDominatorTree {
327 idom: vec![None; size],
328 dom_children: vec![Vec::new(); size],
329 dom_depth: vec![0; size],
330 }
331 }
332 #[allow(dead_code)]
333 pub fn set_idom(&mut self, node: usize, idom: u32) {
334 self.idom[node] = Some(idom);
335 }
336 #[allow(dead_code)]
337 pub fn dominates(&self, a: usize, b: usize) -> bool {
338 if a == b {
339 return true;
340 }
341 let mut cur = b;
342 loop {
343 match self.idom[cur] {
344 Some(parent) if parent as usize == a => return true,
345 Some(parent) if parent as usize == cur => return false,
346 Some(parent) => cur = parent as usize,
347 None => return false,
348 }
349 }
350 }
351 #[allow(dead_code)]
352 pub fn depth(&self, node: usize) -> u32 {
353 self.dom_depth.get(node).copied().unwrap_or(0)
354 }
355}
356#[derive(Debug, Clone, PartialEq)]
358pub enum JsLit {
359 Num(f64),
361 BigInt(i64),
363 Str(std::string::String),
365 Bool(bool),
367 Null,
369 Undefined,
371}
372#[derive(Debug, Clone, PartialEq)]
374pub struct JsFunction {
375 pub name: std::string::String,
377 pub params: Vec<std::string::String>,
379 pub body: Vec<JsStmt>,
381 pub is_async: bool,
383 pub is_export: bool,
385}
386#[allow(dead_code)]
387#[derive(Debug, Clone)]
388pub struct JSPassConfig {
389 pub phase: JSPassPhase,
390 pub enabled: bool,
391 pub max_iterations: u32,
392 pub debug_output: bool,
393 pub pass_name: String,
394}
395impl JSPassConfig {
396 #[allow(dead_code)]
397 pub fn new(name: impl Into<String>, phase: JSPassPhase) -> Self {
398 JSPassConfig {
399 phase,
400 enabled: true,
401 max_iterations: 10,
402 debug_output: false,
403 pass_name: name.into(),
404 }
405 }
406 #[allow(dead_code)]
407 pub fn disabled(mut self) -> Self {
408 self.enabled = false;
409 self
410 }
411 #[allow(dead_code)]
412 pub fn with_debug(mut self) -> Self {
413 self.debug_output = true;
414 self
415 }
416 #[allow(dead_code)]
417 pub fn max_iter(mut self, n: u32) -> Self {
418 self.max_iterations = n;
419 self
420 }
421}
422#[derive(Debug, Clone, PartialEq)]
424pub enum JsExpr {
425 Lit(JsLit),
427 Var(std::string::String),
429 Call(Box<JsExpr>, Vec<JsExpr>),
431 Method(Box<JsExpr>, std::string::String, Vec<JsExpr>),
433 Field(Box<JsExpr>, std::string::String),
435 Index(Box<JsExpr>, Box<JsExpr>),
437 Arrow(Vec<std::string::String>, Box<JsStmt>),
439 Ternary(Box<JsExpr>, Box<JsExpr>, Box<JsExpr>),
441 BinOp(std::string::String, Box<JsExpr>, Box<JsExpr>),
443 UnOp(std::string::String, Box<JsExpr>),
445 Await(Box<JsExpr>),
447 New(std::string::String, Vec<JsExpr>),
449 Spread(Box<JsExpr>),
451 Object(Vec<(std::string::String, JsExpr)>),
453 Array(Vec<JsExpr>),
455}
456#[allow(dead_code)]
457#[derive(Debug, Clone)]
458pub struct JSAnalysisCache {
459 pub(super) entries: std::collections::HashMap<String, JSCacheEntry>,
460 pub(super) max_size: usize,
461 pub(super) hits: u64,
462 pub(super) misses: u64,
463}
464impl JSAnalysisCache {
465 #[allow(dead_code)]
466 pub fn new(max_size: usize) -> Self {
467 JSAnalysisCache {
468 entries: std::collections::HashMap::new(),
469 max_size,
470 hits: 0,
471 misses: 0,
472 }
473 }
474 #[allow(dead_code)]
475 pub fn get(&mut self, key: &str) -> Option<&JSCacheEntry> {
476 if self.entries.contains_key(key) {
477 self.hits += 1;
478 self.entries.get(key)
479 } else {
480 self.misses += 1;
481 None
482 }
483 }
484 #[allow(dead_code)]
485 pub fn insert(&mut self, key: String, data: Vec<u8>) {
486 if self.entries.len() >= self.max_size {
487 if let Some(oldest) = self.entries.keys().next().cloned() {
488 self.entries.remove(&oldest);
489 }
490 }
491 self.entries.insert(
492 key.clone(),
493 JSCacheEntry {
494 key,
495 data,
496 timestamp: 0,
497 valid: true,
498 },
499 );
500 }
501 #[allow(dead_code)]
502 pub fn invalidate(&mut self, key: &str) {
503 if let Some(entry) = self.entries.get_mut(key) {
504 entry.valid = false;
505 }
506 }
507 #[allow(dead_code)]
508 pub fn clear(&mut self) {
509 self.entries.clear();
510 }
511 #[allow(dead_code)]
512 pub fn hit_rate(&self) -> f64 {
513 let total = self.hits + self.misses;
514 if total == 0 {
515 return 0.0;
516 }
517 self.hits as f64 / total as f64
518 }
519 #[allow(dead_code)]
520 pub fn size(&self) -> usize {
521 self.entries.len()
522 }
523}
524#[allow(dead_code)]
526#[derive(Debug, Clone, Default)]
527pub struct JsSourceMap {
528 pub(super) entries: Vec<SourceMapEntry>,
529}
530impl JsSourceMap {
531 #[allow(dead_code)]
533 pub fn new() -> Self {
534 Self::default()
535 }
536 #[allow(dead_code)]
538 pub fn add(&mut self, entry: SourceMapEntry) {
539 self.entries.push(entry);
540 }
541 #[allow(dead_code)]
543 pub fn len(&self) -> usize {
544 self.entries.len()
545 }
546 #[allow(dead_code)]
548 pub fn is_empty(&self) -> bool {
549 self.entries.is_empty()
550 }
551 #[allow(dead_code)]
553 pub fn entries_for_line(&self, gen_line: u32) -> Vec<&SourceMapEntry> {
554 self.entries
555 .iter()
556 .filter(|e| e.gen_line == gen_line)
557 .collect()
558 }
559}
560#[allow(dead_code)]
562pub struct JsPrettyPrinter {
563 pub indent_width: usize,
565 pub line_width: usize,
567 pub trailing_commas: bool,
569}
570impl JsPrettyPrinter {
571 #[allow(dead_code)]
573 pub fn new() -> Self {
574 JsPrettyPrinter {
575 indent_width: 2,
576 line_width: 80,
577 trailing_commas: false,
578 }
579 }
580 #[allow(dead_code)]
582 pub fn print_module(&self, module: &JsModule) -> std::string::String {
583 module.emit()
584 }
585 #[allow(dead_code)]
587 pub fn print_function(&self, func: &JsFunction) -> std::string::String {
588 func.to_string()
589 }
590 #[allow(dead_code)]
592 pub fn print_expr(&self, expr: &JsExpr, _depth: usize) -> std::string::String {
593 expr.to_string()
594 }
595}
596#[allow(dead_code)]
597#[derive(Debug, Clone)]
598pub struct JSCacheEntry {
599 pub key: String,
600 pub data: Vec<u8>,
601 pub timestamp: u64,
602 pub valid: bool,
603}
604#[allow(dead_code)]
605#[derive(Debug, Clone)]
606pub struct JSWorklist {
607 pub(super) items: std::collections::VecDeque<u32>,
608 pub(super) in_worklist: std::collections::HashSet<u32>,
609}
610impl JSWorklist {
611 #[allow(dead_code)]
612 pub fn new() -> Self {
613 JSWorklist {
614 items: std::collections::VecDeque::new(),
615 in_worklist: std::collections::HashSet::new(),
616 }
617 }
618 #[allow(dead_code)]
619 pub fn push(&mut self, item: u32) -> bool {
620 if self.in_worklist.insert(item) {
621 self.items.push_back(item);
622 true
623 } else {
624 false
625 }
626 }
627 #[allow(dead_code)]
628 pub fn pop(&mut self) -> Option<u32> {
629 let item = self.items.pop_front()?;
630 self.in_worklist.remove(&item);
631 Some(item)
632 }
633 #[allow(dead_code)]
634 pub fn is_empty(&self) -> bool {
635 self.items.is_empty()
636 }
637 #[allow(dead_code)]
638 pub fn len(&self) -> usize {
639 self.items.len()
640 }
641 #[allow(dead_code)]
642 pub fn contains(&self, item: u32) -> bool {
643 self.in_worklist.contains(&item)
644 }
645}
646pub struct JsBackend {
651 pub module: JsModule,
653 pub fn_map: HashMap<std::string::String, std::string::String>,
655 pub fresh_counter: usize,
657}
658impl JsBackend {
659 pub fn new() -> Self {
661 JsBackend {
662 module: JsModule::new(),
663 fn_map: HashMap::new(),
664 fresh_counter: 0,
665 }
666 }
667 pub fn fresh_var(&mut self) -> std::string::String {
669 let n = self.fresh_counter;
670 self.fresh_counter += 1;
671 format!("_t{}", n)
672 }
673 pub fn mangle_name(&self, name: &str) -> std::string::String {
680 let mangled: std::string::String = name
681 .chars()
682 .map(|c| match c {
683 '.' => '_',
684 '\'' => '_',
685 '-' => '_',
686 c if c.is_alphanumeric() || c == '_' => c,
687 _ => '_',
688 })
689 .collect();
690 if JS_KEYWORDS.contains(&mangled.as_str())
691 || mangled.starts_with(|c: char| c.is_ascii_digit())
692 {
693 format!("_{}", mangled)
694 } else if mangled.is_empty() {
695 "_anon".to_string()
696 } else {
697 mangled
698 }
699 }
700 pub fn compile_module(
703 decls: &[LcnfFunDecl],
704 ) -> Result<std::string::String, std::string::String> {
705 let mut backend = JsBackend::new();
706 for decl in decls {
707 let js_name = backend.mangle_name(&decl.name);
708 backend.fn_map.insert(decl.name.clone(), js_name);
709 }
710 for decl in decls {
711 let func = backend.compile_decl(decl)?;
712 backend.module.add_function(func);
713 }
714 for decl in decls {
715 if let Some(js_name) = backend.fn_map.get(&decl.name) {
716 backend.module.add_export(js_name.clone());
717 }
718 }
719 Ok(backend.module.emit())
720 }
721 pub fn compile_decl(&mut self, decl: &LcnfFunDecl) -> Result<JsFunction, std::string::String> {
723 let js_name = self.mangle_name(&decl.name);
724 self.fn_map.insert(decl.name.clone(), js_name.clone());
725 let params: Vec<std::string::String> = decl
726 .params
727 .iter()
728 .map(|p| {
729 if p.erased {
730 format!("_{}", self.mangle_name(&p.name))
731 } else {
732 self.mangle_name(&p.name)
733 }
734 })
735 .collect();
736 let mut body_stmts: Vec<JsStmt> = Vec::new();
737 let result = self.compile_expr(&decl.body, &mut body_stmts)?;
738 let last_is_return = body_stmts.last().is_some_and(|s| {
739 matches!(s, JsStmt::Return(_) | JsStmt::ReturnVoid | JsStmt::Throw(_))
740 });
741 if !last_is_return {
742 body_stmts.push(JsStmt::Return(result));
743 }
744 Ok(JsFunction {
745 name: js_name,
746 params,
747 body: body_stmts,
748 is_async: false,
749 is_export: false,
750 })
751 }
752 pub fn compile_expr(
755 &mut self,
756 expr: &LcnfExpr,
757 stmts: &mut Vec<JsStmt>,
758 ) -> Result<JsExpr, std::string::String> {
759 match expr {
760 LcnfExpr::Let {
761 id: _,
762 name,
763 ty: _,
764 value,
765 body,
766 } => {
767 let js_val = self.compile_let_value(value, stmts)?;
768 let js_name = self.mangle_name(name);
769 stmts.push(JsStmt::Const(js_name.clone(), js_val));
770 self.compile_expr(body, stmts)
771 }
772 LcnfExpr::Case {
773 scrutinee,
774 scrutinee_ty: _,
775 alts,
776 default,
777 } => {
778 let scrutinee_name = format!("_x{}", scrutinee.0);
779 let tag_expr = JsExpr::Field(
780 Box::new(JsExpr::Var(scrutinee_name.clone())),
781 "tag".to_string(),
782 );
783 if alts.len() == 1 && alts[0].ctor_name.is_empty() {
784 let alt = &alts[0];
785 for (i, param) in alt.params.iter().enumerate() {
786 let field_expr = JsExpr::Call(
787 Box::new(JsExpr::Field(
788 Box::new(JsExpr::Var("_OL".to_string())),
789 "proj".to_string(),
790 )),
791 vec![
792 JsExpr::Var(scrutinee_name.clone()),
793 JsExpr::Lit(JsLit::Num(i as f64)),
794 ],
795 );
796 let pname = self.mangle_name(¶m.name);
797 stmts.push(JsStmt::Const(pname, field_expr));
798 }
799 return self.compile_expr(&alt.body, stmts);
800 }
801 let result_var = self.fresh_var();
802 stmts.push(JsStmt::Let(
803 result_var.clone(),
804 JsExpr::Lit(JsLit::Undefined),
805 ));
806 let mut cases: Vec<(JsExpr, Vec<JsStmt>)> = Vec::new();
807 for alt in alts {
808 let mut case_stmts: Vec<JsStmt> = Vec::new();
809 for (i, param) in alt.params.iter().enumerate() {
810 let field_expr = JsExpr::Call(
811 Box::new(JsExpr::Field(
812 Box::new(JsExpr::Var("_OL".to_string())),
813 "proj".to_string(),
814 )),
815 vec![
816 JsExpr::Var(scrutinee_name.clone()),
817 JsExpr::Lit(JsLit::Num(i as f64)),
818 ],
819 );
820 let pname = self.mangle_name(¶m.name);
821 case_stmts.push(JsStmt::Const(pname, field_expr));
822 }
823 let branch_result = self.compile_expr(&alt.body, &mut case_stmts)?;
824 case_stmts.push(JsStmt::Expr(JsExpr::BinOp(
825 "=".to_string(),
826 Box::new(JsExpr::Var(result_var.clone())),
827 Box::new(branch_result),
828 )));
829 let tag_lit = JsExpr::Lit(JsLit::Str(alt.ctor_name.clone()));
830 cases.push((tag_lit, case_stmts));
831 }
832 let default_stmts = if let Some(def) = default {
833 let mut def_stmts: Vec<JsStmt> = Vec::new();
834 let def_result = self.compile_expr(def, &mut def_stmts)?;
835 def_stmts.push(JsStmt::Expr(JsExpr::BinOp(
836 "=".to_string(),
837 Box::new(JsExpr::Var(result_var.clone())),
838 Box::new(def_result),
839 )));
840 def_stmts
841 } else {
842 vec![JsStmt::Throw(JsExpr::New(
843 "Error".to_string(),
844 vec![JsExpr::Lit(JsLit::Str("Unreachable case".to_string()))],
845 ))]
846 };
847 stmts.push(JsStmt::Switch(tag_expr, cases, default_stmts));
848 Ok(JsExpr::Var(result_var))
849 }
850 LcnfExpr::Return(arg) => Ok(self.compile_arg(arg)),
851 LcnfExpr::Unreachable => {
852 stmts.push(JsStmt::Throw(JsExpr::New(
853 "Error".to_string(),
854 vec![JsExpr::Lit(JsLit::Str("Unreachable".to_string()))],
855 )));
856 Ok(JsExpr::Lit(JsLit::Undefined))
857 }
858 LcnfExpr::TailCall(func, args) => {
859 let js_func = self.compile_arg(func);
860 let js_args: Vec<JsExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
861 Ok(JsExpr::Call(Box::new(js_func), js_args))
862 }
863 }
864 }
865 pub(super) fn compile_let_value(
867 &mut self,
868 value: &LcnfLetValue,
869 stmts: &mut Vec<JsStmt>,
870 ) -> Result<JsExpr, std::string::String> {
871 match value {
872 LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
873 LcnfLetValue::App(func, args) => {
874 let js_func = self.compile_arg(func);
875 let js_args: Vec<JsExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
876 Ok(JsExpr::Call(Box::new(js_func), js_args))
877 }
878 LcnfLetValue::Ctor(name, tag, args) => {
879 let mut ctor_args = vec![JsExpr::Lit(JsLit::Str(name.clone()))];
880 for a in args {
881 ctor_args.push(self.compile_arg(a));
882 }
883 let _ = tag;
884 Ok(JsExpr::Call(
885 Box::new(JsExpr::Field(
886 Box::new(JsExpr::Var("_OL".to_string())),
887 "ctor".to_string(),
888 )),
889 ctor_args,
890 ))
891 }
892 LcnfLetValue::Proj(_, idx, var) => {
893 let var_expr = JsExpr::Var(format!("_x{}", var.0));
894 Ok(JsExpr::Call(
895 Box::new(JsExpr::Field(
896 Box::new(JsExpr::Var("_OL".to_string())),
897 "proj".to_string(),
898 )),
899 vec![var_expr, JsExpr::Lit(JsLit::Num(*idx as f64))],
900 ))
901 }
902 LcnfLetValue::Erased => Ok(JsExpr::Lit(JsLit::Undefined)),
903 LcnfLetValue::FVar(id) => Ok(JsExpr::Var(format!("_x{}", id.0))),
904 LcnfLetValue::Reset(_var) => Ok(JsExpr::Lit(JsLit::Null)),
905 LcnfLetValue::Reuse(_slot, name, _tag, args) => {
906 let mut ctor_args = vec![JsExpr::Lit(JsLit::Str(name.clone()))];
907 for a in args {
908 ctor_args.push(self.compile_arg(a));
909 }
910 let _ = stmts;
911 Ok(JsExpr::Call(
912 Box::new(JsExpr::Field(
913 Box::new(JsExpr::Var("_OL".to_string())),
914 "ctor".to_string(),
915 )),
916 ctor_args,
917 ))
918 }
919 }
920 }
921 pub fn compile_arg(&self, arg: &LcnfArg) -> JsExpr {
923 match arg {
924 LcnfArg::Var(id) => JsExpr::Var(format!("_x{}", id.0)),
925 LcnfArg::Lit(lit) => self.compile_lit(lit),
926 LcnfArg::Erased => JsExpr::Lit(JsLit::Undefined),
927 LcnfArg::Type(_) => JsExpr::Lit(JsLit::Undefined),
928 }
929 }
930 pub fn compile_lit(&self, lit: &LcnfLit) -> JsExpr {
932 match lit {
933 LcnfLit::Nat(n) => JsExpr::Lit(JsLit::BigInt(*n as i64)),
934 LcnfLit::Str(s) => JsExpr::Lit(JsLit::Str(s.clone())),
935 }
936 }
937}
938#[allow(dead_code)]
939pub struct JSConstantFoldingHelper;
940impl JSConstantFoldingHelper {
941 #[allow(dead_code)]
942 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
943 a.checked_add(b)
944 }
945 #[allow(dead_code)]
946 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
947 a.checked_sub(b)
948 }
949 #[allow(dead_code)]
950 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
951 a.checked_mul(b)
952 }
953 #[allow(dead_code)]
954 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
955 if b == 0 {
956 None
957 } else {
958 a.checked_div(b)
959 }
960 }
961 #[allow(dead_code)]
962 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
963 a + b
964 }
965 #[allow(dead_code)]
966 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
967 a * b
968 }
969 #[allow(dead_code)]
970 pub fn fold_neg_i64(a: i64) -> Option<i64> {
971 a.checked_neg()
972 }
973 #[allow(dead_code)]
974 pub fn fold_not_bool(a: bool) -> bool {
975 !a
976 }
977 #[allow(dead_code)]
978 pub fn fold_and_bool(a: bool, b: bool) -> bool {
979 a && b
980 }
981 #[allow(dead_code)]
982 pub fn fold_or_bool(a: bool, b: bool) -> bool {
983 a || b
984 }
985 #[allow(dead_code)]
986 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
987 a.checked_shl(b)
988 }
989 #[allow(dead_code)]
990 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
991 a.checked_shr(b)
992 }
993 #[allow(dead_code)]
994 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
995 if b == 0 {
996 None
997 } else {
998 Some(a % b)
999 }
1000 }
1001 #[allow(dead_code)]
1002 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1003 a & b
1004 }
1005 #[allow(dead_code)]
1006 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1007 a | b
1008 }
1009 #[allow(dead_code)]
1010 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1011 a ^ b
1012 }
1013 #[allow(dead_code)]
1014 pub fn fold_bitnot_i64(a: i64) -> i64 {
1015 !a
1016 }
1017}
1018#[allow(dead_code)]
1020pub struct JsNameMangler {
1021 pub namespace: std::string::String,
1023 pub preserve_case: bool,
1025}
1026impl JsNameMangler {
1027 #[allow(dead_code)]
1029 pub fn new(namespace: &str) -> Self {
1030 JsNameMangler {
1031 namespace: namespace.to_string(),
1032 preserve_case: true,
1033 }
1034 }
1035 #[allow(dead_code)]
1037 pub fn mangle(&self, name: &str) -> std::string::String {
1038 let backend = JsBackend::new();
1039 let mangled = backend.mangle_name(name);
1040 if self.namespace.is_empty() {
1041 mangled
1042 } else {
1043 format!("{}_{}", self.namespace, mangled)
1044 }
1045 }
1046 #[allow(dead_code)]
1048 pub fn mangle_qualified(&self, parts: &[&str]) -> std::string::String {
1049 let joined = parts.join(".");
1050 self.mangle(&joined)
1051 }
1052}
1053#[allow(dead_code)]
1055#[derive(Debug, Clone)]
1056pub struct SourceMapEntry {
1057 pub gen_line: u32,
1059 pub gen_col: u32,
1061 pub source_fn: std::string::String,
1063 pub source_line: u32,
1065}
1066impl SourceMapEntry {
1067 #[allow(dead_code)]
1069 pub fn new(gen_line: u32, gen_col: u32, source_fn: &str, source_line: u32) -> Self {
1070 SourceMapEntry {
1071 gen_line,
1072 gen_col,
1073 source_fn: source_fn.to_string(),
1074 source_line,
1075 }
1076 }
1077}
1078#[allow(dead_code)]
1079#[derive(Debug, Clone, PartialEq)]
1080pub enum JSPassPhase {
1081 Analysis,
1082 Transformation,
1083 Verification,
1084 Cleanup,
1085}
1086impl JSPassPhase {
1087 #[allow(dead_code)]
1088 pub fn name(&self) -> &str {
1089 match self {
1090 JSPassPhase::Analysis => "analysis",
1091 JSPassPhase::Transformation => "transformation",
1092 JSPassPhase::Verification => "verification",
1093 JSPassPhase::Cleanup => "cleanup",
1094 }
1095 }
1096 #[allow(dead_code)]
1097 pub fn is_modifying(&self) -> bool {
1098 matches!(self, JSPassPhase::Transformation | JSPassPhase::Cleanup)
1099 }
1100}
1101#[allow(dead_code)]
1103#[derive(Debug, Clone, Default)]
1104pub struct JsIdentTable {
1105 pub(super) idents: std::collections::HashSet<std::string::String>,
1107 pub(super) collisions: std::collections::HashMap<std::string::String, usize>,
1109}
1110impl JsIdentTable {
1111 #[allow(dead_code)]
1113 pub fn new() -> Self {
1114 Self::default()
1115 }
1116 #[allow(dead_code)]
1118 pub fn register(&mut self, name: &str) -> std::string::String {
1119 if self.idents.contains(name) {
1120 let count = self.collisions.entry(name.to_string()).or_insert(0);
1121 *count += 1;
1122 let renamed = format!("{}_{}", name, count);
1123 self.idents.insert(renamed.clone());
1124 renamed
1125 } else {
1126 self.idents.insert(name.to_string());
1127 name.to_string()
1128 }
1129 }
1130 #[allow(dead_code)]
1132 pub fn is_taken(&self, name: &str) -> bool {
1133 self.idents.contains(name)
1134 }
1135 #[allow(dead_code)]
1137 pub fn len(&self) -> usize {
1138 self.idents.len()
1139 }
1140 #[allow(dead_code)]
1142 pub fn is_empty(&self) -> bool {
1143 self.idents.is_empty()
1144 }
1145}
1146#[derive(Debug, Clone, PartialEq)]
1148pub enum JsStmt {
1149 Expr(JsExpr),
1151 Let(std::string::String, JsExpr),
1153 Const(std::string::String, JsExpr),
1155 Return(JsExpr),
1157 ReturnVoid,
1159 If(JsExpr, Vec<JsStmt>, Vec<JsStmt>),
1161 While(JsExpr, Vec<JsStmt>),
1163 For(std::string::String, JsExpr, Vec<JsStmt>),
1165 Block(Vec<JsStmt>),
1167 Throw(JsExpr),
1169 TryCatch(Vec<JsStmt>, std::string::String, Vec<JsStmt>),
1171 Switch(JsExpr, Vec<(JsExpr, Vec<JsStmt>)>, Vec<JsStmt>),
1173}
1174#[allow(dead_code)]
1176pub struct JsModuleLinker {
1177 pub(super) modules: Vec<JsModule>,
1178}
1179impl JsModuleLinker {
1180 #[allow(dead_code)]
1182 pub fn new() -> Self {
1183 JsModuleLinker {
1184 modules: Vec::new(),
1185 }
1186 }
1187 #[allow(dead_code)]
1189 pub fn add_module(&mut self, module: JsModule) {
1190 self.modules.push(module);
1191 }
1192 #[allow(dead_code)]
1197 pub fn link(&self) -> JsModule {
1198 let mut combined = JsModule::new();
1199 let mut seen_preamble: std::collections::HashSet<std::string::String> =
1200 std::collections::HashSet::new();
1201 for module in &self.modules {
1202 for line in &module.preamble {
1203 if seen_preamble.insert(line.clone()) {
1204 combined.preamble.push(line.clone());
1205 }
1206 }
1207 for func in &module.functions {
1208 combined.functions.push(func.clone());
1209 }
1210 for name in &module.exports {
1211 if !combined.exports.contains(name) {
1212 combined.exports.push(name.clone());
1213 }
1214 }
1215 }
1216 combined
1217 }
1218 #[allow(dead_code)]
1220 pub fn len(&self) -> usize {
1221 self.modules.len()
1222 }
1223 #[allow(dead_code)]
1225 pub fn is_empty(&self) -> bool {
1226 self.modules.is_empty()
1227 }
1228}
1229#[allow(dead_code)]
1231#[derive(Debug, Clone)]
1232pub struct JsBackendConfig {
1233 pub use_bigint_for_nat: bool,
1235 pub strict_mode: bool,
1237 pub include_runtime: bool,
1239 pub emit_jsdoc: bool,
1241 pub module_format: JsModuleFormat,
1243 pub minify: bool,
1245}
1246#[allow(dead_code)]
1248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1249pub enum JsModuleFormat {
1250 Es,
1252 Cjs,
1254 None,
1256}
1257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1259pub enum JsType {
1260 Undefined,
1262 Null,
1264 Boolean,
1266 Number,
1268 BigInt,
1270 String,
1272 Object,
1274 Array,
1276 Function,
1278 Unknown,
1280}
1281#[allow(dead_code)]
1283pub struct JsEmitContext {
1284 pub indent_level: usize,
1286 pub indent_str: std::string::String,
1288 pub source_map: JsSourceMap,
1290 pub current_line: u32,
1292 pub current_col: u32,
1294 pub in_async: bool,
1296}
1297impl JsEmitContext {
1298 #[allow(dead_code)]
1300 pub fn new(indent: &str) -> Self {
1301 JsEmitContext {
1302 indent_level: 0,
1303 indent_str: indent.to_string(),
1304 source_map: JsSourceMap::new(),
1305 current_line: 0,
1306 current_col: 0,
1307 in_async: false,
1308 }
1309 }
1310 #[allow(dead_code)]
1312 pub fn indent(&self) -> std::string::String {
1313 self.indent_str.repeat(self.indent_level)
1314 }
1315 #[allow(dead_code)]
1317 pub fn push_indent(&mut self) {
1318 self.indent_level += 1;
1319 }
1320 #[allow(dead_code)]
1322 pub fn pop_indent(&mut self) {
1323 if self.indent_level > 0 {
1324 self.indent_level -= 1;
1325 }
1326 }
1327 #[allow(dead_code)]
1329 pub fn newline(&mut self) {
1330 self.current_line += 1;
1331 self.current_col = 0;
1332 }
1333 #[allow(dead_code)]
1335 pub fn record_mapping(&mut self, fn_name: &str, source_line: u32) {
1336 self.source_map.add(SourceMapEntry::new(
1337 self.current_line,
1338 self.current_col,
1339 fn_name,
1340 source_line,
1341 ));
1342 }
1343}
1344#[allow(dead_code)]
1345#[derive(Debug, Clone, Default)]
1346pub struct JSPassStats {
1347 pub total_runs: u32,
1348 pub successful_runs: u32,
1349 pub total_changes: u64,
1350 pub time_ms: u64,
1351 pub iterations_used: u32,
1352}
1353impl JSPassStats {
1354 #[allow(dead_code)]
1355 pub fn new() -> Self {
1356 Self::default()
1357 }
1358 #[allow(dead_code)]
1359 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1360 self.total_runs += 1;
1361 self.successful_runs += 1;
1362 self.total_changes += changes;
1363 self.time_ms += time_ms;
1364 self.iterations_used = iterations;
1365 }
1366 #[allow(dead_code)]
1367 pub fn average_changes_per_run(&self) -> f64 {
1368 if self.total_runs == 0 {
1369 return 0.0;
1370 }
1371 self.total_changes as f64 / self.total_runs as f64
1372 }
1373 #[allow(dead_code)]
1374 pub fn success_rate(&self) -> f64 {
1375 if self.total_runs == 0 {
1376 return 0.0;
1377 }
1378 self.successful_runs as f64 / self.total_runs as f64
1379 }
1380 #[allow(dead_code)]
1381 pub fn format_summary(&self) -> String {
1382 format!(
1383 "Runs: {}/{}, Changes: {}, Time: {}ms",
1384 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1385 )
1386 }
1387}
1388#[allow(dead_code)]
1389#[derive(Debug, Clone)]
1390pub struct JSLivenessInfo {
1391 pub live_in: Vec<std::collections::HashSet<u32>>,
1392 pub live_out: Vec<std::collections::HashSet<u32>>,
1393 pub defs: Vec<std::collections::HashSet<u32>>,
1394 pub uses: Vec<std::collections::HashSet<u32>>,
1395}
1396impl JSLivenessInfo {
1397 #[allow(dead_code)]
1398 pub fn new(block_count: usize) -> Self {
1399 JSLivenessInfo {
1400 live_in: vec![std::collections::HashSet::new(); block_count],
1401 live_out: vec![std::collections::HashSet::new(); block_count],
1402 defs: vec![std::collections::HashSet::new(); block_count],
1403 uses: vec![std::collections::HashSet::new(); block_count],
1404 }
1405 }
1406 #[allow(dead_code)]
1407 pub fn add_def(&mut self, block: usize, var: u32) {
1408 if block < self.defs.len() {
1409 self.defs[block].insert(var);
1410 }
1411 }
1412 #[allow(dead_code)]
1413 pub fn add_use(&mut self, block: usize, var: u32) {
1414 if block < self.uses.len() {
1415 self.uses[block].insert(var);
1416 }
1417 }
1418 #[allow(dead_code)]
1419 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1420 self.live_in
1421 .get(block)
1422 .map(|s| s.contains(&var))
1423 .unwrap_or(false)
1424 }
1425 #[allow(dead_code)]
1426 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1427 self.live_out
1428 .get(block)
1429 .map(|s| s.contains(&var))
1430 .unwrap_or(false)
1431 }
1432}
1433#[allow(dead_code)]
1434#[derive(Debug, Clone)]
1435pub struct JSDepGraph {
1436 pub(super) nodes: Vec<u32>,
1437 pub(super) edges: Vec<(u32, u32)>,
1438}
1439impl JSDepGraph {
1440 #[allow(dead_code)]
1441 pub fn new() -> Self {
1442 JSDepGraph {
1443 nodes: Vec::new(),
1444 edges: Vec::new(),
1445 }
1446 }
1447 #[allow(dead_code)]
1448 pub fn add_node(&mut self, id: u32) {
1449 if !self.nodes.contains(&id) {
1450 self.nodes.push(id);
1451 }
1452 }
1453 #[allow(dead_code)]
1454 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1455 self.add_node(dep);
1456 self.add_node(dependent);
1457 self.edges.push((dep, dependent));
1458 }
1459 #[allow(dead_code)]
1460 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1461 self.edges
1462 .iter()
1463 .filter(|(d, _)| *d == node)
1464 .map(|(_, dep)| *dep)
1465 .collect()
1466 }
1467 #[allow(dead_code)]
1468 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1469 self.edges
1470 .iter()
1471 .filter(|(_, dep)| *dep == node)
1472 .map(|(d, _)| *d)
1473 .collect()
1474 }
1475 #[allow(dead_code)]
1476 pub fn topological_sort(&self) -> Vec<u32> {
1477 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1478 for &n in &self.nodes {
1479 in_degree.insert(n, 0);
1480 }
1481 for (_, dep) in &self.edges {
1482 *in_degree.entry(*dep).or_insert(0) += 1;
1483 }
1484 let mut queue: std::collections::VecDeque<u32> = self
1485 .nodes
1486 .iter()
1487 .filter(|&&n| in_degree[&n] == 0)
1488 .copied()
1489 .collect();
1490 let mut result = Vec::new();
1491 while let Some(node) = queue.pop_front() {
1492 result.push(node);
1493 for dep in self.dependents_of(node) {
1494 let cnt = in_degree.entry(dep).or_insert(0);
1495 *cnt = cnt.saturating_sub(1);
1496 if *cnt == 0 {
1497 queue.push_back(dep);
1498 }
1499 }
1500 }
1501 result
1502 }
1503 #[allow(dead_code)]
1504 pub fn has_cycle(&self) -> bool {
1505 self.topological_sort().len() < self.nodes.len()
1506 }
1507}