Skip to content

Commit 8459fdf

Browse files
committed
OpaqueClosure - Runtime & Codegen only
This is #37849 modulo inference/optimization support. This is self-contained and the functionality tests pass, though the tests testing for various optimizations of course don't (they're marked as test_broken). I believe I have addressed the review in the original PR relevant to the files extracted here.
1 parent d49ece5 commit 8459fdf

26 files changed

+616
-78
lines changed

base/Base.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ using .Libc: getpid, gethostname, time
232232

233233
include("env.jl")
234234

235+
# OpaqueClosure
236+
include("opaque_closure.jl")
237+
235238
# Concurrency
236239
include("linked_list.jl")
237240
include("condition.jl")

base/compiler/ssair/ir.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ function is_relevant_expr(e::Expr)
375375
:gc_preserve_begin, :gc_preserve_end,
376376
:foreigncall, :isdefined, :copyast,
377377
:undefcheck, :throw_undef_if_not,
378-
:cfunction, :method, :pop_exception)
378+
:cfunction, :method, :pop_exception,
379+
:new_opaque_closure)
379380
end
380381

381382
function setindex!(x::UseRef, @nospecialize(v))

base/compiler/validation.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}(
3030
:thunk => 1:1,
3131
:throw_undef_if_not => 2:2,
3232
:aliasscope => 0:0,
33-
:popaliasscope => 0:0
33+
:popaliasscope => 0:0,
34+
:new_opaque_closure => 4:typemax(Int)
3435
)
3536

3637
# @enum isn't defined yet, otherwise I'd use it for this

base/opaque_closure.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function show(io::IO, oc::Core.OpaqueClosure{A, R}) where {A, R}
2+
show_tuple_as_call(io, Symbol(""), A; hasfirst=false)
3+
print(io, "::", R)
4+
print(io, "->◌")
5+
end
6+
7+
function show(io::IO, ::MIME"text/plain", oc::Core.OpaqueClosure{A, R}) where {A, R}
8+
show(io, oc)
9+
end
10+
11+
"""
12+
@opaque (args...)->...
13+
14+
Marks a given closure as "opaque". Opaque closures capture the
15+
world age of their creation (as opposed to their invocation).
16+
This allows for more aggressive optimization of the capture
17+
list, but trades off against the ability to inline opaque
18+
closures at the call site, if their creation is not statically
19+
visible.
20+
"""
21+
macro opaque(ex)
22+
esc(Expr(:opaque_closure, ex))
23+
end

