From 7bc6a0358fcfa739be0fe16d6424d70c967cfa28 Mon Sep 17 00:00:00 2001 From: Em Chu Date: Wed, 26 Nov 2025 12:15:19 -0800 Subject: [PATCH] [JuliaLowering] `ccall((lib,sym)...)` and `cfunction` fixes - `ccall` with a `lib,sym` tuple argument was being desugared to a call to `Core.Tuple` when we actually want an `Expr(:tuple)` - `cfunction` only worked in simple cases, since the check for local variables in scope resolution will fail with any nontrivial function body. I know that using the new `static_eval` head could be considered a bugfix (see discussion at https://github.com/JuliaLang/JuliaLowering.jl/pull/36), but this PR just uses `inert` to match Base. stdlib status: 38/51 --- JuliaLowering/src/desugaring.jl | 7 ++--- JuliaLowering/src/eval.jl | 16 ++++++---- JuliaLowering/src/syntax_macros.jl | 8 ++--- JuliaLowering/test/function_calls_ir.jl | 4 +-- JuliaLowering/test/misc.jl | 42 +++++++++++++++++++++++-- JuliaLowering/test/misc_ir.jl | 2 +- 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/JuliaLowering/src/desugaring.jl b/JuliaLowering/src/desugaring.jl index 9c77473830335..0f59629aecda5 100644 --- a/JuliaLowering/src/desugaring.jl +++ b/JuliaLowering/src/desugaring.jl @@ -1724,13 +1724,12 @@ end # Expand the (sym,lib) argument to ccall/cglobal function expand_C_library_symbol(ctx, ex) - expanded = expand_forms_2(ctx, ex) if kind(ex) == K"tuple" - expanded = @ast ctx ex [K"static_eval"(meta=name_hint("function name and library expression")) - expanded + return @ast ctx ex [K"static_eval"(meta=name_hint("function name and library expression")) + mapchildren(e->expand_forms_2(ctx,e), ctx, ex) ] end - return expanded + return expand_forms_2(ctx, ex) end function expand_ccall(ctx, ex) diff --git a/JuliaLowering/src/eval.jl b/JuliaLowering/src/eval.jl index 933f4c31f385f..4d544ca783267 100644 --- a/JuliaLowering/src/eval.jl +++ b/JuliaLowering/src/eval.jl @@ -412,13 +412,16 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int) Expr(:meta, args...) elseif k == K"static_eval" @assert numchildren(ex) == 1 - _to_lowered_expr(ex[1], stmt_offset) - elseif k == K"cfunction" - args = Any[_to_lowered_expr(e, stmt_offset) for e in children(ex)] - if kind(ex[2]) == K"static_eval" - args[2] = QuoteNode(args[2]) + if kind(ex[1]) === K"tuple" + # Should just be ccall library spec + @assert numchildren(ex[1]) === 2 + Expr(:tuple, _to_lowered_expr(ex[1][1], stmt_offset), + _to_lowered_expr(ex[1][2], stmt_offset)) + elseif kind(ex[1]) === K"function" + QuoteNode(Expr(ex)) + else + _to_lowered_expr(ex[1], stmt_offset) end - Expr(:cfunction, args...) else # Allowed forms according to https://docs.julialang.org/en/v1/devdocs/ast/ # @@ -438,6 +441,7 @@ function _to_lowered_expr(ex::SyntaxTree, stmt_offset::Int) k == K"gc_preserve_begin" ? :gc_preserve_begin : k == K"gc_preserve_end" ? :gc_preserve_end : k == K"foreigncall" ? :foreigncall : + k == K"cfunction" ? :cfunction : k == K"new_opaque_closure" ? :new_opaque_closure : nothing if isnothing(head) diff --git a/JuliaLowering/src/syntax_macros.jl b/JuliaLowering/src/syntax_macros.jl index cd6599a3fbbb8..fb96f643cb9ed 100644 --- a/JuliaLowering/src/syntax_macros.jl +++ b/JuliaLowering/src/syntax_macros.jl @@ -90,10 +90,10 @@ function Base.var"@cfunction"(__context__::MacroContext, callable, return_type, typ = Base.CFunction else # Kinda weird semantics here - without `$`, the callable is a top level - # expression which will be evaluated by `jl_resolve_globals_in_ir`, - # implicitly within the module where the `@cfunction` is expanded into. - fptr = @ast __context__ callable [K"static_eval"( - meta=name_hint("cfunction function name")) + # expression evaluated within the module where the `@cfunction` is + # expanded into. + fptr = @ast __context__ callable [K"inert"( + meta=CompileHints(:as_Expr, true)) callable ] typ = Ptr{Cvoid} diff --git a/JuliaLowering/test/function_calls_ir.jl b/JuliaLowering/test/function_calls_ir.jl index 1426ed228ddc8..3cd8ed7b11d7d 100644 --- a/JuliaLowering/test/function_calls_ir.jl +++ b/JuliaLowering/test/function_calls_ir.jl @@ -370,7 +370,7 @@ ccall((:strlen, libc), Csize_t, (Cstring,), "asdfg") 1 TestMod.Cstring 2 (call top.cconvert %₁ "asdfg") 3 (call top.unsafe_convert %₁ %₂) -4 (foreigncall (static_eval (call core.tuple :strlen TestMod.libc)) (static_eval TestMod.Csize_t) (static_eval (call core.svec TestMod.Cstring)) 0 :ccall %₃ %₂) +4 (foreigncall (static_eval (tuple-p :strlen TestMod.libc)) (static_eval TestMod.Csize_t) (static_eval (call core.svec TestMod.Cstring)) 0 :ccall %₃ %₂) 5 (return %₄) ######################################## @@ -521,7 +521,7 @@ ccall(:foo, Csize_t, (Cstring..., Cstring...), "asdfg", "blah") cglobal((:sym, lib), Int) #--------------------- 1 TestMod.Int -2 (call core.cglobal (static_eval (call core.tuple :sym TestMod.lib)) %₁) +2 (call core.cglobal (static_eval (tuple-p :sym TestMod.lib)) %₁) 3 (return %₂) ######################################## diff --git a/JuliaLowering/test/misc.jl b/JuliaLowering/test/misc.jl index 75bb26e9a6f92..273b338630a8e 100644 --- a/JuliaLowering/test/misc.jl +++ b/JuliaLowering/test/misc.jl @@ -69,6 +69,15 @@ function cvarargs_2(arg1::Float64, arg2::Float64) end """) isa Function @test test_mod.cvarargs_2(1.1, 2.2) == "1.1 2.2" +# (function, library) syntax +@test JuliaLowering.include_string(test_mod, """ + ccall((:ctest, :libccalltest), Complex{Int}, (Complex{Int},), 10 + 20im) +""") === 11 + 18im +# (function, library): library is a global +@eval test_mod libccalltest_var = "libccalltest" +@test JuliaLowering.include_string(test_mod, """ + ccall((:ctest, libccalltest_var), Complex{Int}, (Complex{Int},), 10 + 20im) +""") === 11 + 18im # cfunction JuliaLowering.include_string(test_mod, """ @@ -85,15 +94,14 @@ cf_float = JuliaLowering.include_string(test_mod, """ """) @test @ccall($cf_float(2::Float64, 3::Float64)::Float64) == 32.0 -# Test that hygiene works with @ccallable function names (this is broken in -# Base) +# Test that hygiene works with @ccallable function names JuliaLowering.include_string(test_mod, raw""" f_ccallable_hygiene() = 1 module Nested f_ccallable_hygiene() = 2 macro cfunction_hygiene() - :(@cfunction(f_ccallable_hygiene, Int, ())) + :(@cfunction($f_ccallable_hygiene, Int, ())) end end """) @@ -101,6 +109,34 @@ cf_hygiene = JuliaLowering.include_string(test_mod, """ Nested.@cfunction_hygiene """) @test @ccall($cf_hygiene()::Int) == 2 +# Same as above, but non-interpolated symbol. Arguably this could return 20, +# but if it should, this is a bug in the macro implementation, not lowering. +# Match Base for now. +JuliaLowering.include_string(test_mod, raw""" +f_ccallable_hygiene() = 10 + +module Nested + f_ccallable_hygiene() = 20 + macro cfunction_hygiene() + :(@cfunction(f_ccallable_hygiene, Int, ())) + end +end +""") +cf_hygiene = JuliaLowering.include_string(test_mod, """ +Nested.@cfunction_hygiene +""") +@test @ccall($cf_hygiene()::Int) == 10 + +# quoted function in cfunction +quoted_cfn_anon = JuliaLowering.include_string(test_mod, raw""" + @cfunction((function(x); x; end), Int, (Int,)) +""") +@test ccall(quoted_cfn_anon, Int, (Int,), 1) == 1 + +quoted_cfn_named = JuliaLowering.include_string(test_mod, raw""" + @cfunction((function fname_unused(x); x; end), Int, (Int,)) +""") +@test ccall(quoted_cfn_named, Int, (Int,), 1) == 1 # Test that ccall can be passed static parameters in type signatures. # diff --git a/JuliaLowering/test/misc_ir.jl b/JuliaLowering/test/misc_ir.jl index 0b9ab2ee7e78c..dd5811ffd31c7 100644 --- a/JuliaLowering/test/misc_ir.jl +++ b/JuliaLowering/test/misc_ir.jl @@ -355,7 +355,7 @@ JuxtuposeTest.@emit_juxtupose # @cfunction expansion with global generic function as function argument @cfunction(callable, Int, (Int, Float64)) #--------------------- -1 (cfunction Ptr{Nothing} (static_eval TestMod.callable) (static_eval TestMod.Int) (static_eval (call core.svec TestMod.Int TestMod.Float64)) :ccall) +1 (cfunction Ptr{Nothing} (inert callable) (static_eval TestMod.Int) (static_eval (call core.svec TestMod.Int TestMod.Float64)) :ccall) 2 (return %₁) ########################################