|
542 | 542 | """ |
543 | 543 | Compile a :call expression (intrinsics, builtins, generic calls). |
544 | 544 | """ |
| 545 | +# Extract keyword arguments from a NamedTuple SSA in unoptimized IR. |
| 546 | +# The IR pattern is: Core.apply_type(NamedTuple, (:x,:y,:mode)), Core.tuple(vals...), NamedTupleType(tuple) |
| 547 | +# Returns Dict{Symbol, String} mapping kwarg names to compiled JS expressions. |
| 548 | +function _extract_kwargs(ctx::JSCompilationContext, kwargs_ssa) |
| 549 | + kwargs = Dict{Symbol, String}() |
| 550 | + |
| 551 | + if !(kwargs_ssa isa Core.SSAValue) |
| 552 | + return kwargs |
| 553 | + end |
| 554 | + |
| 555 | + # The kwargs SSA points to a NamedTuple constructor call |
| 556 | + nt_stmt = ctx.code_info.code[kwargs_ssa.id] |
| 557 | + |
| 558 | + # Handle (:=) wrapping |
| 559 | + if nt_stmt isa Expr && nt_stmt.head === :(=) |
| 560 | + nt_stmt = nt_stmt.args[2] |
| 561 | + end |
| 562 | + |
| 563 | + # Pattern: (%apply_type_result)(%tuple_of_values) |
| 564 | + # Where apply_type_result = Core.apply_type(NamedTuple, (:x, :y, :mode)) |
| 565 | + if !(nt_stmt isa Expr && nt_stmt.head === :call) |
| 566 | + return kwargs |
| 567 | + end |
| 568 | + |
| 569 | + # Get the NamedTuple type (has field names) from the callee |
| 570 | + type_ssa = nt_stmt.args[1] |
| 571 | + values_ssa = length(nt_stmt.args) >= 2 ? nt_stmt.args[2] : nothing |
| 572 | + |
| 573 | + # Resolve field names from the type |
| 574 | + field_names = Symbol[] |
| 575 | + if type_ssa isa Core.SSAValue |
| 576 | + type_type = ctx.code_info.ssavaluetypes[type_ssa.id] |
| 577 | + if type_type isa Core.Const && type_type.val isa DataType |
| 578 | + nt_type = type_type.val |
| 579 | + if nt_type <: NamedTuple |
| 580 | + field_names = collect(nt_type.parameters[1]) |
| 581 | + end |
| 582 | + end |
| 583 | + end |
| 584 | + |
| 585 | + if isempty(field_names) || values_ssa === nothing |
| 586 | + return kwargs |
| 587 | + end |
| 588 | + |
| 589 | + # Resolve values from the tuple |
| 590 | + if values_ssa isa Core.SSAValue |
| 591 | + vals_stmt = ctx.code_info.code[values_ssa.id] |
| 592 | + if vals_stmt isa Expr && vals_stmt.head === :call |
| 593 | + vals_callee = vals_stmt.args[1] |
| 594 | + if vals_callee isa GlobalRef && vals_callee.name === :tuple |
| 595 | + vals = vals_stmt.args[2:end] |
| 596 | + for (i, name) in enumerate(field_names) |
| 597 | + if i <= length(vals) |
| 598 | + kwargs[name] = compile_value(ctx, vals[i]) |
| 599 | + end |
| 600 | + end |
| 601 | + end |
| 602 | + end |
| 603 | + end |
| 604 | + |
| 605 | + return kwargs |
| 606 | +end |
| 607 | + |
545 | 608 | # Compile Base.materialize(broadcasted_ssa) to JS .map() chains. |
546 | 609 | # sin.(x) -> x.map(_b => Math.sin(_b)), x.*f -> x.map(_b => _b*f), etc. |
547 | 610 | function _compile_broadcast_materialize(ctx::JSCompilationContext, bc_arg) |
@@ -716,6 +779,16 @@ function compile_call(ctx::JSCompilationContext, expr::Expr) |
716 | 779 | fn_name = string(nameof(resolved_fn)) |
717 | 780 | call_args = [compile_value(ctx, a) for a in args[2:end]] |
718 | 781 |
|
| 782 | + # Check package registry for positional calls |
| 783 | + if resolved_fn isa Function |
| 784 | + fn_mod = parentmodule(resolved_fn) |
| 785 | + fn_sym = nameof(resolved_fn) |
| 786 | + compiler_fn = lookup_package_compilation(fn_mod, fn_sym) |
| 787 | + if compiler_fn !== nothing |
| 788 | + return compiler_fn(ctx, Dict{Symbol,String}(), call_args) |
| 789 | + end |
| 790 | + end |
| 791 | + |
719 | 792 | # Array creation: Float64[] → getindex(Float64) → [] |
720 | 793 | # Also handles array indexing: arr[i] → arr[i-1] |
721 | 794 | if resolved_fn === Base.getindex |
@@ -848,6 +921,49 @@ function compile_call(ctx::JSCompilationContext, expr::Expr) |
848 | 921 | end |
849 | 922 | end |
850 | 923 |
|
| 924 | + # ─── Handle Core.kwcall (keyword argument function calls) ─── |
| 925 | + # IR: Core.kwcall(NamedTuple{(:x,:y,:mode)}(vals...), func, pos_args...) |
| 926 | + # Check package registry for the target function |
| 927 | + if callee isa typeof(Core.kwcall) || (callee isa GlobalRef && callee.name === :kwcall && callee.mod === Core) |
| 928 | + if length(args) >= 3 |
| 929 | + # args[2] = NamedTuple with kwargs, args[3] = function, args[4:end] = positional |
| 930 | + kwargs_ssa = args[2] |
| 931 | + func_ssa = args[3] |
| 932 | + pos_raw = args[4:end] |
| 933 | + |
| 934 | + # Resolve the function being called |
| 935 | + func_type = nothing |
| 936 | + if func_ssa isa Core.SSAValue |
| 937 | + func_type = ctx.code_info.ssavaluetypes[func_ssa.id] |
| 938 | + elseif func_ssa isa GlobalRef |
| 939 | + func_type = try Core.Const(getfield(func_ssa.mod, func_ssa.name)) catch; nothing end |
| 940 | + end |
| 941 | + |
| 942 | + if func_type isa Core.Const |
| 943 | + fn = func_type.val |
| 944 | + fn_mod = parentmodule(fn) |
| 945 | + fn_name = nameof(fn) |
| 946 | + |
| 947 | + # Check package registry |
| 948 | + compiler_fn = lookup_package_compilation(fn_mod, fn_name) |
| 949 | + if compiler_fn !== nothing |
| 950 | + # Extract kwargs from NamedTuple construction |
| 951 | + kwargs = _extract_kwargs(ctx, kwargs_ssa) |
| 952 | + pos_args = [compile_value(ctx, a) for a in pos_raw] |
| 953 | + return compiler_fn(ctx, kwargs, pos_args) |
| 954 | + end |
| 955 | + end |
| 956 | + end |
| 957 | + # Fallback: compile as regular call (strip kwargs) |
| 958 | + if length(args) >= 3 |
| 959 | + func_js = compile_value(ctx, args[3]) |
| 960 | + pos_args = [compile_value(ctx, a) for a in args[4:end]] |
| 961 | + kwargs = _extract_kwargs(ctx, args[2]) |
| 962 | + all_args = vcat(pos_args, ["$(k)=$(v)" for (k, v) in kwargs]) |
| 963 | + return "$(func_js)($(join(all_args, ", ")))" |
| 964 | + end |
| 965 | + end |
| 966 | + |
851 | 967 | # Check for Core.Intrinsics — may be referenced via Base.add_int etc. |
852 | 968 | if callee isa GlobalRef |
853 | 969 | resolved = try |
|
0 commit comments