base/reflection.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,9 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple);
11401140
if isa(f, Core.Builtin)
11411141
throw(ArgumentError("argument is not a generic function"))
11421142
end
1143+
if isa(f, Core.OpaqueClosure)
1144+
return code_typed_opaque_closure(f, types; optimize, debuginfo, world, interp)
1145+
end
11431146
ft = Core.Typeof(f)
11441147
if isa(types, Type)
11451148
u = unwrap_unionall(types)
@@ -1186,6 +1189,24 @@ function code_typed_by_type(@nospecialize(tt::Type);
11861189
return asts
11871190
end
11881191

1192+
function code_typed_opaque_closure(closure::Core.OpaqueClosure, @nospecialize(types=Tuple);
1193+
optimize=true,
1194+
debuginfo::Symbol=:default,
1195+
world = get_world_counter(),
1196+
interp = Core.Compiler.NativeInterpreter(world))
1197+
ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
1198+
if isa(closure.ci, CodeInfo)
1199+
code = copy(closure.ci)
1200+
debuginfo === :none && remove_linenums!(code)
1201+
return Any[Pair{CodeInfo,Any}(code, code.rettype)]
1202+
else
1203+
mi = Core.Compiler.specialize_method(closure.ci::Method, signature_type(closure.env, types), Core.svec())
1204+
(code, ty) = Core.Compiler.typeinf_code(interp, mi, optimize)
1205+
debuginfo === :none && remove_linenums!(code)
1206+
return Any[Pair{CodeInfo,Any}(code, ty)]
1207+
end
1208+
end
1209+
11891210
function return_types(@nospecialize(f), @nospecialize(types=Tuple), interp=Core.Compiler.NativeInterpreter())
11901211
ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
11911212
if isa(f, Core.Builtin)

src/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ RUNTIME_SRCS := \
4545
simplevector runtime_intrinsics precompile \
4646
threading partr stackwalk gc gc-debug gc-pages gc-stacks method \
4747
jlapi signal-handling safepoint timing subtype \
48-
crc32c APInt-C processor ircode
48+
crc32c APInt-C processor ircode opaque_closure
4949
SRCS := jloptions runtime_ccall rtutils
5050

5151
LLVMLINK :=

src/ast.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jl_sym_t *pop_exception_sym;
4343
jl_sym_t *exc_sym; jl_sym_t *error_sym;
4444
jl_sym_t *new_sym; jl_sym_t *using_sym;
4545
jl_sym_t *splatnew_sym;
46+
jl_sym_t *new_opaque_closure_sym;
4647
jl_sym_t *const_sym; jl_sym_t *thunk_sym;
4748
jl_sym_t *foreigncall_sym; jl_sym_t *as_sym;
4849
jl_sym_t *global_sym; jl_sym_t *list_sym;
@@ -359,6 +360,7 @@ void jl_init_common_symbols(void)
359360
pop_exception_sym = jl_symbol("pop_exception");
360361
new_sym = jl_symbol("new");
361362
splatnew_sym = jl_symbol("splatnew");
363+
new_opaque_closure_sym = jl_symbol("new_opaque_closure");
362364
const_sym = jl_symbol("const");
363365
global_sym = jl_symbol("global");
364366
thunk_sym = jl_symbol("thunk");

src/builtins.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,8 @@ void jl_init_intrinsic_functions(void) JL_GC_DISABLED
15031503
inm->parent = jl_core_module;
15041504
jl_set_const(jl_core_module, jl_symbol("Intrinsics"), (jl_value_t*)inm);
15051505
jl_mk_builtin_func(jl_intrinsic_type, "IntrinsicFunction", jl_f_intrinsic_call);
1506+
jl_mk_builtin_func(jl_unwrap_unionall(jl_opaque_closure_type),
1507+
"OpaqueClosure", jl_f_opaque_closure_call);
15061508

15071509
#define ADD_I(name, nargs) add_intrinsic(inm, #name, name);
15081510
#define ADD_HIDDEN(name, nargs)
@@ -1618,6 +1620,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
16181620
add_builtin("Ptr", (jl_value_t*)jl_pointer_type);
16191621
add_builtin("LLVMPtr", (jl_value_t*)jl_llvmpointer_type);
16201622
add_builtin("Task", (jl_value_t*)jl_task_type);
1623+
add_builtin("OpaqueClosure", (jl_value_t*)jl_opaque_closure_type);
16211624

16221625
add_builtin("AbstractArray", (jl_value_t*)jl_abstractarray_type);
16231626
add_builtin("DenseArray", (jl_value_t*)jl_densearray_type);

src/clangsa/GCChecker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ bool GCChecker::isGCTrackedType(QualType QT) {
753753
Name.endswith_lower("jl_uniontype_t") ||
754754
Name.endswith_lower("jl_method_match_t") ||
755755
Name.endswith_lower("jl_vararg_t") ||
756+
Name.endswith_lower("jl_opaque_closure_t") ||
756757
// Probably not technically true for these, but let's allow
757758
// it
758759
Name.endswith_lower("typemap_intersection_env") ||

src/codegen.cpp

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,8 @@ static const std::map<jl_fptr_args_t, JuliaFunction*> builtin_func_map = {
862862
{ &jl_f_apply_type, new JuliaFunction{"jl_f_apply_type", get_func_sig, get_func_attrs} },
863863
};
864864

865+
static const auto jl_new_opaque_closure_jlcall_func = new JuliaFunction{"jl_new_opaque_closure_jlcall", get_func_sig, get_func_attrs};
866+
865867
static int globalUnique = 0;
866868

867869
// --- code generation ---
@@ -2624,6 +2626,13 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva
26242626
return emit_box_compare(ctx, arg1, arg2, nullcheck1, nullcheck2);
26252627
}
26262628

2629+
static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
2630+
emit_function(
2631+
jl_method_instance_t *lam,
2632+
jl_code_info_t *src,
2633+
jl_value_t *jlrettype,
2634+
jl_codegen_params_t &params);
2635+
26272636
static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
26282637
const jl_cgval_t *argv, size_t nargs, jl_value_t *rt,
26292638
jl_expr_t *ex)
@@ -3228,6 +3237,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
32283237
}
32293238
return true;
32303239
}
3240+
32313241
return false;
32323242
}
32333243

