@@ -70,6 +70,27 @@ function compile_function(ctx::JSCompilationContext)
7070 print (buf, " let $(ctx. js_locals[idx]) ;\n " )
7171 end
7272
73+ # Declare slot variables (used in optimize=false IR for local variables)
74+ if hasproperty (ctx. code_info, :slotnames ) && ctx. code_info. slotnames != = nothing
75+ slot_declared = Set {String} ()
76+ for i in 2 : length (ctx. code_info. slotnames) # Skip slot 1 (#self#)
77+ name = string (ctx. code_info. slotnames[i])
78+ if startswith (name, " #" ) || startswith (name, " @" ) || isempty (name)
79+ name = " _tmp$(i) "
80+ end
81+ # Skip argument names (already declared as parameters)
82+ if name in ctx. arg_names || name in slot_declared
83+ continue
84+ end
85+ # Only declare slots that are actually assigned in the code
86+ is_assigned = any (s -> s isa Expr && s. head == :(= ) && s. args[1 ] isa Core. SlotNumber && s. args[1 ]. id == i, ctx. code_info. code)
87+ if is_assigned
88+ push! (slot_declared, name)
89+ print (buf, " let $(name) ;\n " )
90+ end
91+ end
92+ end
93+
7394 # Emit the code body
7495 emit_structured! (ctx, buf, code, 1 , n, loop_headers, backward_edges, forward_gotos, phi_info, 1 )
7596
@@ -479,6 +500,26 @@ function compile_expr_stmt!(ctx, buf, idx, expr::Expr, indent::String)
479500 end
480501 elseif expr. head === :leave || expr. head === :pop_exception
481502 # Exception frame management: no-op in JS (try/catch handles this)
503+ elseif expr. head === :(= )
504+ # Slot assignment (optimize=false IR): _var = expr
505+ lhs = expr. args[1 ]
506+ rhs = expr. args[2 ]
507+ lhs_name = compile_value (ctx, lhs)
508+ # RHS may be an Expr (call, invoke, etc.) or a plain value
509+ rhs_val = if rhs isa Expr
510+ if rhs. head === :call
511+ compile_call (ctx, rhs)
512+ elseif rhs. head === :invoke
513+ compile_invoke (ctx, rhs)
514+ elseif rhs. head === :new
515+ compile_new_expr (ctx, rhs)
516+ else
517+ compile_value (ctx, rhs)
518+ end
519+ else
520+ compile_value (ctx, rhs)
521+ end
522+ print (buf, " $(indent)$(lhs_name) = $(rhs_val) ;\n " )
482523 end
483524end
484525
@@ -489,6 +530,148 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
489530 args = expr. args
490531 callee = args[1 ]
491532
533+ # ─── Resolve SSA callees (from unoptimized IR) ───
534+ # In optimize=false IR, calls like push!(x, val) appear as (%20)(%21, %22)
535+ # where %20 is an SSA holding Core.Const(push!). Resolve and dispatch.
536+ if callee isa Core. SSAValue
537+ callee_type = ctx. code_info. ssavaluetypes[callee. id]
538+ if callee_type isa Core. Const
539+ resolved_fn = callee_type. val
540+ fn_name = string (nameof (resolved_fn))
541+ call_args = [compile_value (ctx, a) for a in args[2 : end ]]
542+
543+ # Array creation: Float64[] → getindex(Float64) → []
544+ # Also handles array indexing: arr[i] → arr[i-1]
545+ if resolved_fn === Base. getindex
546+ # Check if first arg is a Type (array construction)
547+ if length (args) >= 2
548+ first_arg = args[2 ]
549+ first_type = nothing
550+ if first_arg isa Core. SSAValue
551+ first_type = try ctx. code_info. ssavaluetypes[first_arg. id] catch ; nothing end
552+ elseif first_arg isa GlobalRef
553+ first_type = try Core. Const (getfield (first_arg. mod, first_arg. name)) catch ; nothing end
554+ end
555+ if first_type isa Core. Const && first_type. val isa DataType
556+ return " []"
557+ end
558+ end
559+ # Array indexing: arr[i] → arr[i-1]
560+ if length (call_args) == 2
561+ return " $(call_args[1 ]) [($(call_args[2 ]) ) - 1]"
562+ end
563+ return " []"
564+ end
565+
566+ # push!(arr, val) → arr.push(val)
567+ if resolved_fn === Base. push! && length (call_args) >= 2
568+ return " $(call_args[1 ]) .push($(call_args[2 ]) )"
569+ end
570+
571+ # length(arr) → arr.length
572+ if resolved_fn === Base. length && length (call_args) >= 1
573+ return " $(call_args[1 ]) .length"
574+ end
575+
576+ # Math functions
577+ if resolved_fn === Base. sin || resolved_fn === sin
578+ return " Math.sin($(call_args[1 ]) )"
579+ end
580+ if resolved_fn === Base. cos || resolved_fn === cos
581+ return " Math.cos($(call_args[1 ]) )"
582+ end
583+ if resolved_fn === Base. sqrt || resolved_fn === sqrt
584+ return " Math.sqrt($(call_args[1 ]) )"
585+ end
586+ if resolved_fn === Base. abs || resolved_fn === abs
587+ return " Math.abs($(call_args[1 ]) )"
588+ end
589+ if resolved_fn === Base. max || resolved_fn === max
590+ return " Math.max($(join (call_args, " , " )) )"
591+ end
592+ if resolved_fn === Base. min || resolved_fn === min
593+ return " Math.min($(join (call_args, " , " )) )"
594+ end
595+
596+ # println → console.log
597+ if resolved_fn === Base. println || resolved_fn === println
598+ require_runtime! (ctx, :jl_println )
599+ return " jl_println($(join (call_args, " , " )) )"
600+ end
601+
602+ # Type constructors: Float64(x) → +(x) (identity for numbers)
603+ if resolved_fn === Float64
604+ return " +($(call_args[1 ]) )"
605+ end
606+ if resolved_fn === Float32
607+ return " Math.fround($(call_args[1 ]) )"
608+ end
609+ if resolved_fn === Int || resolved_fn === Int64
610+ return " (($(call_args[1 ]) ) | 0)"
611+ end
612+
613+ # Colon constructor: (:)(start, stop) → range not needed, handled by iterate
614+ if resolved_fn === Base. Colon ()
615+ # Range creation — this is just (:)(1, n), handled by iterate below
616+ return " ({start: $(call_args[1 ]) , stop: $(call_args[2 ]) })"
617+ end
618+
619+ # iterate(range) and iterate(range, state) for for-loops
620+ if resolved_fn === Base. iterate
621+ if length (call_args) == 1
622+ # iterate(range) → { value: range.start, done: range.start > range.stop }
623+ r = call_args[1 ]
624+ return " ($(r) .start <= $(r) .stop ? [$(r) .start, $(r) .start] : null)"
625+ else
626+ # iterate(range, state) → { value: state+1, done: state+1 > range.stop }
627+ r = call_args[1 ]
628+ s = call_args[2 ]
629+ return " (($(s) + 1) <= $(r) .stop ? [($(s) + 1), ($(s) + 1)] : null)"
630+ end
631+ end
632+
633+ # Multiplication, addition etc (when not inlined as intrinsics)
634+ if resolved_fn === Base.:(* ) && length (call_args) == 2
635+ return " ($(call_args[1 ]) * $(call_args[2 ]) )"
636+ end
637+ if resolved_fn === Base.:(+ ) && length (call_args) == 2
638+ return " ($(call_args[1 ]) + $(call_args[2 ]) )"
639+ end
640+ if resolved_fn === Base.:(- ) && length (call_args) == 2
641+ return " ($(call_args[1 ]) - $(call_args[2 ]) )"
642+ end
643+ if resolved_fn === Base.:(- ) && length (call_args) == 1
644+ return " (-($(call_args[1 ]) ))"
645+ end
646+ if resolved_fn === Base.:(/ ) && length (call_args) == 2
647+ return " ($(call_args[1 ]) / $(call_args[2 ]) )"
648+ end
649+
650+ # js() escape hatch
651+ if fn_name == " js"
652+ template_str = nothing
653+ if length (args) >= 2 && args[2 ] isa String
654+ template_str = args[2 ]
655+ elseif length (call_args) >= 1
656+ s = call_args[1 ]
657+ if length (s) >= 2 && s[1 ] == ' "' && s[end ] == ' "'
658+ template_str = replace (replace (s[2 : end - 1 ], " \\\" " => " \" " ), " \\\\ " => " \\ " )
659+ end
660+ end
661+ if template_str != = nothing
662+ result = template_str
663+ for i in 2 : length (call_args)
664+ result = replace (result, " \$ $(i- 1 ) " => call_args[i])
665+ end
666+ return result
667+ end
668+ end
669+
670+ # Fallback: emit as function call
671+ return " $(fn_name) ($(join (call_args, " , " )) )"
672+ end
673+ end
674+
492675 # Check for Core.Intrinsics — may be referenced via Base.add_int etc.
493676 if callee isa GlobalRef
494677 resolved = try
@@ -540,6 +723,41 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
540723 if bname === :string && callee. mod === Base
541724 return compile_string_concat (ctx, args[2 : end ])
542725 end
726+
727+ # Base.getindex — array creation (Type[]) or array access (arr[i])
728+ if bname === :getindex && callee. mod === Base
729+ # Check if first arg is a Type → empty array creation
730+ if length (args) >= 2
731+ first_arg = args[2 ]
732+ first_type = nothing
733+ if first_arg isa Core. SSAValue
734+ first_type = try ctx. code_info. ssavaluetypes[first_arg. id] catch ; nothing end
735+ elseif first_arg isa GlobalRef
736+ first_type = try Core. Const (getfield (first_arg. mod, first_arg. name)) catch ; nothing end
737+ end
738+ if first_type isa Core. Const && first_type. val isa DataType
739+ return " []"
740+ end
741+ end
742+ # Array indexing
743+ call_args_gr = [compile_value (ctx, a) for a in args[2 : end ]]
744+ if length (call_args_gr) == 2
745+ return " $(call_args_gr[1 ]) [($(call_args_gr[2 ]) ) - 1]"
746+ end
747+ end
748+
749+ # Base.iterate — for-loop iteration
750+ if bname === :iterate && callee. mod === Base
751+ call_args_it = [compile_value (ctx, a) for a in args[2 : end ]]
752+ if length (call_args_it) == 1
753+ r = call_args_it[1 ]
754+ return " ($(r) .start <= $(r) .stop ? [$(r) .start, $(r) .start] : null)"
755+ else
756+ r = call_args_it[1 ]
757+ s = call_args_it[2 ]
758+ return " (($(s) + 1) <= $(r) .stop ? [($(s) + 1), ($(s) + 1)] : null)"
759+ end
760+ end
543761 end
544762
545763 # Handle Core.=== (egal)
@@ -1382,6 +1600,12 @@ function compile_value(ctx::JSCompilationContext, val)
13821600 if stmt isa Core. PiNode
13831601 # PiNode is a type narrowing no-op: pass through the value
13841602 return compile_value (ctx, stmt. val)
1603+ elseif stmt isa Core. SlotNumber
1604+ # Slot read in unoptimized IR — resolve to slot variable name
1605+ return compile_value (ctx, stmt)
1606+ elseif stmt isa GlobalRef
1607+ # Global reference — resolve directly
1608+ return compile_value (ctx, stmt)
13851609 elseif stmt isa Expr && stmt. head === :call
13861610 return compile_call (ctx, stmt)
13871611 elseif stmt isa Expr && stmt. head === :invoke
@@ -1390,9 +1614,38 @@ function compile_value(ctx::JSCompilationContext, val)
13901614 return compile_new_expr (ctx, stmt)
13911615 elseif stmt isa Expr && stmt. head === :boundscheck
13921616 return " false"
1617+ elseif stmt isa Expr && stmt. head === :(= )
1618+ # Slot assignment — the SSA value is the RHS result
1619+ rhs = stmt. args[2 ]
1620+ if rhs isa Expr
1621+ if rhs. head === :call
1622+ return compile_call (ctx, rhs)
1623+ elseif rhs. head === :invoke
1624+ return compile_invoke (ctx, rhs)
1625+ end
1626+ end
1627+ return compile_value (ctx, rhs)
13931628 end
13941629 # Fallback: create a local
13951630 return get_local! (ctx, id)
1631+ elseif val isa Core. SlotNumber
1632+ # Local variable (used in optimize=false IR)
1633+ slot_id = val. id
1634+ if slot_id == 1
1635+ # Slot 1 is #self# (the function)
1636+ return " null"
1637+ end
1638+ # Use slot name from CodeInfo if available
1639+ slot_names = ctx. code_info. slotnames
1640+ if slot_id <= length (slot_names)
1641+ name = string (slot_names[slot_id])
1642+ # Clean up generated names (remove # prefixes, @ etc.)
1643+ if startswith (name, " #" ) || startswith (name, " @" ) || isempty (name)
1644+ return " _tmp$(slot_id) "
1645+ end
1646+ return name
1647+ end
1648+ return " _tmp$(slot_id) "
13961649 elseif val isa Core. Argument
13971650 idx = val. n - 1
13981651 if idx >= 1 && idx <= length (ctx. arg_names)
0 commit comments