1use std::collections::BTreeSet;
26
27use wasm_encoder::{BlockType, ConstExpr, GlobalSection, GlobalType, Instruction, ValType};
28
29use crate::codegen::CodegenCtx;
30use crate::codegen::function_emitter::FunctionEmitter;
31use crate::codegen::symbol_table::SymbolTable;
32use crate::compiler_error::CompilerFailure;
33use crate::{MangledName, StmtId, TypedAst, TypedExprKind, TypedStmtKind};
34
35#[derive(Clone, Debug, PartialEq)]
37pub struct InitGuard {
38 pub global: MangledName,
39 pub binding: String,
42 pub declared_by: StmtId,
43}
44
45#[derive(Clone, Debug, PartialEq)]
47pub struct ClassGuard {
48 pub name: String,
49 pub uses: Vec<MangledName>,
52 pub declared_at: usize,
55}
56
57#[derive(Clone, Debug, PartialEq)]
59pub struct InitFlag {
60 pub flag_idx: u32,
61 pub declared_by: Option<StmtId>,
64 pub message: String,
65}
66
67pub fn guarded_globals(ta: &TypedAst) -> Result<Vec<InitGuard>, CompilerFailure> {
69 let declared: BTreeSet<&MangledName> = ta.globals.iter().map(|g| &g.mangled_name).collect();
70 let mut seen = BTreeSet::new();
71 let mut code_may_have_run = false;
72 let mut guards = Vec::new();
73 for &stmt_id in &ta.top_level_statements {
74 let stmt = ta
75 .try_stmt(stmt_id)
76 .map_err(crate::codegen::arena_failure)?;
77 code_may_have_run |= may_run_code(ta, stmt_id)?;
79 let TypedStmtKind::AssignGlobal { ident, mangled, .. } = &stmt.kind else {
80 continue;
81 };
82 let is_first_declaration = declared.contains(mangled) && seen.insert(mangled);
85 if !is_first_declaration || !code_may_have_run {
86 continue;
87 }
88 guards.push(InitGuard {
89 global: mangled.clone(),
90 binding: reported_binding(&ident.name).to_string(),
91 declared_by: stmt_id,
92 });
93 }
94 Ok(guards)
95}
96
97pub fn guarded_classes(ta: &TypedAst) -> Result<Vec<ClassGuard>, CompilerFailure> {
99 let mut code_may_have_run = Vec::with_capacity(ta.top_level_statements.len() + 1);
100 code_may_have_run.push(false);
101 for &stmt_id in &ta.top_level_statements {
102 let before = code_may_have_run.last().copied().unwrap_or(false);
103 code_may_have_run.push(before || may_run_code(ta, stmt_id)?);
104 }
105 let mut guards = Vec::new();
106 for decl in &ta.types {
107 let crate::TypedTypeDecl::Class(class) = decl else {
108 continue;
109 };
110 let Some(&declared_at) = ta.class_declaration_points.get(&class.mangled_name) else {
111 continue;
112 };
113 if !code_may_have_run.get(declared_at).copied().unwrap_or(false) {
114 continue;
115 }
116 let mangled = &class.mangled_name;
117 let mut uses = vec![
118 mangled.clone(),
119 crate::mangle::extend(mangled, "constructor"),
120 ];
121 uses.extend(static_method_names(class));
122 guards.push(ClassGuard {
123 name: class.name.name.clone(),
124 uses,
125 declared_at,
126 });
127 }
128 Ok(guards)
129}
130
131fn may_run_code(ta: &TypedAst, stmt_id: StmtId) -> Result<bool, CompilerFailure> {
133 let stmt = ta
134 .try_stmt(stmt_id)
135 .map_err(crate::codegen::arena_failure)?;
136 match &stmt.kind {
137 TypedStmtKind::AssignGlobal { value, .. } => Ok(!is_inert(ta, *value)?),
138 _ => Ok(true),
139 }
140}
141
142fn reported_binding(global_name: &str) -> &str {
145 global_name
146 .split_once('.')
147 .map_or(global_name, |(class, _)| class)
148}
149
150pub fn before_initialization_message(binding: &str) -> String {
152 format!("Cannot access '{binding}' before initialization")
153}
154
155pub fn allocate_flags(
158 ta: &TypedAst,
159 globals: &mut GlobalSection,
160 symbols: &mut SymbolTable,
161 next_global_idx: &mut u32,
162) -> Result<u32, CompilerFailure> {
163 let guards = guarded_globals(ta)?;
164 for guard in &guards {
165 let flag_idx = allocate_flag(globals, next_global_idx)?;
166 symbols.record_init_guard(
167 guard.global.clone(),
168 InitFlag {
169 flag_idx,
170 declared_by: Some(guard.declared_by),
171 message: before_initialization_message(&guard.binding),
172 },
173 );
174 }
175 let classes = guarded_classes(ta)?;
176 for class in &classes {
177 let flag_idx = allocate_flag(globals, next_global_idx)?;
178 for used in &class.uses {
179 symbols.record_init_guard(
180 used.clone(),
181 InitFlag {
182 flag_idx,
183 declared_by: None,
184 message: before_initialization_message(&class.name),
185 },
186 );
187 }
188 symbols.record_class_declaration_flag(class.declared_at, flag_idx);
189 }
190 crate::codegen::wasm_u32(guards.len() + classes.len())
191}
192
193fn allocate_flag(
194 globals: &mut GlobalSection,
195 next_global_idx: &mut u32,
196) -> Result<u32, CompilerFailure> {
197 globals.global(
198 GlobalType {
199 val_type: ValType::I32,
200 mutable: true,
201 shared: false,
202 },
203 &ConstExpr::i32_const(0),
204 );
205 let flag_idx = *next_global_idx;
206 crate::codegen::next_index(next_global_idx)?;
207 Ok(flag_idx)
208}
209
210pub(crate) fn emit_check(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, global: &MangledName) {
213 let Some(flag) = ctx.symbols.init_guard(global) else {
214 return;
215 };
216 emitter.instruction(Instruction::GlobalGet(flag.flag_idx));
217 emitter.instruction(Instruction::I32Eqz);
218 emitter.emit_if(BlockType::Empty);
219 crate::codegen::throw::emit_reference_error_throw(emitter, ctx, &flag.message);
220 emitter.emit_end();
221}
222
223pub(crate) fn is_declaration(ctx: &CodegenCtx, global: &MangledName, stmt: StmtId) -> bool {
226 ctx.symbols
227 .init_guard(global)
228 .is_some_and(|flag| flag.declared_by == Some(stmt))
229}
230
231pub(crate) fn emit_mark_classes_declared(
233 emitter: &mut FunctionEmitter,
234 ctx: &CodegenCtx,
235 index: usize,
236) {
237 for &flag_idx in ctx.symbols.class_declaration_flags(index) {
238 emitter.instruction(Instruction::I32Const(1));
239 emitter.instruction(Instruction::GlobalSet(flag_idx));
240 }
241}
242
243pub(crate) fn emit_mark_initialized(
244 emitter: &mut FunctionEmitter,
245 ctx: &CodegenCtx,
246 global: &MangledName,
247) {
248 let Some(flag) = ctx.symbols.init_guard(global) else {
249 return;
250 };
251 emitter.instruction(Instruction::I32Const(1));
252 emitter.instruction(Instruction::GlobalSet(flag.flag_idx));
253}
254
255fn is_inert(ta: &TypedAst, expr: crate::ExprId) -> Result<bool, CompilerFailure> {
258 let expr = ta.try_expr(expr).map_err(crate::codegen::arena_failure)?;
259 Ok(match &expr.kind {
260 TypedExprKind::Number(_)
261 | TypedExprKind::BigInt(_)
262 | TypedExprKind::String(_)
263 | TypedExprKind::Boolean(_)
264 | TypedExprKind::Null => true,
265 TypedExprKind::FunctionRef { mangled, .. } => !is_class_static_method(ta, mangled),
267 TypedExprKind::Unary { operand, .. } => is_primitive_literal(ta, *operand)?,
269 TypedExprKind::Binary { lhs, rhs, .. } => {
270 is_primitive_literal(ta, *lhs)? && is_primitive_literal(ta, *rhs)?
271 }
272 TypedExprKind::TupleLiteral { elements, .. } => all_inert(ta, elements.iter().copied())?,
273 TypedExprKind::ArrayLiteral { elements, .. } => {
274 let mut values = Vec::with_capacity(elements.len());
275 for element in elements {
276 let crate::TypedArrayElement::Value(id) = element else {
277 return Ok(false);
278 };
279 values.push(*id);
280 }
281 all_inert(ta, values.into_iter())?
282 }
283 _ => false,
284 })
285}
286
287fn is_class_static_method(ta: &TypedAst, mangled: &MangledName) -> bool {
288 ta.types.iter().any(|decl| {
289 let crate::TypedTypeDecl::Class(class) = decl else {
290 return false;
291 };
292 static_method_names(class).any(|method| method == *mangled)
293 })
294}
295
296fn static_method_names(class: &crate::TypedClassDecl) -> impl Iterator<Item = MangledName> + '_ {
297 class
298 .static_methods
299 .keys()
300 .map(|method| crate::mangle::static_member(&class.mangled_name, method))
301}
302
303fn is_primitive_literal(ta: &TypedAst, expr: crate::ExprId) -> Result<bool, CompilerFailure> {
304 let expr = ta.try_expr(expr).map_err(crate::codegen::arena_failure)?;
305 Ok(matches!(
306 expr.kind,
307 TypedExprKind::Number(_)
308 | TypedExprKind::BigInt(_)
309 | TypedExprKind::String(_)
310 | TypedExprKind::Boolean(_)
311 ))
312}
313
314fn all_inert(
315 ta: &TypedAst,
316 mut exprs: impl Iterator<Item = crate::ExprId>,
317) -> Result<bool, CompilerFailure> {
318 exprs.try_fold(true, |inert, id| Ok(inert && is_inert(ta, id)?))
319}