@@ -4477,6 +4487,98 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
44774487
// it to the inferred type.
44784488
return mark_julia_type(ctx, val, true, (jl_value_t*)jl_any_type);
44794489
}
4490+
else if (head == new_opaque_closure_sym) {
4491+
size_t nargs = jl_array_len(ex->args);
4492+
assert(nargs >= 4 && "Not enough arguments in new_opaque_closure");
4493+
jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs);
4494+
for (size_t i = 0; i < nargs; ++i) {
4495+
argv[i] = emit_expr(ctx, args[i]);
4496+
}
4497+
const jl_cgval_t &argt = argv[0];
4498+
const jl_cgval_t &lb = argv[1];
4499+
const jl_cgval_t &ub = argv[2];
4500+
const jl_cgval_t &source = argv[3];
4501+
if (source.constant == NULL || argt.constant == NULL || lb.constant == NULL || ub.constant == NULL) {
4502+
// For now, we require non-constant source to be created using
4503+
// eval - we may lift this restriction if necessary.
4504+
emit_error(ctx, "Opaque closure source be constant");
4505+
return jl_cgval_t();
4506+
}
4507+
bool valid = jl_is_type(argt.constant) && jl_is_type(lb.constant) &&
4508+
jl_is_type(ub.constant);
4509+
if (valid && jl_is_code_info(source.constant) && jl_is_concrete_immutable(argt.constant)) {
4510+
// TODO: Emit this inline and outline it late using LLVM's coroutine
4511+
// support.
4512+
jl_code_info_t *closure_src = (jl_code_info_t *)source.constant;
4513+
std::unique_ptr<Module> closure_m;
4514+
jl_llvm_functions_t closure_decls;
4515+
4516+
jl_method_instance_t *li;
4517+
jl_value_t *closure_t;
4518+
jl_tupletype_t *env_t;
4519+
jl_svec_t *sig_args;
4520+
JL_GC_PUSH4(&li, &closure_t, &env_t, &sig_args);
4521+
4522+
li = jl_new_method_instance_uninit();
4523+
li->uninferred = (jl_value_t*)closure_src;
4524+
jl_tupletype_t *argt_typ = (jl_tupletype_t *)argt.constant;
4525+
4526+
closure_t = jl_apply_type2((jl_value_t*)jl_opaque_closure_type, (jl_value_t*)argt_typ, ub.constant);
4527+
4528+
size_t nsig = 1 + jl_svec_len(argt_typ->parameters);
4529+
sig_args = jl_alloc_svec_uninit(nsig);
4530+
jl_svecset(sig_args, 0, closure_t);
4531+
for (size_t i = 0; i < jl_svec_len(argt_typ->parameters); ++i) {
4532+
jl_svecset(sig_args, 1+i, jl_svecref(argt_typ->parameters, i));
4533+
}
4534+
li->specTypes = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(sig_args), nsig);
4535+
li->def.module = ctx.module;
4536+
std::tie(closure_m, closure_decls) = emit_function(li, closure_src,
4537+
ub.constant, ctx.emission_context);
4538+
jl_merge_module(ctx.f->getParent(), std::move(closure_m));
4539+
4540+
jl_value_t **env_component_ts = (jl_value_t**)alloca(sizeof(jl_value_t*) * (nargs-4));
4541+
for (size_t i = 0; i < nargs - 4; ++i) {
4542+
env_component_ts[i] = argv[4+i].typ;
4543+
}
4544+
4545+
env_t = jl_apply_tuple_type_v(env_component_ts, nargs-4);
4546+
4547+
// TODO: Inline the env at the end of the opaque closure and generate a descriptor for GC
4548+
jl_cgval_t env = emit_new_struct(ctx, (jl_value_t*)env_t, nargs-4, &argv[4]);
4549+
4550+
Function *specptr = ctx.f->getParent()->getFunction(closure_decls.specFunctionObject);
4551+
jl_cgval_t fptr = mark_julia_type(ctx,
4552+
specptr ? (llvm::Value*)specptr : (llvm::Value*)ConstantPointerNull::get((llvm::PointerType*)T_pvoidfunc),
4553+
false, jl_voidpointer_type);
4554+
4555+
Value *jlptr;
4556+
if (closure_decls.functionObject == "jl_fptr_args" ||
4557+
closure_decls.functionObject == "jl_fptr_sparam") {
4558+
jlptr = specptr;
4559+
} else {
4560+
jlptr = ctx.f->getParent()->getFunction(closure_decls.functionObject);
4561+
}
4562+
jl_cgval_t jlcall_ptr = mark_julia_type(ctx,
4563+
jlptr ? (llvm::Value*)jlptr : (llvm::Value*)ConstantPointerNull::get((llvm::PointerType*)T_pvoidfunc),
4564+
false, jl_voidpointer_type);
4565+
4566+
jl_cgval_t closure_fields[4] = {
4567+
env,
4568+
source,
4569+
jlcall_ptr,
4570+
fptr
4571+
};
4572+
4573+
jl_cgval_t ret = emit_new_struct(ctx, closure_t, 4, closure_fields);
4574+
JL_GC_POP();
4575+
return ret;
4576+
}
4577+
4578+
return mark_julia_type(ctx,
4579+
emit_jlcall(ctx, jl_new_opaque_closure_jlcall_func, V_rnull, argv, nargs, JLCALL_F_CC),
4580+
true, jl_any_type);
4581+
}
44804582
else if (head == exc_sym) {
44814583
return mark_julia_type(ctx,
44824584
ctx.builder.CreateCall(prepare_call(jl_current_exception_func)),
@@ -5742,15 +5844,29 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
57425844
ctx.code = src->code;
57435845

57445846
std::map<int, BasicBlock*> labels;
5847+
bool toplevel = false;
57455848
ctx.module = jl_is_method(lam->def.method) ? lam->def.method->module : lam->def.module;
57465849
ctx.linfo = lam;
5850+
ctx.name = name_from_method_instance(lam);
5851+
bool is_opaque_closure = false;
5852+
if (jl_is_method(lam->def.method)) {
5853+
ctx.nargs = lam->def.method->nargs;
5854+
}
5855+
else {
5856+
ctx.nargs = 0;
5857+
// This is an opaque closure
5858+
jl_datatype_t *closure = jl_first_argument_datatype(lam->specTypes);
5859+
if (closure && jl_is_opaque_closure_type(closure)) {
5860+
assert(jl_is_concrete_immutable(jl_tparam0(closure)));
5861+
ctx.nargs = 1 + jl_datatype_nfields(jl_tparam0(closure));
5862+
is_opaque_closure = true;
5863+
}
5864+
}
5865+
toplevel = !jl_is_method(lam->def.method);
57475866
ctx.rettype = jlrettype;
57485867
ctx.source = src;
5749-
ctx.name = name_from_method_instance(lam);
57505868
ctx.funcName = ctx.name;
57515869
ctx.spvals_ptr = NULL;
5752-
ctx.nargs = jl_is_method(lam->def.method) ? lam->def.method->nargs : 0;
5753-
bool toplevel = !jl_is_method(lam->def.method);
57545870
jl_array_t *stmts = ctx.code;
57555871
size_t stmtslen = jl_array_dim0(stmts);
57565872

@@ -5764,7 +5880,7 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
57645880

57655881
StringRef dbgFuncName = ctx.name;
57665882
int toplineno = -1;
5767-
if (jl_is_method(lam->def.method)) {
5883+
if (lam && jl_is_method(lam->def.method)) {
57685884
toplineno = lam->def.method->line;
57695885
ctx.file = jl_symbol_name(lam->def.method->file);
57705886
}
@@ -5793,7 +5909,7 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
57935909

57945910
assert(lam->specTypes); // the specTypes field should always be assigned
57955911

5796-
if (nreq > 0 && lam->def.method->isva) {
5912+
if (nreq > 0 && jl_is_method(lam->def.value) && lam->def.method->isva) {
57975913
nreq--;
57985914
va = 1;
57995915
jl_sym_t *vn = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, ctx.nargs - 1);
@@ -5820,6 +5936,15 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
58205936
if (argname == unused_sym)
58215937
continue;
58225938
jl_value_t *ty = jl_nth_slot_type(lam->specTypes, i);
5939+
// OpaqueClosure implicitly loads the env
5940+
if (i == 0 && is_opaque_closure) {
5941+
if (jl_is_array(src->slottypes)) {
5942+
ty = jl_arrayref((jl_array_t*)src->slottypes, i);
5943+
}
5944+
else {
5945+
ty = (jl_value_t*)jl_any_type;
5946+
}
5947+
}
58235948
varinfo.value = mark_julia_type(ctx, (Value*)NULL, false, ty);
58245949
}
58255950
if (va && ctx.vaSlot != -1) {
@@ -6302,6 +6427,13 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
63026427
}
63036428
}
63046429

6430+
// If this is an opaque closure, implicitly load the env
6431+
if (i == 0 && is_opaque_closure) {
6432+
theArg = convert_julia_type(ctx,
6433+
emit_getfield_knownidx(ctx, theArg, 0, (jl_datatype_t*)argType),
6434+
vi.value.typ);
6435+
}
6436+
63056437
if (vi.boxroot == NULL) {
63066438
assert(vi.value.V == NULL && "unexpected variable slot created for argument");
63076439
// keep track of original (possibly boxed) value to avoid re-boxing or moving

0 commit comments

Comments
 (0)