Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ function getmeta(ex::SyntaxTree, name::Symbol, default)
isnothing(meta) ? default : get(meta, name, default)
end

name_hint(name) = CompileHints(:name_hint, name)

#-------------------------------------------------------------------------------
# Predicates and accessors working on expression trees

Expand Down
2 changes: 1 addition & 1 deletion src/closure_conversion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)
else
access
end
elseif is_leaf(ex) || k == K"inert"
elseif is_leaf(ex) || k == K"inert" || k == K"static_eval"
ex
elseif k == K"="
convert_assignment(ctx, ex)
Expand Down
38 changes: 29 additions & 9 deletions src/desugaring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1626,17 +1626,24 @@ function expand_kw_call(ctx, srcref, farg, args, kws)
]
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
]
end
return expanded
end

function expand_ccall(ctx, ex)
@assert kind(ex) == K"call" && is_core_ref(ex[1], "ccall")
if numchildren(ex) < 4
throw(LoweringError(ex, "too few arguments to ccall"))
end
cfunc_name = ex[2]
# Detect calling convention if present.
#
# Note `@ccall` also emits `Expr(:cconv, convention, nreq)`, but this is a
# somewhat undocumented performance workaround. Instead we should just make
# sure @ccall can emit foreigncall directly and efficiently.
known_conventions = ("cdecl", "stdcall", "fastcall", "thiscall", "llvmcall")
cconv = if any(is_same_identifier_like(ex[3], id) for id in known_conventions)
ex[3]
Expand Down Expand Up @@ -1748,11 +1755,15 @@ function expand_ccall(ctx, ex)
@ast ctx ex [K"block"
sctx.stmts...
[K"foreigncall"
expand_forms_2(ctx, cfunc_name)
expand_forms_2(ctx, return_type)
[K"call"
"svec"::K"core"
expanded_types...
expand_C_library_symbol(ctx, cfunc_name)
[K"static_eval"(meta=name_hint("ccall return type"))
expand_forms_2(ctx, return_type)
]
[K"static_eval"(meta=name_hint("ccall argument type"))
[K"call"
"svec"::K"core"
expanded_types...
]
]
num_required_args::K"Integer"
if isnothing(cconv)
Expand Down Expand Up @@ -1828,6 +1839,15 @@ function expand_call(ctx, ex)
farg = ex[1]
if is_core_ref(farg, "ccall")
return expand_ccall(ctx, ex)
elseif is_core_ref(farg, "cglobal")
@chk numchildren(ex) in 2:3 (ex, "cglobal must have one or two arguments")
return @ast ctx ex [K"call"
ex[1]
expand_C_library_symbol(ctx, ex[2])
if numchildren(ex) == 3
expand_forms_2(ctx, ex[3])
end
]
end
args = copy(ex[2:end])
kws = remove_kw_args!(ctx, args)
Expand Down
10 changes: 9 additions & 1 deletion src/eval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ function to_lowered_expr(mod, ex, ssa_offset=0)
# Unpack K"Symbol" QuoteNode as `Expr(:meta)` requires an identifier here.
args[1] = args[1].value
Expr(:meta, args...)
elseif k == K"static_eval"
@assert numchildren(ex) == 1
to_lowered_expr(mod, ex[1], ssa_offset)
elseif k == K"cfunction"
args = Any[to_lowered_expr(mod, e, ssa_offset) for e in children(ex)]
if kind(ex[2]) == K"static_eval"
args[2] = QuoteNode(args[2])
end
Expr(:cfunction, args...)
else
# Allowed forms according to https://docs.julialang.org/en/v1/devdocs/ast/
#
Expand All @@ -324,7 +333,6 @@ function to_lowered_expr(mod, ex, ssa_offset=0)
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)
Expand Down
5 changes: 5 additions & 0 deletions src/kinds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ function _register_kinds()
# For expr-macro compatibility; gone after expansion
"escape"
"hygienic_scope"
# An expression which will eventually be evaluated "statically" in
# the context of a CodeInfo and thus allows access only to globals
# and static parameters. Used for ccall, cfunction, cglobal
# TODO: Use this for GeneratedFunctionStub also?
"static_eval"
# Catch-all for additional syntax extensions without the need to
# extend `Kind`. Known extensions include:
# locals, islocal
Expand Down
62 changes: 9 additions & 53 deletions src/linear_ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

function is_valid_ir_argument(ctx, ex)
k = kind(ex)
if is_simple_atom(ctx, ex) || k in KSet"inert top core quote"
if is_simple_atom(ctx, ex) || k in KSet"inert top core quote static_eval"
true
elseif k == K"BindingId"
binfo = lookup_binding(ctx, ex)
Expand Down Expand Up @@ -112,7 +112,7 @@ end
function is_simple_arg(ctx, ex)
k = kind(ex)
return is_simple_atom(ctx, ex) || k == K"BindingId" || k == K"quote" || k == K"inert" ||
k == K"top" || k == K"core" || k == K"globalref"
k == K"top" || k == K"core" || k == K"globalref" || k == K"static_eval"
end

function is_single_assign_var(ctx::LinearIRContext, ex)
Expand All @@ -128,7 +128,7 @@ function is_const_read_arg(ctx, ex)
# Even if we have side effects, we know that singly-assigned
# locals cannot be affected by them so we can inline them anyway.
# TODO from flisp: "We could also allow const globals here"
return k == K"inert" || k == K"top" || k == K"core" ||
return k == K"inert" || k == K"top" || k == K"core" || k == K"static_eval" ||
is_simple_atom(ctx, ex) || is_single_assign_var(ctx, ex)
end

Expand Down Expand Up @@ -167,19 +167,6 @@ function compile_args(ctx, args)
return args_out
end

# Compile the (sym,lib) argument to ccall/cglobal
function compile_C_library_symbol(ctx, ex)
if kind(ex) == K"call" && kind(ex[1]) == K"core" && ex[1].name_val == "tuple"
# Tuples like core.tuple(:funcname, mylib_name) are allowed and are
# kept inline, but may only reference globals.
check_no_local_bindings(ctx, ex,
"function name and library expression cannot reference local variables")
ex
else
only(compile_args(ctx, (ex,)))
end
end

function emit(ctx::LinearIRContext, ex)
push!(ctx.code, ex)
return ex
Expand Down Expand Up @@ -593,7 +580,7 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
k = kind(ex)
if k == K"BindingId" || is_literal(k) || k == K"quote" || k == K"inert" ||
k == K"top" || k == K"core" || k == K"Value" || k == K"Symbol" ||
k == K"SourceLocation"
k == K"SourceLocation" || k == K"static_eval"
if in_tail_pos
emit_return(ctx, ex)
elseif needs_value
Expand All @@ -614,39 +601,7 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
nothing
elseif k == K"call" || k == K"new" || k == K"splatnew" || k == K"foreigncall" ||
k == K"new_opaque_closure" || k == K"cfunction"
if k == K"foreigncall"
args = SyntaxList(ctx)
push!(args, compile_C_library_symbol(ctx, ex[1]))
# 2nd to 5th arguments of foreigncall are special. They must be
# left in place but cannot reference locals.
check_no_local_bindings(ctx, ex[2], "ccall return type cannot reference local variables")
for argt in children(ex[3])
check_no_local_bindings(ctx, argt,
"ccall argument types cannot reference local variables")
end
append!(args, ex[2:5])
append!(args, compile_args(ctx, ex[6:end]))
args
elseif k == K"cfunction"
# Arguments of cfunction must be left in place except for argument
# 2 (fptr)
args = copy(children(ex))
args[2] = only(compile_args(ctx, args[2:2]))
check_no_local_bindings(ctx, ex[3],
"cfunction return type cannot reference local variables")
for arg in children(ex[4])
check_no_local_bindings(ctx, arg,
"cfunction argument cannot reference local variables")
end
elseif k == K"call" && is_core_ref(ex[1], "cglobal")
args = SyntaxList(ctx)
push!(args, ex[1])
push!(args, compile_C_library_symbol(ctx, ex[2]))
append!(args, compile_args(ctx, ex[3:end]))
else
args = compile_args(ctx, children(ex))
end
callex = makenode(ctx, ex, k, args)
callex = makenode(ctx, ex, k, compile_args(ctx, children(ex)))
if in_tail_pos
emit_return(ctx, ex, callex)
elseif needs_value
Expand Down Expand Up @@ -909,7 +864,7 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
end

function _remove_vars_with_isdefined_check!(vars, ex)
if is_leaf(ex) || is_quoted(ex)
if is_leaf(ex) || is_quoted(ex) || kind(ex) == K"static_eval"
return
elseif kind(ex) == K"isdefined"
delete!(vars, ex[1].var_id)
Expand Down Expand Up @@ -1017,10 +972,11 @@ function _renumber(ctx, ssa_rewrites, slot_rewrites, label_table, ex)
makeleaf(ctx, ex, K"globalref", binfo.name, mod=binfo.mod)
end
end
elseif k == K"meta"
elseif k == K"meta" || k == K"static_eval"
# Somewhat-hack for Expr(:meta, :generated, gen) which has
# weird top-level semantics for `gen`, but we still need to translate
# the binding it contains to a globalref.
# the binding it contains to a globalref. (TODO: use
# static_eval for this meta, somehow)
mapchildren(ctx, ex) do e
_renumber(ctx, ssa_rewrites, slot_rewrites, label_table, e)
end
Expand Down
7 changes: 7 additions & 0 deletions src/macro_expansion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ function Base.showerror(io::IO, exc::MacroExpansionError)
pos == :end ? (lb+1:lb) :
error("Unknown position $pos")
highlight(io, src.file, byterange, note=exc.msg)
if !isnothing(exc.err)
print(io, "\nCaused by:\n")
showerror(io, exc.err)
end
end

function eval_macro_name(ctx::MacroExpansionContext, mctx::MacroContext, ex::SyntaxTree)
Expand Down Expand Up @@ -222,6 +226,9 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
if all(==('_'), name_str)
@ast ctx ex ex=>K"Placeholder"
elseif is_ccall_or_cglobal(name_str)
# Lower special identifiers `cglobal` and `ccall` to `K"core"`
# psuedo-refs very early so that cglobal and ccall can never be
# turned into normal bindings (eg, assigned to)
@ast ctx ex name_str::K"core"
else
layerid = get(ex, :scope_layer, ctx.current_layer.id)
Expand Down
25 changes: 25 additions & 0 deletions src/scope_analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,24 @@ function init_closure_bindings!(ctx, fname)
end
end

function find_any_local_binding(ctx, ex)
k = kind(ex)
if k == K"BindingId"
bkind = lookup_binding(ctx, ex.var_id).kind
if bkind != :global && bkind != :static_parameter
return ex
end
elseif !is_leaf(ex) && !is_quoted(ex)
for e in children(ex)
r = find_any_local_binding(ctx, e)
if !isnothing(r)
return r
end
end
end
return nothing
end

# Update ctx.bindings and ctx.lambda_bindings metadata based on binding usage
function analyze_variables!(ctx, ex)
k = kind(ex)
Expand All @@ -649,6 +667,13 @@ function analyze_variables!(ctx, ex)
end
elseif is_leaf(ex) || is_quoted(ex)
return
elseif k == K"static_eval"
badvar = find_any_local_binding(ctx, ex[1])
if !isnothing(badvar)
name_hint = getmeta(ex, :name_hint, "syntax")
throw(LoweringError(badvar, "$(name_hint) cannot reference local variable"))
end
return
elseif k == K"local" || k == K"global"
# Presence of BindingId within local/global is ignored.
return
Expand Down
Loading
Loading