-
Notifications
You must be signed in to change notification settings - Fork 0
0001: Enhanced static compilation and C interface
The behavior of ccall
depends on the ability of codegen to recurse back into Julia code. This makes it's actual behavior slightly unpredictable. It also means the static compilation semantics are subtly and unpredictably different from the JIT compiled semantics. This in turn severely limits the ability of the generic function cache logic to coalesce calls, as it doesn't know if this might affect the meaning of a ccall.
Remove the special handling of ccall
in the parser. Make the special-case lowering code more similar to other macro-expansion. Remove the Intrinsics.ccall
function. Introduce a new Expr(:foreigncall)
to represent the primitive foreign function invocation.
Later, add new features to Expr(:foreigncall)
that the old structure couldn't easily represent, including: integrated handling of llvmcall
functions and arbitrary function and argument attributes.
- Perfectly statically compilable (use declarative syntax rather than runtime discovery)
- Ability to split declaration and usage sites
- Zero Intrinsic functions
- Break runtime dependence on requiring LLVM as a backend
- Support all current features
- Parameterized return types for Ptr, Array, Ref
- Call by value or name
- Runtime/lazy memoized dlsym
- Explicit library linking
- Calls to LLVM intrinsics and LLVM modules
- Add support for attributes (pure, fortran, compiler target)
- Add support for calling convention on cfunction
- Add support for backend-managed LLVM modules from other frontends (e.g. Cxx.jl),including for precompilation & serialization
- Less usage of
@static
? (fewer invalid syntax issues) - More predictable behavior of the type parameters to
ccall
- Makes it clear that this is called in the global scope
- Don't permit dependancies on the later definition of other functions, late binding resolution, etc.
- Don't require the evaluation of arbitrary code to compile the function body
- Don't permit uncomputable dependencies on the static parameters of the function
- Permit dependencies on the static parameters of the enclosing function that resolve to simple UnionAll bounds and don't impact the ABI of the type (all pointers are equivalent, but other values may not be handled identically across all ABIs).
- Supporting va_args more (left as an exercise for future PR)
- Supporting changing the library name/path during load time (some thoughts for doing this was to make the library search function and DL_LOAD_PATH either be a special type or accept a thunk, like
ifunc
) - Preserving support for & argument syntax (although it is not actually changed here)
- Remove special parsing rules for ccall (and thus removes
Expr(:ccall)
), fixes #18687 - Make
Expr(:call, :ccall)
special in lowering (effectively, reservingccall
as a macro name) - Use
Expr(:foreigncall)
to represent the lowered ffi call (instead of expanding toExpr(:call, Core.Intrinsics.ccall)
- Adds support to ccall for calling llvm intrinsics directly (via a "llvmcall" calling convention)
- Evaluate the global arguments to the ccall macro during method definition time.
- Eliminates the call back into the runtime from inside codegen (yay!)
- Makes its semantics independent of runtime values (so that its behavior is statically compilable)
In this scheme, a call of the form ccall(to, :attrs, return_type, arg_types, args...)
would effectively lower to:
roots = cconvert.(arg_types, args)
args = unsafe_convert.(arg_types, roots)
Expr(:foreigncall, to, $return_type, $arg_types, zip(args, roots)..., Expr(:meta, :attrs, ...))
Additionally, static parameters from the containing function can be used in the arg_types
and return_type
, provided that they don't impact the layout of the type (as seen by the C ABI).
For example, f{T}(x::T) = ccall(:valid, Ptr{T}, (Ptr{T},), x)
is valid, since Ptr
is always a word-size bitstype.
But, g{T}(x::T) = ccall(:notvalid, T, (T,), x)
is not valid, since the type layout of T
is not known statically.
Packages could provide alternative macros to configure the behavior in other ways, if desired. For example, we might imagine the creation of a @extern
macro which converts extern declarations of C functions and wraps them in the appropriate callable type.
const library = "libraryname"
@extern name(ArgumentType,)::ReturnType in library # ccall to ("name", "libraryname")
@extern name(ArgumentType,)::ReturnType in library, pure, stdcall # ccall with some attributes
@extern name{T}(ArgumentType{T},)::ReturnType{T} in "libraryname" # ccall with a type parameter
@extern jlname = name(ArgumentType,)::ReturnType in library # ccall to "name" from `Module.jlname`
@extern jlname = (ArgumentType,)::ReturnType # ccall-by-pointer
Or we might use a ccall
macro that just arranges the syntax more clearly:
const library = "libraryname"
ret = @ccall name(a::ArgumentType,)::ReturnType in library # ccall to ("name", "libraryname")
ret = @ccall name(a::ArgumentType,)::ReturnType in library, pure, stdcall # ccall with some attributes
ret = @ccall name(a::ArgumentType{T},)::ReturnType{T} in "libraryname" # ccall with a type parameter
ret = @ccall $julia_ptr(a::ArgumentType,)::ReturnType # ccall-by-pointer to julia_ptr::Ptr{Cvoid}
Arbitrary attributes would be handled via syntactic symbols specified before the return type. A tuple of symbols would be interpreted to be specifying the attributes for the arguments, indexed by position.
ccall((:name, "library"),
(:attr_for_function, :attr_for_return_value,
(:attr_for_arg1, (:attr_for_arg2, :another_attr_for_arg2), (#=no attribute for arg 3=#), :arg4_attribute)))
ReturnType,
(ArgType1, ArgType2, ArgType3, ArgType4, ArgType5),
arg1,
arg2,
arg3,
arg4,
arg5)
For attributes that take integer or string arguments, those value(s) should appear immediately after it in the list. For example, __attribute__((aligned (4)))
would be expressed as ..., :aligned, 4, ...
.
If interpolation is desired, that must be done manually via @eval
:
const attrs = (:(:fastcall), :(:pure), #=(:arg_attrs, :if_we_had_arguments)=#)
@eval myfun() = ccall((:myfun, "mylib"), $(attrs...), Void, ())
No validation on attributes will be performed by the frontend, but would simply move them all into an Expr(:meta)
argument appended to the end of the Expr(:foreigncall)
argument list. The lowering for ccall
may choose to do some validation and call emit_error
or directly print a warning for invalid or unhandled attributes.
2/8/18 vtjnash: added example @ccall
syntax proposal demonstration