Skip to content

Commit 377701a

Browse files
Dale-Blackclaude
andcommitted
Compile closures, comparison operators, and type conversions in optimize=false IR
- Resolve closure types from SSAValues in compile_new_expr (%new) - Compile closure bodies with optimize=false for correct high-level operations - Add comparison operators (==, !=, <, <=, >, >=) in SSA callee path - Add type conversion calls (Int32, Float64, etc.) in SSA callee path - Handle dynamic `rev` kwarg in sort (runtime check instead of compile-time) - Resolve isa() type arg from SSAValues; return true for unresolvable types - Suppress _typeof_captured_variable calls (IR artifact, no-op in JS) Enables sort(arr, by=lambda, rev=expr) and filter(fn, arr) to compile correctly without manual JST package registrations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fc20745 commit 377701a

1 file changed

Lines changed: 115 additions & 8 deletions

File tree

src/compiler/codegen.jl

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,19 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
859859
return ""
860860
end
861861

862+
# Type conversions: Int32(x) → (x)|0, Float64(x) → +(x), etc.
863+
if resolved_fn isa Type && length(call_args) == 1
864+
if resolved_fn === Int32 || resolved_fn === Int64 || resolved_fn === UInt32
865+
return "($(call_args[1]))|0"
866+
elseif resolved_fn === Float64 || resolved_fn === Float32
867+
return "+($(call_args[1]))"
868+
elseif resolved_fn === Bool
869+
return "!!($(call_args[1]))"
870+
elseif resolved_fn === String
871+
return "String($(call_args[1]))"
872+
end
873+
end
874+
862875
# Check package registry for positional calls (functions AND type constructors)
863876
if resolved_fn isa Function || resolved_fn isa Type
864877
fn_mod = parentmodule(resolved_fn)
@@ -1018,6 +1031,26 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
10181031
return "($(call_args[1]) / $(call_args[2]))"
10191032
end
10201033

1034+
# Comparison operators (unoptimized IR keeps these as calls)
1035+
if resolved_fn === Base.:(==) && length(call_args) == 2
1036+
return "$(call_args[1]) === $(call_args[2])"
1037+
end
1038+
if resolved_fn === Base.:(!=) && length(call_args) == 2
1039+
return "$(call_args[1]) !== $(call_args[2])"
1040+
end
1041+
if resolved_fn === Base.:(<) && length(call_args) == 2
1042+
return "$(call_args[1]) < $(call_args[2])"
1043+
end
1044+
if resolved_fn === Base.:(<=) && length(call_args) == 2
1045+
return "$(call_args[1]) <= $(call_args[2])"
1046+
end
1047+
if resolved_fn === Base.:(>) && length(call_args) == 2
1048+
return "$(call_args[1]) > $(call_args[2])"
1049+
end
1050+
if resolved_fn === Base.:(>=) && length(call_args) == 2
1051+
return "$(call_args[1]) >= $(call_args[2])"
1052+
end
1053+
10211054
# js() escape hatch
10221055
if fn_name == "js"
10231056
template_str = nothing
@@ -1203,13 +1236,20 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
12031236
arr_js = pos_args[1]
12041237
by_js = get(kwargs, :by, nothing)
12051238
rev = get(kwargs, :rev, nothing)
1206-
is_rev = rev !== nothing && rev != "false"
1239+
# Determine if rev is a static literal or a dynamic expression
1240+
is_static_true = rev == "true"
1241+
is_static_false = rev === nothing || rev == "false"
12071242
if by_js !== nothing
1208-
cmp = is_rev ?
1209-
"(function(a,b){var _a=$(by_js)(a),_b=$(by_js)(b);return _a<_b?1:_a>_b?-1:0})" :
1210-
"(function(a,b){var _a=$(by_js)(a),_b=$(by_js)(b);return _a<_b?-1:_a>_b?1:0})"
1243+
if is_static_true
1244+
cmp = "(function(a,b){var _a=$(by_js)(a),_b=$(by_js)(b);return _a<_b?1:_a>_b?-1:0})"
1245+
elseif is_static_false
1246+
cmp = "(function(a,b){var _a=$(by_js)(a),_b=$(by_js)(b);return _a<_b?-1:_a>_b?1:0})"
1247+
else
1248+
# Dynamic rev: emit comparator that checks rev at runtime
1249+
cmp = "(function(a,b){var _a=$(by_js)(a),_b=$(by_js)(b),_d=_a<_b?-1:_a>_b?1:0;return $(rev)?-_d:_d})"
1250+
end
12111251
return "$(arr_js).slice().sort($(cmp))"
1212-
elseif is_rev
1252+
elseif is_static_true || (!is_static_false && rev !== nothing)
12131253
return "$(arr_js).slice().sort().reverse()"
12141254
else
12151255
return "$(arr_js).slice().sort()"
@@ -1409,9 +1449,23 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
14091449
try getfield(type_arg.mod, type_arg.name) catch; nothing end
14101450
elseif type_arg isa Type || type_arg isa DataType
14111451
type_arg
1452+
elseif type_arg isa Core.SSAValue
1453+
# In optimize=false IR, types may be SSAValues (e.g. apply_type results)
1454+
stype = try ctx.code_info.ssavaluetypes[type_arg.id] catch; nothing end
1455+
if stype isa Core.Const
1456+
stype.val
1457+
elseif stype isa DataType
1458+
stype
1459+
else
1460+
nothing
1461+
end
14121462
else
14131463
nothing
14141464
end
1465+
# Unresolvable type: Julia already type-checks at compile time, so isa is always true
1466+
if T === nothing
1467+
return "true"
1468+
end
14151469
if T === Nothing
14161470
return "$(x_val) === null"
14171471
elseif T === Int32 || T === Int64 || T === UInt32 || T === UInt64 || T === Float64 || T === Float32
@@ -1597,6 +1651,47 @@ function compile_call(ctx::JSCompilationContext, expr::Expr)
15971651
end
15981652
end
15991653

