From 8441383e4d2a0af629abd8493010647e150e1b75 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 30 Jul 2025 09:13:43 -0400 Subject: [PATCH] ccall: make distinction of pointer vs name a syntatic distinction We have long expected users to be explicit about the library name for `ccall`, and the `@ccall` macro has even always enforced that. That means users should have already been using explicit syntax. And indeed, other syntax forms weren't handled reliably (since doing so would require linearizing IR if and only if the runtime values required it, which is not something that is computable, and thus was often done wrong). This now aligns the runtime and compiler to expect only syntax forms that we could reliably handle before without errors, and adds explicit errors for cases that we previously knew would be unreliable because they relied upon inference making particular decisions for the semantics. The `ccall` function is already very special since it is more like a actual macro (it does not exist as a binding or value), so we can make unusual syntax decisions like this, mirroring `@ccall`. This drops support for https://github.com/JuliaLang/julia/pull/37123, since we were going to use that for LazyLibraries, be we decided that approach was quite buggy and that PR would make static compilation quite impossible to support, so we instead actually implemented LazyLibraries with a different approach. It could be re-enabled, but we never had correct lowering or inference support for it, so it is presumably still unused. The goal is to cause breakage only where the package authors really failed to express intent with syntax, and otherwise to explicitly maintain support by adding cases to normalize the given syntax into one of the supported cases. All existing functionality (and more) can be accessed by explicit management of a pointer or by a LazyLibrary-like type, so this shouldn't cause any reduction in possible functionality, just possibly altered syntax. --- Compiler/src/abstractinterpretation.jl | 66 +-- Compiler/src/optimize.jl | 7 +- Compiler/src/ssair/EscapeAnalysis.jl | 6 +- Compiler/src/ssair/inlining.jl | 2 +- Compiler/src/ssair/verify.jl | 8 +- Compiler/src/verifytrim.jl | 14 +- Compiler/test/inference.jl | 4 +- Compiler/test/irpasses.jl | 2 +- base/c.jl | 34 +- base/deprecated.jl | 3 +- base/gmp.jl | 4 +- doc/src/devdocs/llvm.md | 2 +- doc/src/manual/calling-c-and-fortran-code.md | 69 ++- src/ccall.cpp | 444 +++++++++++-------- src/codegen.cpp | 2 +- src/julia-syntax.scm | 58 ++- src/julia_internal.h | 3 +- src/method.c | 80 ++++ src/runtime_ccall.cpp | 37 +- src/runtime_intrinsics.c | 19 +- test/ccall.jl | 24 +- 21 files changed, 527 insertions(+), 361 deletions(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 4c20483d0367e..f755d8f02dab2 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3480,65 +3480,29 @@ function refine_partial_type(@nospecialize t) return t end -abstract_eval_nonlinearized_foreigncall_name( - ::AbstractInterpreter, @nospecialize(e), ::StatementState, ::IRInterpretationState - ) = nothing - -function abstract_eval_nonlinearized_foreigncall_name( - interp::AbstractInterpreter, @nospecialize(e), sstate::StatementState, sv::InferenceState - ) - if isexpr(e, :call) - n = length(e.args) - argtypes = Vector{Any}(undef, n) - callresult = Future{CallMeta}() - i::Int = 1 - nextstate::UInt8 = 0x0 - local ai, res - function evalargs(interp, sv) - if nextstate === 0x1 - @goto state1 - elseif nextstate === 0x2 - @goto state2 - end - while i <= n - ai = abstract_eval_nonlinearized_foreigncall_name(interp, e.args[i], sstate, sv) - if !isready(ai) - nextstate = 0x1 - return false - @label state1 - end - argtypes[i] = ai[].rt - i += 1 - end - res = abstract_call(interp, ArgInfo(e.args, argtypes), sstate, sv) - if !isready(res) - nextstate = 0x2 - return false - @label state2 - end - callresult[] = res[] - return true - end - evalargs(interp, sv) || push!(sv.tasks, evalargs) - return callresult - else - return Future(abstract_eval_basic_statement(interp, e, sstate, sv)) - end -end - function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) callee = e.args[1] - if isexpr(callee, :call) && length(callee.args) > 1 && callee.args[1] == GlobalRef(Core, :tuple) - # NOTE these expressions are not properly linearized - abstract_eval_nonlinearized_foreigncall_name(interp, callee.args[2], sstate, sv) - if length(callee.args) > 2 - abstract_eval_nonlinearized_foreigncall_name(interp, callee.args[3], sstate, sv) + if isexpr(callee, :tuple) + if length(callee.args) >= 1 + abstract_eval_value(interp, callee.args[1], sstate, sv) + if length(callee.args) >= 2 + abstract_eval_value(interp, callee.args[2], sstate, sv) + #TODO: implement abstract_eval_nonlinearized_foreigncall_name correctly? + # lib_effects = abstract_call(interp, ArgInfo(e.args, Any[typeof(Libdl.dlopen), lib]), sstate, sv)::Future + end end else abstract_eval_value(interp, callee, sstate, sv) end mi = frame_instance(sv) t = sp_type_rewrap(e.args[2], mi, true) + let fptr = e.args[1] + if !isexpr(fptr, :tuple) + if !hasintersect(widenconst(abstract_eval_value(interp, fptr, sstate, sv)), Ptr) + return RTEffects(Bottom, Any, EFFECTS_THROWS) + end + end + end for i = 3:length(e.args) if abstract_eval_value(interp, e.args[i], sstate, sv) === Bottom return RTEffects(Bottom, Any, EFFECTS_THROWS) diff --git a/Compiler/src/optimize.jl b/Compiler/src/optimize.jl index 50d4639325149..3a7eda2634639 100644 --- a/Compiler/src/optimize.jl +++ b/Compiler/src/optimize.jl @@ -1439,8 +1439,11 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return params.inline_nonleaf_penalty elseif head === :foreigncall foreigncall = ex.args[1] - if foreigncall isa QuoteNode && foreigncall.value === :jl_string_ptr - return 1 + if isexpr(foreigncall, :tuple, 1) + foreigncall = foreigncall.args[1] + if foreigncall isa QuoteNode && foreigncall.value === :jl_string_ptr + return 1 + end end return 20 elseif head === :invoke || head === :invoke_modify diff --git a/Compiler/src/ssair/EscapeAnalysis.jl b/Compiler/src/ssair/EscapeAnalysis.jl index 4ce972937700c..d78ec529cbd9f 100644 --- a/Compiler/src/ssair/EscapeAnalysis.jl +++ b/Compiler/src/ssair/EscapeAnalysis.jl @@ -1037,8 +1037,10 @@ function escape_foreigncall!(astate::AnalysisState, pc::Int, args::Vector{Any}) # NOTE array allocations might have been proven as nothrow (https://github.com/JuliaLang/julia/pull/43565) nothrow = is_nothrow(astate.ir, pc) name_info = nothrow ? ⊥ : ThrownEscape(pc) - add_escape_change!(astate, name, name_info) - add_liveness_change!(astate, name, pc) + if !isexpr(name, :tuple) + add_escape_change!(astate, name, name_info) + add_liveness_change!(astate, name, pc) + end for i = 1:nargs # we should escape this argument if it is directly called, # otherwise just impose ThrownEscape if not nothrow diff --git a/Compiler/src/ssair/inlining.jl b/Compiler/src/ssair/inlining.jl index 324b43db3e797..9ef8a993dc818 100644 --- a/Compiler/src/ssair/inlining.jl +++ b/Compiler/src/ssair/inlining.jl @@ -1753,7 +1753,7 @@ function late_inline_special_case!(ir::IRCode, idx::Int, stmt::Expr, flag::UInt3 length(stmt.args) == 2 ? Any : stmt.args[end]) return SomeCase(typevar_call) elseif f === UnionAll && length(argtypes) == 3 && ⊑(optimizer_lattice(state.interp), argtypes[2], TypeVar) - unionall_call = Expr(:foreigncall, QuoteNode(:jl_type_unionall), Any, svec(Any, Any), + unionall_call = Expr(:foreigncall, Expr(:tuple, QuoteNode(:jl_type_unionall)), Any, svec(Any, Any), 0, QuoteNode(:ccall), stmt.args[2], stmt.args[3]) return SomeCase(unionall_call) elseif is_return_type(f) diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index 8c3ce9962bea6..e190ae7a8438f 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -77,10 +77,10 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, end elseif isa(op, Expr) # Only Expr(:boundscheck) is allowed in value position - if isforeigncall && arg_idx == 1 && op.head === :call - # Allow a tuple in symbol position for foreigncall - this isn't actually - # a real call - it's interpreted in global scope by codegen. However, - # we do need to keep this a real use, because it could also be a pointer. + if isforeigncall && arg_idx == 1 && op.head === :tuple + # Allow a tuple literal in symbol position for foreigncall - this + # is syntax for a literal value or globalref - it is interpreted in + # global scope by codegen. elseif !is_value_pos_expr_head(op.head) if !allow_frontend_forms || op.head !== :opaque_closure_method @verify_error "Expr not allowed in value position" diff --git a/Compiler/src/verifytrim.jl b/Compiler/src/verifytrim.jl index dd5f69a18f21e..eef2f15d43e86 100644 --- a/Compiler/src/verifytrim.jl +++ b/Compiler/src/verifytrim.jl @@ -279,9 +279,17 @@ function verify_codeinstance!(interp::NativeInterpreter, codeinst::CodeInstance, error = "unresolved cfunction" elseif isexpr(stmt, :foreigncall) foreigncall = stmt.args[1] - if foreigncall isa QuoteNode - if foreigncall.value in runtime_functions - error = "disallowed ccall into a runtime function" + if isexpr(foreigncall, :tuple, 1) + foreigncall = foreigncall.args[1] + if foreigncall isa String + foreigncall = QuoteNode(Symbol(foreigncall)) + end + if foreigncall isa QuoteNode + if foreigncall.value in runtime_functions + error = "disallowed ccall into a runtime function" + end + else + error = "disallowed ccall with non-constant name and no library" end end elseif isexpr(stmt, :new_opaque_closure) diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index 36b65778b9d90..3ae787e108cf0 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -5219,11 +5219,11 @@ end @testset "#45956: non-linearized cglobal needs special treatment for stmt effects" begin function foo() cglobal((a, )) - ccall(0, Cvoid, (Nothing,), b) + ccall(C_NULL, Cvoid, (Nothing,), b) end @test only(code_typed() do cglobal((a, )) - ccall(0, Cvoid, (Nothing,), b) + ccall(C_NULL, Cvoid, (Nothing,), b) end)[2] === Nothing end diff --git a/Compiler/test/irpasses.jl b/Compiler/test/irpasses.jl index 622652a2142ce..86567440e9fb4 100644 --- a/Compiler/test/irpasses.jl +++ b/Compiler/test/irpasses.jl @@ -1071,7 +1071,7 @@ let # Test for https://github.com/JuliaLang/julia/issues/43402 end refs = map(Core.SSAValue, findall(@nospecialize(x)->Meta.isexpr(x, :new), src.code)) - some_ccall = findfirst(@nospecialize(x) -> Meta.isexpr(x, :foreigncall) && x.args[1] == :(:some_ccall), src.code) + some_ccall = findfirst(@nospecialize(x) -> Meta.isexpr(x, :foreigncall) && x.args[1] == Expr(:tuple, :(:some_ccall)), src.code) @assert some_ccall !== nothing stmt = src.code[some_ccall] nccallargs = length(stmt.args[3]::Core.SimpleVector) diff --git a/base/c.jl b/base/c.jl index 91337f0a242a5..6e9633ccb2301 100644 --- a/base/c.jl +++ b/base/c.jl @@ -313,11 +313,15 @@ function ccall_macro_parse(exprs) # get the function symbols func = let f = call.args[1] if isexpr(f, :.) - :(($(f.args[2]), $(f.args[1]))) + Expr(:tuple, f.args[2], f.args[1]) elseif isexpr(f, :$) - f + func = f.args[1] + if isa(func, String) || (isa(func, QuoteNode) && !isa(func.value, Ptr)) || isa(func, Tuple) || isexpr(func, :tuple) + throw(ArgumentError("interpolated value should be a variable or expression, not a literal name or tuple")) + end + func elseif f isa Symbol - QuoteNode(f) + Expr(:tuple, QuoteNode(f)) else throw(ArgumentError("@ccall function name must be a symbol, a `.` node (e.g. `libc.printf`) or an interpolated function pointer (with `\$`)")) end @@ -363,33 +367,13 @@ end function ccall_macro_lower(convention, func, rettype, types, args, gc_safe, nreq) - statements = [] - - # if interpolation was used, ensure the value is a function pointer at runtime. - if isexpr(func, :$) - push!(statements, Expr(:(=), :func, esc(func.args[1]))) - name = QuoteNode(func.args[1]) - func = :func - check = quote - if !isa(func, Ptr{Cvoid}) - name = $name - throw(ArgumentError(LazyString("interpolated function `", name, "` was not a Ptr{Cvoid}, but ", typeof(func)))) - end - end - push!(statements, check) - else - func = esc(func) - end - cconv = nothing if convention isa Tuple cconv = Expr(:cconv, (convention..., gc_safe), nreq) else cconv = Expr(:cconv, (convention, UInt16(0), gc_safe), nreq) end - - return Expr(:block, statements..., - Expr(:call, :ccall, func, cconv, esc(rettype), - Expr(:tuple, map!(esc, types, types)...), map!(esc, args, args)...)) + return Expr(:call, :ccall, esc(func), cconv, esc(rettype), + Expr(:tuple, map!(esc, types, types)...), map!(esc, args, args)...) end """ diff --git a/base/deprecated.jl b/base/deprecated.jl index 0ee6b6b790837..088c77b3cbabb 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -24,9 +24,10 @@ const __internal_changes_list = ( :invertedlinetables, :codeinforefactor, :miuninferredrm, - :codeinfonargs, # #54341 + :codeinfonargs, #54341 :ocnopartial, :printcodeinfocalls, + :syntacticccall, #59165 # Add new change names above this line ) diff --git a/base/gmp.jl b/base/gmp.jl index b35b8de1d5d12..ee8e620603e9d 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -151,7 +151,7 @@ using ..GMP: BigInt, Limb, BITS_PER_LIMB, libgmp const mpz_t = Ref{BigInt} const bitcnt_t = Culong -gmpz(op::Symbol) = (Symbol(:__gmpz_, op), libgmp) +gmpz(op::Symbol) = Expr(:tuple, QuoteNode(Symbol(:__gmpz_, op)), GlobalRef(MPZ, :libgmp)) init!(x::BigInt) = (ccall((:__gmpz_init, libgmp), Cvoid, (mpz_t,), x); x) init2!(x::BigInt, a) = (ccall((:__gmpz_init2, libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) @@ -955,7 +955,7 @@ module MPQ import .Base: unsafe_rational, __throw_rational_argerror_zero import ..GMP: BigInt, MPZ, Limb, libgmp -gmpq(op::Symbol) = (Symbol(:__gmpq_, op), libgmp) +gmpq(op::Symbol) = Expr(:tuple, QuoteNode(Symbol(:__gmpq_, op)), GlobalRef(MPZ, :libgmp)) mutable struct _MPQ num_alloc::Cint diff --git a/doc/src/devdocs/llvm.md b/doc/src/devdocs/llvm.md index 480bca6fa6ecf..3db57a59deb06 100644 --- a/doc/src/devdocs/llvm.md +++ b/doc/src/devdocs/llvm.md @@ -346,7 +346,7 @@ need to make sure that the array does stay alive while we're doing the [`ccall`](@ref). To understand how this is done, lets look at a hypothetical approximate possible lowering of the above code: ```julia -return $(Expr(:foreigncall, :(:foo), Cvoid, svec(Ptr{Float64}), 0, :(:ccall), Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), :(A)), :(A))) +return $(Expr(:foreigncall, Expr(:tuple, :(:foo)), Cvoid, svec(Ptr{Float64}), 0, :(:ccall), Expr(:foreigncall, Expr(:tuple, :(:jl_array_ptr)), Ptr{Float64}, svec(Any), 0, :(:ccall), :(A)), :(A))) ``` The last `:(A)`, is an extra argument list inserted during lowering that informs the code generator which Julia level values need to be kept alive for the diff --git a/doc/src/manual/calling-c-and-fortran-code.md b/doc/src/manual/calling-c-and-fortran-code.md index bbc3ccdeb6fc0..bf0429fdff3f0 100644 --- a/doc/src/manual/calling-c-and-fortran-code.md +++ b/doc/src/manual/calling-c-and-fortran-code.md @@ -32,11 +32,15 @@ The syntax for [`@ccall`](@ref) to generate a call to the library function is: @ccall $function_pointer(argvalue1::argtype1, ...)::returntype ``` -where `library` is a string constant or literal (but see [Non-constant Function -Specifications](@ref) below). The library may be omitted, in which case the -function name is resolved in the current process. This form can be used to call -C library functions, functions in the Julia runtime, or functions in an -application linked to Julia. The full path to the library may also be specified. +where `library` is a string constant or global variable name (see [Non-constant +Function -Specifications](@ref) below). The library can be just a name, or it +can specify a full path to the library. The library may be omitted, in which +case the function name is resolved in the current executable, the current libc, +or libjulia(-internal). This form can be used to call C library functions, +functions in the Julia runtime, or functions in an application linked to Julia. +Omitting the library *cannot* be used to call a function in any library (like +specifying `RTLD_DEFAULT` to `dlsym`) as such behavior is slow, complicated, +and not implemented on all platforms. Alternatively, `@ccall` may also be used to call a function pointer `$function_pointer`, such as one returned by `Libdl.dlsym`. The `argtype`s corresponds to the C-function signature and the `argvalue`s are the actual @@ -848,13 +852,17 @@ it must be handled in other ways. ## Non-constant Function Specifications -In some cases, the exact name or path of the needed library is not known in advance and must -be computed at run time. To handle such cases, the library component -specification can be a function call, e.g. `find_blas().dgemm`. The call expression will -be executed when the `ccall` itself is executed. However, it is assumed that the library -location does not change once it is determined, so the result of the call can be cached and -reused. Therefore, the number of times the expression executes is unspecified, and returning -different values for multiple calls results in unspecified behavior. +In some cases, the exact name or path of the needed library is not known in +advance and must be computed at run time. To handle such cases, the library +component specification can be a value such as `Libdl.LazyLibrary`. For +example, in `@ccall blas.dgemm()`, there can be a global defined as `const blas += LazyLibrary("libblas")`. The runtime will call `dlsym(:dgemm, dlopen(blas))` +when the `@ccall` itself is executed. The `Libdl.dlopen` function can be +overloaded for custom types to provide alternate behaviors. However, it is +assumed that the library location does not change once it is determined, so the +result of the call can be cached and reused. Therefore, the number of times the +expression executes is unspecified, and returning different values for multiple +calls results in unspecified behavior. If even more flexibility is needed, it is possible to use computed values as function names by staging through [`eval`](@ref) as follows: @@ -990,11 +998,38 @@ The arguments to [`ccall`](@ref) are: !!! note - The `(:function, "library")` pair, return type, and input types must be literal constants - (i.e., they can't be variables, but see [Non-constant Function Specifications](@ref)). - - The remaining parameters are evaluated at compile-time, when the containing method is defined. - + The `(:function, "library")` pair and the input type list must be syntactic tuples + (i.e., they can't be variables or values with a type of Tuple. + + The rettype and argument type values are evaluated at when the containing method is + defined, not runtime. + +!!! note "Function Name vs Pointer Syntax" + The syntax of the first argument to `ccall` determines whether you're calling by **name** or by **pointer**: + * **Name-based calls** (tuple literal syntax): + - Both the function and library names can be a quoted Symbol, a String, a + variable name (a GlobalRef), or a dotted expression ending with a variable + name. + - Single name: `(:function_name,)` or `"function_name"` - uses default library lookup. + - Name with library: `(:function_name, "library")` - specifies both function and library. + - Symbol, string, and tuple literal constants (not expressions that evaluate to those constants, + but actual literals) are automatically normalized to tuple form. + * **Pointer-based calls** (non-tuple syntax): + - Anything that is not a literal tuple expression specified above is assumed to be an + expression that evaluates to a function pointers at runtime. + - Function pointer variables: `fptr` where `fptr` is a runtime pointer value. + - Function pointer computations: `dlsym(:something)` where the result is computed at + runtime every time (usually along with some caching logic). + * **Library name expressions**: + - When given as a variable, the library name can resolve to a `Symbol`, a `String`, or + any other value. The runtime will call `Libdl.dlopen(name)` on the value an + unspecified number of times, caching the result. The result is not invalidated if the + value of the binding changes or if it becomes undefined, as long as there exists any + value for that binding in any past or future worlds, that value may be used. + - Dot expressions, such as `A.B().c`, will be executed at method definition + time up to the final `c`. The first part must resolve to a Module, and the + second part to a quoted symbol. The value of that global will be resolved at + runtime when the `ccall` is first executed. A table of translations between the macro and function interfaces is given below. diff --git a/src/ccall.cpp b/src/ccall.cpp index 63f7613e8b824..43b40f7ad0205 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -54,13 +54,43 @@ GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) return prepare_global_in(M, jlRTLD_DEFAULT_var); } +typedef struct { + jl_value_t *gcroot[2]; // GC roots for strings [f_name, f_lib] + + // Static name resolution (compile-time known) + const char *f_name; // static function name + const char *f_lib; // static library name + + // Dynamic name resolution (simple runtime expressions) + jl_value_t *f_name_expr; // expression for function name + jl_value_t *f_lib_expr; // expression for library name + + // Runtime pointer + Value *jl_ptr; // callable pointer expression result +} native_sym_arg_t; // Find or create the GVs for the library and symbol lookup. -// Return `runtime_lib` (whether the library name is a string) +// Return `runtime_lib` (whether the library name is a string) if it returns `lib`. // The `lib` and `sym` GV returned may not be in the current module. -static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_name, +static bool runtime_sym_gvs(jl_codectx_t &ctx, const native_sym_arg_t &symarg, GlobalVariable *&lib, GlobalVariable *&sym) { + const auto &f_lib = symarg.f_lib; + const auto &f_name = symarg.f_name; + // If f_name isn't constant or f_lib_expr is present but not present, + // emit a local cache for sym, but do not cache lib + if (!((f_lib || symarg.f_lib_expr == NULL) && f_name)) { + std::string name = "dynccall_"; + name += std::to_string(jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1)); + Module *M = jl_Module; + auto T_pvoidfunc = getPointerTy(M->getContext()); + lib = nullptr; + sym = new GlobalVariable(*M, T_pvoidfunc, false, + GlobalVariable::InternalLinkage, + Constant::getNullValue(T_pvoidfunc), name); + return false; + } + auto M = &ctx.emission_context.shared_module(); bool runtime_lib = false; GlobalVariable *libptrgv; @@ -119,19 +149,22 @@ static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_ static Value *runtime_sym_lookup( jl_codegen_params_t &emission_context, IRBuilder<> &irbuilder, - jl_codectx_t *ctx, - PointerType *funcptype, const char *f_lib, jl_value_t *lib_expr, - const char *f_name, Function *f, + jl_codectx_t *pctx, + const native_sym_arg_t &symarg, Function *f, GlobalVariable *libptrgv, GlobalVariable *llvmgv, bool runtime_lib) { ++RuntimeSymLookups; - // in pseudo-code, this function emits the following: + // in pseudo-code, this function emits the following if libptrgv is set: // global HMODULE *libptrgv // global void **llvmgv - // if (*llvmgv == NULL) { + // if (*llvmgv == NULL) // *llvmgv = jl_load_and_lookup(f_lib, f_name, libptrgv); - // } + // return (*llvmgv) + // otherwise it emits: + // global void **llvmgv + // if (*llvmgv == NULL) + // *llvmgv = jl_lazy_load_and_lookup(f_lib_expr, f_name_expr); // return (*llvmgv) auto T_pvoidfunc = getPointerTy(irbuilder.getContext()); BasicBlock *enter_bb = irbuilder.GetInsertBlock(); @@ -139,7 +172,6 @@ static Value *runtime_sym_lookup( BasicBlock *ccall_bb = BasicBlock::Create(irbuilder.getContext(), "ccall"); Constant *initnul = ConstantPointerNull::get(T_pvoidfunc); LoadInst *llvmf_orig = irbuilder.CreateAlignedLoad(T_pvoidfunc, llvmgv, Align(sizeof(void*))); - setName(emission_context, llvmf_orig, f_name + StringRef(".cached")); // This in principle needs a consume ordering so that load from // this pointer sees a valid value. However, this is not supported by // LLVM (or agreed on in the C/C++ standard FWIW) and should be @@ -159,26 +191,42 @@ static Value *runtime_sym_lookup( dlsym_lookup->insertInto(f); irbuilder.SetInsertPoint(dlsym_lookup); Instruction *llvmf; - Value *nameval = stringConstPtr(emission_context, irbuilder, f_name); - if (lib_expr) { - jl_cgval_t libval = emit_expr(*ctx, lib_expr); - llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jllazydlsym_func), - { boxed(*ctx, libval), nameval }); - } - else { + if (libptrgv) { + // Call jl_load_and_lookup + assert(symarg.f_name); Value *libname; - if (runtime_lib) { - libname = stringConstPtr(emission_context, irbuilder, f_lib); - } - else { + if (runtime_lib) + libname = stringConstPtr(emission_context, irbuilder, symarg.f_lib); + else // f_lib is actually one of the special sentinel values - libname = ConstantExpr::getIntToPtr(ConstantInt::get(emission_context.DL.getIntPtrType(irbuilder.getContext()), (uintptr_t)f_lib), getPointerTy(irbuilder.getContext())); - } + libname = ConstantExpr::getIntToPtr(ConstantInt::get(emission_context.DL.getIntPtrType(irbuilder.getContext()), (uintptr_t)symarg.f_lib), getPointerTy(irbuilder.getContext())); + Value *nameval = stringConstPtr(emission_context, irbuilder, symarg.f_name); auto lookup = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlsym_func), { libname, nameval, libptrgv }); llvmf = lookup; + setName(emission_context, llvmf, symarg.f_name + StringRef(".found")); + } + else { + // Call jl_lazy_load_and_lookup + assert(pctx); + jl_codectx_t &ctx = *pctx; + Value *fname_val; + if (symarg.f_name) + fname_val = track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_symbol(symarg.f_name))); + else + fname_val = boxed(ctx, emit_expr(ctx, symarg.f_name_expr)); + Value *lib_val; + if (symarg.f_lib) + lib_val = track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_symbol(symarg.f_lib))); + else if (symarg.f_lib_expr) + // n.b. f_lib_expr is required to be something simple here (from + // resolve_definition_effects validation) such as a globalref or a + // quote node for example, not a general expression + lib_val = boxed(ctx, emit_expr(ctx, symarg.f_lib_expr)); + else + lib_val = ConstantPointerNull::get(ctx.types().T_prjlvalue); + llvmf = irbuilder.CreateCall(prepare_call(jllazydlsym_func), {lib_val, fname_val}); } - setName(emission_context, llvmf, f_name + StringRef(".found")); StoreInst *store = irbuilder.CreateAlignedStore(llvmf, llvmgv, Align(sizeof(void*))); store->setAtomic(AtomicOrdering::Release); irbuilder.CreateBr(ccall_bb); @@ -188,38 +236,21 @@ static Value *runtime_sym_lookup( PHINode *p = irbuilder.CreatePHI(T_pvoidfunc, 2); p->addIncoming(llvmf_orig, enter_bb); p->addIncoming(llvmf, llvmf->getParent()); - setName(emission_context, p, f_name); return p; } static Value *runtime_sym_lookup( jl_codectx_t &ctx, - PointerType *funcptype, const char *f_lib, jl_value_t *lib_expr, - const char *f_name, Function *f) + const native_sym_arg_t &symarg, Function *f) { - auto T_pvoidfunc = getPointerTy(ctx.builder.getContext()); GlobalVariable *libptrgv; GlobalVariable *llvmgv; - bool runtime_lib; - if (lib_expr) { - // for computed library names, generate a global variable to cache the function - // pointer just for this call site. - runtime_lib = true; - libptrgv = NULL; - std::string gvname = "libname_"; - gvname += f_name; - gvname += "_"; - gvname += std::to_string(jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1)); - llvmgv = new GlobalVariable(*jl_Module, T_pvoidfunc, false, - GlobalVariable::ExternalLinkage, - Constant::getNullValue(T_pvoidfunc), gvname); - } - else { - runtime_lib = runtime_sym_gvs(ctx, f_lib, f_name, libptrgv, llvmgv); + bool runtime_lib = runtime_sym_gvs(ctx, symarg, libptrgv, llvmgv); + if (libptrgv) { libptrgv = prepare_global_in(jl_Module, libptrgv); + llvmgv = prepare_global_in(jl_Module, llvmgv); } - llvmgv = prepare_global_in(jl_Module, llvmgv); - return runtime_sym_lookup(ctx.emission_context, ctx.builder, &ctx, funcptype, f_lib, lib_expr, f_name, f, libptrgv, llvmgv, runtime_lib); + return runtime_sym_lookup(ctx.emission_context, ctx.builder, &ctx, symarg, f, libptrgv, llvmgv, runtime_lib); } // Emit a "PLT" entry that will be lazily initialized @@ -227,17 +258,24 @@ static Value *runtime_sym_lookup( static GlobalVariable *emit_plt_thunk( jl_codectx_t &ctx, FunctionType *functype, const AttributeList &attrs, - CallingConv::ID cc, const char *f_lib, const char *f_name, + CallingConv::ID cc, const native_sym_arg_t &symarg, GlobalVariable *libptrgv, GlobalVariable *llvmgv, bool runtime_lib) { ++PLTThunks; - auto M = &ctx.emission_context.shared_module(); - PointerType *funcptype = PointerType::get(functype, 0); - libptrgv = prepare_global_in(M, libptrgv); - llvmgv = prepare_global_in(M, llvmgv); + bool shared = libptrgv != nullptr; + assert(shared && "not yet supported by runtime_sym_lookup"); + Module *M = shared ? &ctx.emission_context.shared_module() : jl_Module; + if (shared) { + assert(symarg.f_name); + libptrgv = prepare_global_in(M, libptrgv); + llvmgv = prepare_global_in(M, llvmgv); + } std::string fname; - raw_string_ostream(fname) << "jlplt_" << f_name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); + if (symarg.f_name) + raw_string_ostream(fname) << "jlplt_" << symarg.f_name << "_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); + else + raw_string_ostream(fname) << "jldynplt_" << jl_atomic_fetch_add_relaxed(&globalUniqueGeneratedNames, 1); Function *plt = Function::Create(functype, GlobalVariable::PrivateLinkage, fname, M); @@ -246,18 +284,19 @@ static GlobalVariable *emit_plt_thunk( plt->setCallingConv(cc); auto T_pvoidfunc = getPointerTy(M->getContext()); GlobalVariable *got = new GlobalVariable(*M, T_pvoidfunc, false, - GlobalVariable::ExternalLinkage, + shared ? GlobalVariable::ExternalLinkage : GlobalVariable::PrivateLinkage, plt, fname + "_got"); - if (runtime_lib) { - got->addAttribute("julia.libname", f_lib); - } else { - got->addAttribute("julia.libidx", std::to_string((uintptr_t) f_lib)); + if (shared) { + if (runtime_lib) + got->addAttribute("julia.libname", symarg.f_lib); + else + got->addAttribute("julia.libidx", std::to_string((uintptr_t) symarg.f_lib)); + got->addAttribute("julia.fname", symarg.f_name); } - got->addAttribute("julia.fname", f_name); BasicBlock *b0 = BasicBlock::Create(M->getContext(), "top", plt); IRBuilder<> irbuilder(b0); - Value *ptr = runtime_sym_lookup(ctx.emission_context, irbuilder, NULL, funcptype, f_lib, NULL, f_name, plt, libptrgv, + Value *ptr = runtime_sym_lookup(ctx.emission_context, irbuilder, NULL, symarg, plt, libptrgv, llvmgv, runtime_lib); StoreInst *store = irbuilder.CreateAlignedStore(ptr, got, Align(sizeof(void*))); store->setAtomic(AtomicOrdering::Release); @@ -302,7 +341,7 @@ static Value *emit_plt( jl_codectx_t &ctx, FunctionType *functype, const AttributeList &attrs, - CallingConv::ID cc, const char *f_lib, const char *f_name) + CallingConv::ID cc, const native_sym_arg_t &symarg) { ++PLT; // Don't do this for vararg functions so that the `musttail` is only @@ -310,18 +349,20 @@ static Value *emit_plt( assert(!functype->isVarArg()); GlobalVariable *libptrgv; GlobalVariable *llvmgv; - bool runtime_lib = runtime_sym_gvs(ctx, f_lib, f_name, libptrgv, llvmgv); + bool runtime_lib = runtime_sym_gvs(ctx, symarg, libptrgv, llvmgv); + if (!libptrgv) + return runtime_sym_lookup(ctx, symarg, ctx.f); auto &pltMap = ctx.emission_context.allPltMap[attrs]; auto key = std::make_tuple(llvmgv, functype, cc); GlobalVariable *&sharedgot = pltMap[key]; if (!sharedgot) { sharedgot = emit_plt_thunk(ctx, - functype, attrs, cc, f_lib, f_name, libptrgv, llvmgv, runtime_lib); + functype, attrs, cc, symarg, libptrgv, llvmgv, runtime_lib); } GlobalVariable *got = prepare_global_in(jl_Module, sharedgot); LoadInst *got_val = ctx.builder.CreateAlignedLoad(got->getValueType(), got, Align(sizeof(void*))); - setName(ctx.emission_context, got_val, f_name); + setName(ctx.emission_context, got_val, symarg.f_name); // See comment in `runtime_sym_lookup` above. This in principle needs a // consume ordering too. This is even less likely to cause issues though // since the only thing we do to this loaded pointer is to call it @@ -551,68 +592,42 @@ static Value *julia_to_native( return slot; } -typedef struct { - Value *jl_ptr; // if the argument is a run-time computed pointer - void (*fptr)(void); // if the argument is a constant pointer - const char *f_name; // if the symbol name is known - const char *f_lib; // if a library name is specified - jl_value_t *lib_expr; // expression to compute library path lazily - jl_value_t *gcroot; -} native_sym_arg_t; - -static inline const char *invalid_symbol_err_msg(bool ccall) -{ - return ccall ? - "ccall: first argument not a pointer or valid constant expression" : - "cglobal: first argument not a pointer or valid constant expression"; -} - // --- parse :sym or (:sym, :lib) argument into address info --- -static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_value_t *arg, bool ccall, bool llvmcall) +static void interpret_cglobal_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_value_t *arg) { Value *&jl_ptr = out.jl_ptr; - void (*&fptr)(void) = out.fptr; const char *&f_name = out.f_name; const char *&f_lib = out.f_lib; - jl_value_t *ptr = static_eval(ctx, arg); if (ptr == NULL) { if (jl_is_expr(arg) && ((jl_expr_t*)arg)->head == jl_call_sym && jl_expr_nargs(arg) == 3 && jl_is_globalref(jl_exprarg(arg,0)) && jl_globalref_mod(jl_exprarg(arg,0)) == jl_core_module && jl_globalref_name(jl_exprarg(arg,0)) == jl_symbol("tuple")) { - // attempt to interpret a non-constant 2-tuple expression as (func_name, lib_name()), where - // `lib_name()` will be executed when first used. - jl_value_t *name_val = static_eval(ctx, jl_exprarg(arg,1)); - if (name_val && jl_is_symbol(name_val)) { - f_name = jl_symbol_name((jl_sym_t*)name_val); - out.lib_expr = jl_exprarg(arg, 2); - return; + // attempt to interpret a non-constant 2-tuple expression as (func_name, lib_name) + out.f_name_expr = jl_exprarg(arg, 1); + out.f_lib_expr = jl_exprarg(arg, 2); + jl_value_t *name_val = static_eval(ctx, out.f_name_expr); + out.gcroot[0] = name_val; + if (name_val) { + if (jl_is_symbol(name_val)) + f_name = jl_symbol_name((jl_sym_t*)name_val); + else if (jl_is_string(name_val)) + f_name = jl_string_data(name_val); } - else if (name_val && jl_is_string(name_val)) { - f_name = jl_string_data(name_val); - out.gcroot = name_val; - out.lib_expr = jl_exprarg(arg, 2); - return; + jl_value_t *lib_val = static_eval(ctx, out.f_lib_expr); + out.gcroot[1] = lib_val; + if (lib_val) { + if (jl_is_symbol(lib_val)) + f_lib = jl_symbol_name((jl_sym_t*)lib_val); + else if (jl_is_string(lib_val)) + f_lib = jl_string_data(lib_val); } } - jl_cgval_t arg1 = emit_expr(ctx, arg); - jl_value_t *ptr_ty = arg1.typ; - if (!jl_is_cpointer_type(ptr_ty)) { - if (!ccall) - return; - const char *errmsg = invalid_symbol_err_msg(ccall); - emit_cpointercheck(ctx, arg1, errmsg); - } - arg1 = update_julia_type(ctx, arg1, (jl_value_t*)jl_voidpointer_type); - jl_ptr = emit_unbox(ctx, ctx.types().T_ptr, arg1, (jl_value_t*)jl_voidpointer_type); - } - else if (jl_is_cpointer_type(jl_typeof(ptr))) { - fptr = *(void(**)(void))jl_data_ptr(ptr); } else { - out.gcroot = ptr; if (jl_is_tuple(ptr) && jl_nfields(ptr) == 1) { ptr = jl_fieldref(ptr, 0); + out.gcroot[0] = ptr; } if (jl_is_symbol(ptr)) @@ -623,20 +638,15 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va if (f_name != NULL) { // just symbol, default to JuliaDLHandle // will look in process symbol table - if (!llvmcall) { - void *symaddr; - std::string iname("i"); - iname += f_name; - if (jl_dlsym(jl_libjulia_internal_handle, iname.c_str(), &symaddr, 0, 0)) { - f_lib = JL_LIBJULIA_INTERNAL_DL_LIBNAME; - f_name = jl_symbol_name(jl_symbol(iname.c_str())); - } - else { - f_lib = jl_dlfind(f_name); - } - } + f_lib = jl_dlfind(f_name); + out.f_name_expr = jl_new_struct(jl_quotenode_type, ptr); + out.gcroot[0] = out.f_name_expr; + } + else if (jl_is_cpointer_type(jl_typeof(ptr))) { + uint64_t fptr = (uintptr_t)*(void(**)(void))jl_data_ptr(ptr); + jl_ptr = ConstantExpr::getIntToPtr(ConstantInt::get(ctx.types().T_size, fptr), ctx.types().T_ptr); } - else if (jl_is_tuple(ptr) && jl_nfields(ptr) > 1) { + else if (jl_is_tuple(ptr) && jl_nfields(ptr) == 2) { jl_value_t *t0 = jl_fieldref(ptr, 0); if (jl_is_symbol(t0)) f_name = jl_symbol_name((jl_sym_t*)t0); @@ -648,9 +658,103 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va f_lib = jl_symbol_name((jl_sym_t*)t1); else if (jl_is_string(t1)) f_lib = jl_string_data(t1); - else { - out.lib_expr = t1; + + out.f_name_expr = jl_new_struct(jl_quotenode_type, t0); + out.gcroot[0] = out.f_name_expr; + out.f_lib_expr = jl_new_struct(jl_quotenode_type, t1); + out.gcroot[1] = out.f_lib_expr; + } + } +} + +static void interpret_ccall_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_value_t *arg) +{ + // Initialize all fields to safe defaults + out.f_name = nullptr; + out.f_lib = nullptr; + out.f_name_expr = nullptr; + out.f_lib_expr = nullptr; + out.jl_ptr = nullptr; + out.gcroot[0] = nullptr; + out.gcroot[1] = nullptr; + + // Check if this is a tuple (normalized by julia-syntax.scm) + if (jl_is_expr(arg) && ((jl_expr_t*)arg)->head == jl_symbol("tuple")) { + size_t nargs = jl_expr_nargs(arg); + jl_array_t *tuple_args = ((jl_expr_t*)arg)->args; + + if (nargs == 1) { + // Single element tuple: (func_name,) - use default library + jl_value_t *fname_arg = jl_array_ptr_ref(tuple_args, 0); + jl_value_t *fname_val = static_eval(ctx, fname_arg); + // Dynamic resolution - single function name expression, will use default library at runtime + out.f_name_expr = fname_arg; + + if (fname_val != nullptr) { + // Static resolution succeeded + out.gcroot[0] = fname_val; + if (jl_is_symbol(fname_val)) { + out.f_name = jl_symbol_name((jl_sym_t*)fname_val); + } + else if (jl_is_string(fname_val)) { + out.f_name = jl_string_data(fname_val); + } + } + } + else if (nargs == 2) { + // Two element tuple: (func_name, lib_name) + jl_value_t *fname_arg = jl_array_ptr_ref(tuple_args, 0); + jl_value_t *lib_arg = jl_array_ptr_ref(tuple_args, 1); + out.f_name_expr = fname_arg; + out.f_lib_expr = lib_arg; + + jl_value_t *fname_val = static_eval(ctx, fname_arg); + jl_value_t *lib_val = static_eval(ctx, lib_arg); + if (fname_val != nullptr) { + // Static resolution for both + out.gcroot[0] = fname_val; // Keep function name for GC + if (jl_is_symbol(fname_val)) { + out.f_name = jl_symbol_name((jl_sym_t*)fname_val); + } + else if (jl_is_string(fname_val)) { + out.f_name = jl_string_data(fname_val); + } } + + if (lib_val != nullptr) { + out.gcroot[1] = lib_val; // Keep library name for GC + if (jl_is_symbol(lib_val)) { + out.f_lib = jl_symbol_name((jl_sym_t*)lib_val); + } + else if (jl_is_string(lib_val)) { + out.f_lib = jl_string_data(lib_val); + } + } + } + } + else { + // Not a tuple - pointer expression + jl_cgval_t arg1 = emit_expr(ctx, arg); + jl_value_t *ptr_ty = arg1.typ; + if (!jl_is_cpointer_type(ptr_ty)) { + const char *errmsg = "ccall: first argument not a pointer or valid constant expression"; + emit_cpointercheck(ctx, arg1, errmsg); + } + arg1 = update_julia_type(ctx, arg1, (jl_value_t*)jl_voidpointer_type); + out.jl_ptr = emit_unbox(ctx, ctx.types().T_ptr, arg1, (jl_value_t*)jl_voidpointer_type); + } + + // Handle Julia internal symbol lookup for static function names + if (out.f_name != nullptr && out.f_lib_expr == nullptr) { + void *symaddr; + std::string iname("i"); + iname += out.f_name; + if (jl_dlsym(jl_libjulia_internal_handle, iname.c_str(), &symaddr, 0, 0)) { + out.f_lib = JL_LIBJULIA_INTERNAL_DL_LIBNAME; + out.f_name = jl_symbol_name(jl_symbol(iname.c_str())); + } + else { + out.f_lib = jl_dlfind(out.f_name); } } } @@ -666,7 +770,7 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg jl_value_t *rt = NULL; Value *res; native_sym_arg_t sym = {}; - JL_GC_PUSH2(&rt, &sym.gcroot); + JL_GC_PUSH3(&rt, &sym.gcroot[0], &sym.gcroot[1]); if (nargs == 2) { rt = static_eval(ctx, args[2]); @@ -684,32 +788,25 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg else { rt = (jl_value_t*)jl_voidpointer_type; } - Type *lrt = ctx.types().T_ptr; - assert(lrt == julia_type_to_llvm(ctx, rt)); - - interpret_symbol_arg(ctx, sym, args[1], /*ccall=*/false, false); - + interpret_cglobal_symbol_arg(ctx, sym, args[1]); if (sym.jl_ptr != NULL) { res = sym.jl_ptr; } - else if (sym.fptr != NULL) { - res = ConstantInt::get(lrt, (uint64_t)sym.fptr); + else if (sym.f_name_expr != NULL) { + res = runtime_sym_lookup(ctx, sym, ctx.f); } - else if (sym.f_name != NULL) { - if (sym.lib_expr) { - res = runtime_sym_lookup(ctx, getPointerTy(ctx.builder.getContext()), NULL, sym.lib_expr, sym.f_name, ctx.f); - } - else { - res = runtime_sym_lookup(ctx, getPointerTy(ctx.builder.getContext()), sym.f_lib, NULL, sym.f_name, ctx.f); - } - } else { + else { // Fall back to runtime intrinsic JL_GC_POP(); jl_cgval_t argv[2]; argv[0] = emit_expr(ctx, args[1]); if (nargs == 2) argv[1] = emit_expr(ctx, args[2]); - return emit_runtime_call(ctx, nargs == 1 ? JL_I::cglobal_auto : JL_I::cglobal, argv, nargs); + if (!jl_is_cpointer_type(argv[0].typ)) + return emit_runtime_call(ctx, nargs == 1 ? JL_I::cglobal_auto : JL_I::cglobal, argv, nargs); + argv[0] = update_julia_type(ctx, argv[0], (jl_value_t*)jl_voidpointer_type); + sym.jl_ptr = emit_unbox(ctx, ctx.types().T_ptr, argv[0], (jl_value_t*)jl_voidpointer_type); + res = sym.jl_ptr; } JL_GC_POP(); @@ -1436,33 +1533,17 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } assert(jl_is_symbol(cc_sym)); native_sym_arg_t symarg = {}; - JL_GC_PUSH3(&rt, &at, &symarg.gcroot); + JL_GC_PUSH4(&rt, &at, &symarg.gcroot[0], &symarg.gcroot[1]); CallingConv::ID cc = CallingConv::C; bool llvmcall = false; std::tie(cc, llvmcall) = convert_cconv(cc_sym); - interpret_symbol_arg(ctx, symarg, args[1], /*ccall=*/true, llvmcall); - Value *&jl_ptr = symarg.jl_ptr; - void (*&fptr)(void) = symarg.fptr; + interpret_ccall_symbol_arg(ctx, symarg, args[1]); const char *&f_name = symarg.f_name; const char *&f_lib = symarg.f_lib; - if (f_name == NULL && fptr == NULL && jl_ptr == NULL) { - if (symarg.gcroot != NULL) { // static_eval(ctx, args[1]) could not be interpreted to a function pointer - const char *errmsg = invalid_symbol_err_msg(/*ccall=*/true); - jl_cgval_t arg1 = emit_expr(ctx, args[1]); - emit_type_error(ctx, arg1, literal_pointer_val(ctx, (jl_value_t *)jl_pointer_type), errmsg); - } else { - emit_error(ctx, "ccall: null function pointer"); - } - JL_GC_POP(); - return jl_cgval_t(); - } - - auto _is_libjulia_func = [&] (uintptr_t ptr, StringRef name) { - if ((uintptr_t)fptr == ptr) - return true; + auto _is_libjulia_func = [&f_lib, &f_name] (StringRef name) { if (f_lib) { if ((f_lib == JL_EXE_LIBNAME) || // preventing invalid pointer access (f_lib == JL_LIBJULIA_INTERNAL_DL_LIBNAME) || @@ -1480,7 +1561,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } return f_name && f_name == name; }; -#define is_libjulia_func(name) _is_libjulia_func((uintptr_t)&(name), StringRef(XSTR(name))) +#define is_libjulia_func(name) _is_libjulia_func(StringRef(XSTR(name))) // emit arguments SmallVector argv(nccallargs); @@ -2075,20 +2156,16 @@ jl_cgval_t function_sig_t::emit_a_ccall( Value *llvmf; if (llvmcall) { ++EmittedLLVMCalls; - if (symarg.jl_ptr != NULL) { - emit_error(ctx, "llvmcall doesn't support dynamic pointers"); + if (symarg.f_name == NULL) { + // TODO: this should be checked/enforced a bit better (less dynamically) + emit_error(ctx, "llvmcall doesn't support dynamic names"); return jl_cgval_t(); } - else if (symarg.fptr != NULL) { - emit_error(ctx, "llvmcall doesn't support static pointers"); - return jl_cgval_t(); - } - else if (symarg.f_lib != NULL) { + else if (symarg.f_lib_expr != NULL) { emit_error(ctx, "llvmcall doesn't support dynamic libraries"); return jl_cgval_t(); } else { - assert(symarg.f_name != NULL); StringRef f_name(symarg.f_name); bool f_extern = f_name.consume_front("extern "); llvmf = NULL; @@ -2138,36 +2215,25 @@ jl_cgval_t function_sig_t::emit_a_ccall( null_pointer_check(ctx, symarg.jl_ptr, nullptr); llvmf = symarg.jl_ptr; } - else if (symarg.fptr != NULL) { - ++LiteralCCalls; - Type *funcptype = PointerType::getUnqual(functype->getContext()); - llvmf = literal_static_pointer_val((void*)(uintptr_t)symarg.fptr, funcptype); - setName(ctx.emission_context, llvmf, "ccall_fptr"); - } else if (!ctx.params->use_jlplt) { if ((symarg.f_lib && !((symarg.f_lib == JL_EXE_LIBNAME) || (symarg.f_lib == JL_LIBJULIA_INTERNAL_DL_LIBNAME) || - (symarg.f_lib == JL_LIBJULIA_DL_LIBNAME))) || symarg.lib_expr) { + (symarg.f_lib == JL_LIBJULIA_DL_LIBNAME))) || symarg.f_lib_expr) { + // n.b. this is not semantically valid, but use_jlplt=1 when semantic correctness is desired emit_error(ctx, "ccall: Had library expression, but symbol lookup was disabled"); } + if (symarg.f_name == nullptr) + emit_error(ctx, "ccall: Had name expression, but symbol lookup was disabled"); llvmf = jl_Module->getOrInsertFunction(symarg.f_name, functype).getCallee(); } else { - assert(symarg.f_name != NULL); - PointerType *funcptype = PointerType::get(functype, 0); - if (symarg.lib_expr) { - ++DeferredCCallLookups; - llvmf = runtime_sym_lookup(ctx, funcptype, NULL, symarg.lib_expr, symarg.f_name, ctx.f); - } - else { - ++DeferredCCallLookups; - // vararg requires musttail, - // but musttail is incompatible with noreturn. - if (functype->isVarArg()) - llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, ctx.f); - else - llvmf = emit_plt(ctx, functype, attributes, cc, symarg.f_lib, symarg.f_name); - } + ++DeferredCCallLookups; + // vararg requires musttail, + // but musttail is incompatible with noreturn. + if (functype->isVarArg()) + llvmf = runtime_sym_lookup(ctx, symarg, ctx.f); + else + llvmf = emit_plt(ctx, functype, attributes, cc, symarg); } // Potentially we could add gc_uses to `gc-transition`, instead of emitting them separately as jl_roots diff --git a/src/codegen.cpp b/src/codegen.cpp index 8cd9ca53b4e5e..71dd815548358 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1280,7 +1280,7 @@ static const auto jldlsym_func = new JuliaFunction<>{ static const auto jllazydlsym_func = new JuliaFunction<>{ XSTR(jl_lazy_load_and_lookup), [](LLVMContext &C) { return FunctionType::get(getPointerTy(C), - {JuliaType::get_prjlvalue_ty(C), getPointerTy(C)}, false); }, + {JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C)}, false); }, nullptr, }; static const auto jltypeassert_func = new JuliaFunction<>{ diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index bd2bc211bd628..6bfb3ff5a7a7e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1087,6 +1087,15 @@ (values name params super)) ex) (error "invalid type signature"))) +;; normalize ccall first argument to tuple form with basic error checking +(define (normalize-ccall-name raw-name) + (cond + ;; Already a tuple - keep as-is, validation will happen in C + ((tuple-syntax? raw-name) + raw-name) + ;; Otherwise it is an atom or pointer expression, which will be validated later in C + (else (expand-forms raw-name)))) + ;; insert calls to convert() in ccall, and pull out expressions that might ;; need to be rooted before conversion. (define (lower-ccall name RT atypes args cconv nreq) @@ -1106,7 +1115,7 @@ (if (null? A) `(block ,.(reverse! stmts) - (foreigncall ,(expand-forms name) ,(expand-forms RT) (call (core svec) ,@(reverse! T)) + (foreigncall ,(normalize-ccall-name name) ,(expand-forms RT) (call (core svec) ,@(reverse! T)) ;; 0 or number of arguments before ... in definition ,(or nreq (if isseq (- (length atypes) 1) 0)) @@ -1150,7 +1159,7 @@ (error (string "invalid argument destructuring syntax \"" (deparse a) "\"")) a)) (define (transform-arg a) - (cond ((and (pair? a) (eq? (car a) 'tuple)) + (cond ((tuple-syntax? a) (let ((a2 (gensy))) (cons a2 `(local (= ,(check-lhs a) ,a2))))) ((or (and (decl? a) (length= a 3)) (kwarg? a)) @@ -1321,8 +1330,7 @@ (= ,vname ,tmp) ,blk))))))) ;; (a, b, c, ...) = rhs - ((and (pair? (cadar binds)) - (eq? (caadar binds) 'tuple)) + ((tuple-syntax? (cadar binds)) (let ((vars (lhs-vars (cadar binds)))) (loop (cdr binds) (let ((tmp (make-ssavalue))) @@ -1861,7 +1869,7 @@ (cons temp (append (cdr e) (list `(= ,temp ,newlhs)))) e)) (newlhs (or temp newlhs))) - (if (and (pair? lhs) (eq? (car lhs) 'tuple)) + (if (tuple-syntax? lhs) (let loop ((a (cdr newlhs)) (b (cdr lhs))) (if (pair? a) @@ -2491,7 +2499,7 @@ ((null? r) #f) ((vararg? (car r)) (null? (cdr r))) (else (sides-match? (cdr l) (cdr r))))) - (if (and (pair? x) (pair? lhss) (eq? (car x) 'tuple) (not (any assignment? (cdr x))) + (if (and (tuple-syntax? x) (pair? lhss) (not (any assignment? (cdr x))) (not (has-parameters? (cdr x))) (sides-match? lhss (cdr x))) ;; (a, b, ...) = (x, y, ...) @@ -2720,13 +2728,12 @@ (argtypes (cadr after-cconv)) (args (cddr after-cconv))) (begin - (if (not (and (pair? argtypes) - (eq? (car argtypes) 'tuple))) - (if (and (pair? RT) - (eq? (car RT) 'tuple)) + (if (not (tuple-syntax? argtypes)) + (if (tuple-syntax? RT) (error "ccall argument types must be a tuple; try \"(T,)\" and check if you specified a correct return type") (error "ccall argument types must be a tuple; try \"(T,)\""))) - (lower-ccall name RT (cdr argtypes) args + (lower-ccall name + RT (cdr argtypes) args (if have-cconv (if have-cconv-expr (cadr cconv) @@ -2771,6 +2778,20 @@ (map expand-forms e)))) (map expand-forms e))) + 'foreigncall + (lambda (e) + (if (not (length> e 5)) (error "too few arguments to foreigncall")) + (let* ((name (car (list-tail e 1))) + (RT (car (list-tail e 2))) + (atypes (car (list-tail e 3))) + (nreq (car (list-tail e 4))) + (cconv (car (list-tail e 5))) + (args-and-roots (list-tail e 6))) + (begin + ;; Return expanded foreigncall + `(foreigncall ,(normalize-ccall-name name) ,(expand-forms RT) ,(expand-forms atypes) + ,nreq ,cconv ,@(map expand-forms args-and-roots))))) + 'do (lambda (e) (let* ((call (cadr e)) @@ -4500,6 +4521,9 @@ f(x) = yt(x) (else #f)) #t)) +(define (tuple-syntax? fptr) + (and (pair? fptr) (eq? (car fptr) 'tuple))) + ;; this pass behaves like an interpreter on the given code. ;; to perform stateful operations, it calls `emit` to record that something ;; needs to be done. in value position, it returns an expression computing @@ -4772,10 +4796,10 @@ f(x) = yt(x) (let* ((args (cond ((eq? (car e) 'foreigncall) ;; NOTE: 2nd to 5th arguments of ccall must be left in place - ;; the 1st should be compiled if an atom. - (append (if (atom-or-not-tuple-call? (cadr e)) - (compile-args (list (cadr e)) break-labels) - (list (cadr e))) + ;; the 1st should be compiled unless it is a syntactic tuple from earlier + (append (if (tuple-syntax? (cadr e)) + (list (cadr e)) + (compile-args (list (cadr e)) break-labels)) (list-head (cddr e) 4) (compile-args (list-tail e 6) break-labels))) ;; NOTE: arguments of cfunction must be left in place @@ -5427,8 +5451,8 @@ f(x) = yt(x) (let ((e (cons (car e) (map renumber-stuff (cdr e))))) (if (and (eq? (car e) 'foreigncall) - (tuple-call? (cadr e)) - (expr-contains-p (lambda (x) (or (ssavalue? x) (slot? x))) (cadr e))) + (tuple-syntax? (cadr e)) + (expr-contains-p (lambda (x) (or (ssavalue? x) (slot? x))) (cadr e))) ;; TODO: use allow-list here (error "ccall function name and library expression cannot reference local variables")) e)))) (let ((body (renumber-stuff (lam:body lam))) diff --git a/src/julia_internal.h b/src/julia_internal.h index 0ccd46774f5ed..ebf9f3690062f 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -943,6 +943,7 @@ jl_value_t *jl_interpret_toplevel_expr_in(jl_module_t *m, jl_value_t *e, JL_DLLEXPORT int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT; jl_value_t *jl_call_scm_on_ast_and_loc(const char *funcname, jl_value_t *expr, jl_module_t *inmodule, const char *file, int line); +int jl_isa_ast_node(jl_value_t *e) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_method_lookup_by_tt(jl_tupletype_t *tt, size_t world, jl_value_t *_mt); JL_DLLEXPORT jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world); @@ -1654,7 +1655,7 @@ JL_DLLEXPORT void *jl_get_library_(const char *f_lib, int throw_err); void *jl_find_dynamic_library_by_addr(void *symbol, int throw_err, int close) JL_NOTSAFEPOINT; #define jl_get_library(f_lib) jl_get_library_(f_lib, 1) JL_DLLEXPORT void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) *hnd); -JL_DLLEXPORT void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name); +JL_DLLEXPORT void *jl_lazy_load_and_lookup(jl_value_t *lib_val, jl_value_t *f_name); JL_DLLEXPORT jl_value_t *jl_get_cfunction_trampoline( jl_value_t *fobj, jl_datatype_t *result, htable_t *cache, jl_svec_t *fill, void *(*init_trampoline)(void *tramp, void **nval), diff --git a/src/method.c b/src/method.c index 9986f33e55f9e..cee941ae77ddb 100644 --- a/src/method.c +++ b/src/method.c @@ -162,6 +162,86 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod if (e->head == jl_foreigncall_sym) { JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects, gc_safe)) jl_task_t *ct = jl_current_task; + jl_value_t *fptr = jl_exprarg(e, 0); + // Handle dot expressions in tuple arguments for ccall by converting to GlobalRef eagerly + jl_sym_t *tuple_sym = jl_symbol("tuple"); + if (jl_is_quotenode(fptr)) { + if (jl_is_string(jl_quotenode_value(fptr)) || jl_is_tuple(jl_quotenode_value(fptr))) + fptr = jl_quotenode_value(fptr); + } + if (jl_is_tuple(fptr)) { + // convert literal Tuple to Expr tuple + jl_expr_t *tupex = jl_exprn(tuple_sym, jl_nfields(fptr)); + jl_value_t *v = NULL; + JL_GC_PUSH2(&tupex, &v); + for (long i = 0; i < jl_nfields(fptr); i++) { + v = jl_fieldref(fptr, i); + if (!jl_is_string(v)) + v = jl_new_struct(jl_quotenode_type, v); + jl_exprargset(tupex, i, v); + } + jl_exprargset(e, 0, tupex); + fptr = (jl_value_t*)tupex; + JL_GC_POP(); + } + if (jl_is_expr(fptr) && ((jl_expr_t*)fptr)->head == tuple_sym) { + // verify Expr tuple can be interpreted and handle + jl_expr_t *tuple_expr = (jl_expr_t*)fptr; + size_t nargs_tuple = jl_expr_nargs(tuple_expr); + if (nargs_tuple == 0) + jl_error("ccall function name cannot be empty tuple"); + if (nargs_tuple > 2) + jl_error("ccall function name tuple can have at most 2 elements"); + // Validate tuple elements are not more complicated than inference/codegen can safely handle + for (size_t i = 0; i < nargs_tuple; i++) { + jl_value_t *arg = jl_exprarg(tuple_expr, i); + // Handle dot expressions by converting to a GlobalRef + if (jl_is_expr(arg) && ((jl_expr_t*)arg)->head == jl_dot_sym) { + jl_expr_t *dot_expr = (jl_expr_t*)arg; + if (jl_expr_nargs(dot_expr) != 2) + jl_error("ccall function name: invalid dot expression"); + jl_value_t *mod_expr = jl_exprarg(dot_expr, 0); + jl_value_t *sym_expr = jl_exprarg(dot_expr, 1); + if (!(jl_is_quotenode(sym_expr) && jl_is_symbol(jl_quotenode_value(sym_expr)))) + jl_type_error("ccall name dot expression", (jl_value_t*)jl_symbol_type, sym_expr); + JL_TRY { + // Evaluate the module expression + jl_value_t *mod_val = jl_toplevel_eval(module, mod_expr); + JL_TYPECHK(ccall name dot expression, module, mod_val); + JL_GC_PROMISE_ROOTED(mod_val); + // Create GlobalRef from evaluated module and quoted symbol + jl_sym_t *sym = (jl_sym_t*)jl_quotenode_value(sym_expr); + jl_value_t *globalref = jl_module_globalref((jl_module_t*)mod_val, sym); + jl_exprargset(tuple_expr, i, globalref); + } + JL_CATCH { + if (jl_typetagis(jl_current_exception(ct), jl_errorexception_type)) + jl_error("could not evaluate ccall function/library name (it might depend on a local variable)"); + else + jl_rethrow(); + } + } + else if (jl_is_quotenode(arg)) { + if (i == 0) { + // function name must be a symbol or string, library can be anything + jl_value_t *quoted_val = jl_quotenode_value(arg); + if (!jl_is_symbol(quoted_val) && !jl_is_string(quoted_val)) + jl_type_error("ccall function name", (jl_value_t*)jl_symbol_type, jl_quotenode_value(arg)); + } + } + else if (!jl_is_globalref(arg) && jl_isa_ast_node(arg)) { + jl_type_error(i == 0 ? "ccall function name" : "ccall library name", (jl_value_t*)jl_symbol_type, arg); + } + } + } + else if (jl_is_string(fptr) || (jl_is_quotenode(fptr) && jl_is_symbol(jl_quotenode_value(fptr)))) { + // convert String to Expr (String,) + // convert QuoteNode(Symbol) to Expr (QuoteNode(Symbol),) + jl_expr_t *tupex = jl_exprn(tuple_sym, 1); + jl_exprargset(tupex, 0, fptr); + jl_exprargset(e, 0, tupex); + fptr = (jl_value_t*)tupex; + } jl_value_t *rt = jl_exprarg(e, 1); jl_value_t *at = jl_exprarg(e, 2); if (!jl_is_type(rt)) { diff --git a/src/runtime_ccall.cpp b/src/runtime_ccall.cpp index dbc5821e7af68..4c787348f6f1b 100644 --- a/src/runtime_ccall.cpp +++ b/src/runtime_ccall.cpp @@ -65,22 +65,35 @@ void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) * // jl_load_and_lookup, but with library computed at run time on first call extern "C" JL_DLLEXPORT -void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name) +void *jl_lazy_load_and_lookup(jl_value_t *lib_val, jl_value_t *f_name) { void *lib_ptr; + const char *fname_str; + + if (jl_is_symbol(f_name)) + fname_str = jl_symbol_name((jl_sym_t*)f_name); + else if (jl_is_string(f_name)) + fname_str = jl_string_data(f_name); + else + jl_type_error("ccall function name", (jl_value_t*)jl_symbol_type, f_name); + + if (lib_val) { + if (jl_is_symbol(lib_val)) + lib_ptr = jl_get_library(jl_symbol_name((jl_sym_t*)lib_val)); + else if (jl_is_string(lib_val)) + lib_ptr = jl_get_library(jl_string_data(lib_val)); + else if (jl_libdl_dlopen_func != NULL) { + lib_ptr = jl_unbox_voidpointer(jl_apply_generic(jl_libdl_dlopen_func, &lib_val, 1)); + } else + jl_type_error("ccall", (jl_value_t*)jl_symbol_type, lib_val); + } + else { + // If the user didn't supply a library name, try to find it now from the runtime value of f_name + lib_ptr = jl_get_library(jl_dlfind(fname_str)); + } - if (jl_is_symbol(lib_val)) - lib_ptr = jl_get_library(jl_symbol_name((jl_sym_t*)lib_val)); - else if (jl_is_string(lib_val)) - lib_ptr = jl_get_library(jl_string_data(lib_val)); - else if (jl_libdl_dlopen_func != NULL) { - // Call `dlopen(lib_val)`; this is the correct path for the `LazyLibrary` case, - // but it also takes any other value, and so we define `dlopen(x::Any) = throw(TypeError(...))`. - lib_ptr = jl_unbox_voidpointer(jl_apply_generic(jl_libdl_dlopen_func, &lib_val, 1)); - } else - jl_type_error("ccall", (jl_value_t*)jl_symbol_type, lib_val); void *ptr; - jl_dlsym(lib_ptr, f_name, &ptr, 1, 1); + jl_dlsym(lib_ptr, fname_str, &ptr, 1, 1); return ptr; } diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index b5e27fcd18fed..31dd3e085033c 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -654,25 +654,8 @@ JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) f_lib = jl_fieldref(v, 1); v = jl_fieldref(v, 0); } - - char *f_name = NULL; - if (jl_is_symbol(v)) - f_name = jl_symbol_name((jl_sym_t*)v); - else if (jl_is_string(v)) - f_name = jl_string_data(v); - else - JL_TYPECHK(cglobal, symbol, v) - - void *ptr; - if (f_lib) { - ptr = jl_lazy_load_and_lookup(f_lib, f_name); - } - else { - void *handle = jl_get_library((char*)jl_dlfind(f_name)); - jl_dlsym(handle, f_name, &ptr, 1, 0); - } + void *ptr = jl_lazy_load_and_lookup(f_lib, v); JL_GC_POP(); - jl_value_t *jv = jl_gc_alloc(jl_current_task->ptls, sizeof(void*), rt); *(void**)jl_data_ptr(jv) = ptr; return jv; diff --git a/test/ccall.jl b/test/ccall.jl index 3446aac68d056..633af8e6a8495 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1537,6 +1537,14 @@ fn45187() = nothing @test Expr(:error, "only the trailing ccall argument type should have \"...\"") == Meta.lower(@__MODULE__, :(ccall(:fn, A, (A, B..., C...), a, x, y, z))) @test Expr(:error, "more types than arguments for ccall") == Meta.lower(@__MODULE__, :(ccall(:fn, A, (B, C...), ))) +# test for ccall first argument tuple validation errors +@test_throws "ccall function name cannot be empty tuple" eval(:(f() = ccall((), A, (), ))) +@test_throws "ccall function name tuple can have at most 2 elements" eval(:(f() = ccall((:a, :b, :c), A, (), ))) +@test_throws "ccall function name tuple can have at most 2 elements" eval(:(f() = ccall((:a, :b, :c, :d), A, (), ))) +@test_throws TypeError eval(:(f() = ccall((1 + 2,), A, (), ))) +@test_throws TypeError eval(:(f() = ccall((:a, 1 + 2), A, (), ))) +@test_throws TypeError eval(:(ccall_lazy_lib_name(x) = ccall((:testUcharX, compute_lib_name()), Int32, (UInt8,), x % UInt8))) + # cfunction on non-function singleton struct CallableSingleton end @@ -1751,18 +1759,16 @@ using Base: ccall_macro_parse, ccall_macro_lower end @testset "ensure the base-case of @ccall works, including library name and pointer interpolation" begin - call = ccall_macro_lower(:ccall, ccall_macro_parse( :( libstring.func( + ccallmacro = ccall_macro_lower(:ccall, ccall_macro_parse( :( libstring.func( str::Cstring, num1::Cint, num2::Cint )::Cstring))...) - @test call == Base.remove_linenums!( - quote - ccall($(Expr(:escape, :((:func, libstring)))), $(Expr(:cconv, (:ccall, UInt16(0), false), 0)), $(Expr(:escape, :Cstring)), ($(Expr(:escape, :Cstring)), $(Expr(:escape, :Cint)), $(Expr(:escape, :Cint))), $(Expr(:escape, :str)), $(Expr(:escape, :num1)), $(Expr(:escape, :num2))) - end) + ccallfunction = :(ccall($(Expr(:escape, :((:func, libstring)))), $(Expr(:cconv, (:ccall, UInt16(0), false), 0)), $(Expr(:escape, :Cstring)), ($(Expr(:escape, :Cstring)), $(Expr(:escape, :Cint)), $(Expr(:escape, :Cint))), $(Expr(:escape, :str)), $(Expr(:escape, :num1)), $(Expr(:escape, :num2)))) + @test ccallmacro == ccallfunction local fptr = :x - @test_throws ArgumentError("interpolated function `fptr` was not a Ptr{Cvoid}, but Symbol") @ccall $fptr()::Cvoid + @test_throws TypeError @ccall $fptr()::Cvoid end @testset "check error paths" begin @@ -1777,7 +1783,7 @@ end # no required args on varargs call @test_throws ArgumentError("C ABI prohibits vararg without one required argument") ccall_macro_parse(:( foo(; x::Cint)::Cint )) # not a function pointer - @test_throws ArgumentError("interpolated function `PROGRAM_FILE` was not a Ptr{Cvoid}, but String") @ccall $PROGRAM_FILE("foo"::Cstring)::Cvoid + @test_throws TypeError @ccall $PROGRAM_FILE("foo"::Cstring)::Cvoid end @testset "check error path for @cfunction" begin @@ -1834,10 +1840,6 @@ end end # issue #36458 -compute_lib_name() = "libcc" * "alltest" -ccall_lazy_lib_name(x) = ccall((:testUcharX, compute_lib_name()), Int32, (UInt8,), x % UInt8) -@test ccall_lazy_lib_name(0) == 0 -@test ccall_lazy_lib_name(3) == 1 ccall_with_undefined_lib() = ccall((:time, xx_nOt_DeFiNeD_xx), Cint, (Ptr{Cvoid},), C_NULL) @test_throws UndefVarError(:xx_nOt_DeFiNeD_xx, @__MODULE__) ccall_with_undefined_lib()