1654+
# Handle Base arithmetic and comparison operators (unoptimized IR)
1655+
if callee isa GlobalRef && callee.mod === Base && length(args) >= 3
1656+
op = callee.name
1657+
a_js = compile_value(ctx, args[2])
1658+
b_js = compile_value(ctx, args[3])
1659+
if op === :(==)
1660+
return "$(a_js) === $(b_js)"
1661+
elseif op === :(!=)
1662+
return "$(a_js) !== $(b_js)"
1663+
elseif op === :(<)
1664+
return "$(a_js) < $(b_js)"
1665+
elseif op === :(<=)
1666+
return "$(a_js) <= $(b_js)"
1667+
elseif op === :(>)
1668+
return "$(a_js) > $(b_js)"
1669+
elseif op === :(>=)
1670+
return "$(a_js) >= $(b_js)"
1671+
elseif op === :(+)
1672+
return "($(a_js) + $(b_js))"
1673+
elseif op === :(-)
1674+
return "($(a_js) - $(b_js))"
1675+
elseif op === :(*)
1676+
return "($(a_js) * $(b_js))"
1677+
elseif op === :(/)
1678+
return "($(a_js) / $(b_js))"
1679+
elseif op === :(%) || op === :rem
1680+
return "($(a_js) % $(b_js))"
1681+
end
1682+
end
1683+
1684+
# Handle unary negation: Base.:(-)(x) → -(x)
1685+
if callee isa GlobalRef && callee.mod === Base && callee.name === :(-) && length(args) == 2
1686+
a_js = compile_value(ctx, args[2])
1687+
return "(-($(a_js)))"
1688+
end
1689+
1690+
# Suppress _typeof_captured_variable — IR artifact, no-op in JS
1691+
if callee isa GlobalRef && callee.name === :_typeof_captured_variable
1692+
return "undefined"
1693+
end
1694+
16001695
# Generic call — will be expanded later
16011696
callee_name = compile_value(ctx, callee)
16021697
call_args = [compile_value(ctx, a) for a in args[2:end]]
@@ -2001,11 +2096,22 @@ function compile_new_expr(ctx::JSCompilationContext, expr::Expr)
20012096
T_ref = expr.args[1]
20022097
field_args = expr.args[2:end]
20032098

2004-
# Resolve type: could be a DataType, GlobalRef, or Argument
2099+
# Resolve type: could be a DataType, GlobalRef, SSAValue, or Argument
20052100
T = if T_ref isa DataType
20062101
T_ref
20072102
elseif T_ref isa GlobalRef
20082103
try getfield(T_ref.mod, T_ref.name) catch; nothing end
2104+
elseif T_ref isa Core.SSAValue
2105+
# In optimize=false IR, closure types may be SSAValues
2106+
# e.g. %5 = Main.:(var"#fn##0#fn##1")::Core.Const(ClosureType)
2107+
ssa_type = try ctx.code_info.ssavaluetypes[T_ref.id] catch; nothing end
2108+
if ssa_type isa Core.Const
2109+
ssa_type.val
2110+
elseif ssa_type isa DataType
2111+
ssa_type
2112+
else
2113+
nothing
2114+
end
20092115
elseif T_ref isa Core.Argument
20102116
# In constructors, Argument(1) is the type — get from arg_types context
20112117
nothing # Can't resolve at compile time from within the constructor
@@ -2059,8 +2165,9 @@ function compile_closure_creation(ctx::JSCompilationContext, T::DataType, captur
20592165
# Get the non-self parameter types from the method signature
20602166
param_types = m.sig.parameters[2:end]
20612167

2062-
# Get typed IR (use code_typed_by_type since code_typed(Method, ...) may return empty)
2063-
ci, rt = Base.code_typed_by_type(Tuple{T, param_types...}; optimize=true)[1]
2168+
# Get typed IR — use optimize=false to preserve high-level operations
2169+
# (e.g. lowercase → .toLowerCase() instead of broken .map(lowercase))
2170+
ci, rt = Base.code_typed_by_type(Tuple{T, param_types...}; optimize=false)[1]
20642171

20652172
# Build argument names for the closure (skip #self# at slot 1)
20662173
nargs = length(param_types)

0 commit comments

Comments
 (0)