Skip to content

RFC: CodegenHooks for external language implementations #19290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 28, 2016
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
20 changes: 18 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,18 @@ function uncompressed_ast(m::Method, s::CodeInfo)
return s
end

# this type mirrors jl_cghooks_t (documented in julia.h)
immutable CodegenHooks
module_setup::Ptr{Void}
module_activation::Ptr{Void}
raise_exception::Ptr{Void}

CodegenHooks(;module_setup=nothing, module_activation=nothing, raise_exception=nothing) =
new(pointer_from_objref(module_setup),
pointer_from_objref(module_activation),
pointer_from_objref(raise_exception))
end

# this type mirrors jl_cgparams_t (documented in julia.h)
immutable CodegenParams
cached::Cint
Expand All @@ -512,14 +524,18 @@ immutable CodegenParams
static_alloc::Cint
dynamic_alloc::Cint

hooks::CodegenHooks

CodegenParams(;cached::Bool=true,
runtime::Bool=true, exceptions::Bool=true,
track_allocations::Bool=true, code_coverage::Bool=true,
static_alloc::Bool=true, dynamic_alloc::Bool=true) =
static_alloc::Bool=true, dynamic_alloc::Bool=true,
hooks::CodegenHooks=CodegenHooks()) =
new(Cint(cached),
Cint(runtime), Cint(exceptions),
Cint(track_allocations), Cint(code_coverage),
Cint(static_alloc), Cint(dynamic_alloc))
Cint(static_alloc), Cint(dynamic_alloc),
hooks)
end

# Printing code representations in IR and assembly
Expand Down
46 changes: 43 additions & 3 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ static Value *prepare_call(Value *Callee)
__FUNCTION__, (ctx)->file.str().c_str(), *(ctx)->line);


// --- hook checks ---

#define JL_HOOK_TEST(params,hook) ((params)->hooks.hook != jl_nothing)

#define JL_HOOK_CALL(params,hook,argc,...) \
_hook_call<argc>((params)->hooks.hook, {{__VA_ARGS__}});
template<int N>
static inline void _hook_call(jl_value_t *hook, std::array<jl_value_t*,N> args) {
jl_value_t **argv;
JL_GC_PUSHARGS(argv, N+1);
argv[0] = hook;
for (int i = 0; i < N; i++)
argv[i+1] = args[i];
jl_apply(argv, N+1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to be adding calls to the runtime system inside codegen. It should actually be OK, since we aren't going to be adding this the result to the JIT, but we should make sure that the interface enforces that you can't add a function to the JIT that has been generated via a hook.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a check to compile_linfo:

if (params != &jl_default_cgparams /* fast path */ &&
    !compare_cgparams(params, &jl_default_cgparams) && params->cached)
    jl_error("functions compiled with custom codegen params mustn't be cached");

JL_GC_POP();
}


// --- string constants ---
static StringMap<GlobalVariable*> stringConstants;
static Value *stringConstPtr(IRBuilder<> &builder, const std::string &txt)
Expand Down Expand Up @@ -673,12 +691,18 @@ static void error_unless(Value *cond, const std::string &msg, jl_codectx_t *ctx)
static void raise_exception(Value *exc, jl_codectx_t *ctx,
BasicBlock *contBB=nullptr)
{
JL_FEAT_REQUIRE(ctx, runtime);
if (JL_HOOK_TEST(ctx->params, raise_exception)) {
JL_HOOK_CALL(ctx->params, raise_exception, 2,
jl_box_voidpointer(wrap(builder.GetInsertBlock())),
jl_box_voidpointer(wrap(exc)));
} else {
JL_FEAT_REQUIRE(ctx, runtime);
#if JL_LLVM_VERSION >= 30700
builder.CreateCall(prepare_call(jlthrow_func), { exc });
builder.CreateCall(prepare_call(jlthrow_func), { exc });
#else
builder.CreateCall(prepare_call(jlthrow_func), exc);
builder.CreateCall(prepare_call(jlthrow_func), exc);
#endif
}
builder.CreateUnreachable();
if (!contBB) {
contBB = BasicBlock::Create(jl_LLVMContext, "after_throw", ctx->f);
Expand Down Expand Up @@ -1849,3 +1873,19 @@ static Value *emit_defer_signal(jl_codectx_t *ctx)
offsetof(jl_tls_states_t, defer_signal) / sizeof(sig_atomic_t));
return builder.CreateGEP(ptls, ArrayRef<Value*>(offset), "jl_defer_signal");
}

static int compare_cgparams(const jl_cgparams_t *a, const jl_cgparams_t *b)
{
return (a->cached == b->cached) &&
// language features
(a->runtime == b->runtime) &&
(a->exceptions == b->exceptions) &&
(a->track_allocations == b->track_allocations) &&
(a->code_coverage == b->code_coverage) &&
(a->static_alloc == b->static_alloc) &&
(a->dynamic_alloc == b->dynamic_alloc) &&
// hooks
(a->hooks.module_setup == b->hooks.module_setup) &&
(a->hooks.module_activation == b->hooks.module_activation) &&
(a->hooks.raise_exception == b->hooks.raise_exception);
}
56 changes: 36 additions & 20 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <sstream>
#include <fstream>
#include <map>
#include <array>
#include <vector>
#include <set>
#include <cstdio>
Expand Down Expand Up @@ -843,6 +844,11 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
jl_method_instance_t *li = *pli;
assert(jl_is_method_instance(li));
jl_llvm_functions_t decls = {};

if (params != &jl_default_cgparams /* fast path */ &&
!compare_cgparams(params, &jl_default_cgparams) && params->cached)
jl_error("functions compiled with custom codegen params mustn't be cached");

// Step 1. See if it is already compiled,
// Get the codegen lock,
// And get the source
Expand Down Expand Up @@ -946,25 +952,30 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
Function *f = (Function*)decls.functionObject;
Function *specf = (Function*)decls.specFunctionObject;

// Step 4. Prepare debug info to receive this function
// record that this function name came from this linfo,
// so we can build a reverse mapping for debug-info.
bool toplevel = li->def == NULL;
if (!toplevel) {
const DataLayout &DL =
#if JL_LLVM_VERSION >= 30500
m->getDataLayout();
#else
*jl_data_layout;
#endif
// but don't remember toplevel thunks because
// they may not be rooted in the gc for the life of the program,
// and the runtime doesn't notify us when the code becomes unreachable :(
jl_add_linfo_in_flight((specf ? specf : f)->getName(), li, DL);
}

// Step 5. Add the result to the execution engine now
jl_finalize_module(m.release(), !toplevel);
if (JL_HOOK_TEST(params, module_activation)) {
JL_HOOK_CALL(params, module_activation, 1, jl_box_voidpointer(wrap(m.release())));
} else {
// Step 4. Prepare debug info to receive this function
// record that this function name came from this linfo,
// so we can build a reverse mapping for debug-info.
bool toplevel = li->def == NULL;
if (!toplevel) {
const DataLayout &DL =
#if JL_LLVM_VERSION >= 30500
m->getDataLayout();
#else
*jl_data_layout;
#endif
// but don't remember toplevel thunks because
// they may not be rooted in the gc for the life of the program,
// and the runtime doesn't notify us when the code becomes unreachable :(
jl_add_linfo_in_flight((specf ? specf : f)->getName(), li, DL);
}

// Step 5. Add the result to the execution engine now
jl_finalize_module(m.release(), !toplevel);
}

if (world && li->jlcall_api != 2) {
// if not inlineable, code won't be needed again
Expand Down Expand Up @@ -1009,8 +1020,13 @@ static Value *getModuleFlag(Module *m, StringRef Key)
#define getModuleFlag(m,str) m->getModuleFlag(str)
#endif

static void jl_setup_module(Module *m)
static void jl_setup_module(Module *m, const jl_cgparams_t *params = &jl_default_cgparams)
{
if (JL_HOOK_TEST(params, module_setup)) {
JL_HOOK_CALL(params, module_setup, 1, jl_box_voidpointer(wrap(m)));
return;
}

// Some linkers (*cough* OS X) don't understand DWARF v4, so we use v2 in
// imaging mode. The structure of v4 is slightly nicer for debugging JIT
// code.
Expand Down Expand Up @@ -4286,7 +4302,7 @@ static std::unique_ptr<Module> emit_function(

ctx.sret = false;
Module *M = new Module(ctx.name, jl_LLVMContext);
jl_setup_module(M);
jl_setup_module(M, params);
if (specsig) { // assumes !va and !needsparams
std::vector<Type*> fsig(0);
Type *rt;
Expand Down
6 changes: 5 additions & 1 deletion src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ JL_DLLEXPORT jl_value_t *jl_emptytuple=NULL;
jl_svec_t *jl_emptysvec;
jl_value_t *jl_nothing;

const jl_cgparams_t jl_default_cgparams = {1, 1, 1, 1, 1, 1, 1};
jl_cgparams_t jl_default_cgparams = {1, 1, 1, 1, 1, 1, 1, {NULL, NULL, NULL}};

// --- type properties and predicates ---

Expand Down Expand Up @@ -3523,6 +3523,10 @@ void jl_init_types(void)
jl_methtable_type = jl_new_uninitialized_datatype();
jl_nothing = jl_gc_alloc(ptls, 0, NULL);

jl_default_cgparams.hooks.module_setup = jl_nothing;
jl_default_cgparams.hooks.module_activation = jl_nothing;
jl_default_cgparams.hooks.raise_exception = jl_nothing;

jl_emptysvec = (jl_svec_t*)jl_gc_alloc(ptls, sizeof(void*),
jl_simplevector_type);
jl_svec_set_len_unsafe(jl_emptysvec, 0);
Expand Down
23 changes: 22 additions & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,25 @@ typedef struct {

// codegen interface ----------------------------------------------------------

typedef struct {
// to disable a hook: set to NULL or nothing

// module setup: prepare a module for code emission (data layout, DWARF version, ...)
// parameters: LLVMModuleRef as Ptr{Void}
// return value: none
jl_value_t *module_setup;

// module activation: registers debug info, adds module to JIT
// parameters: LLVMModuleRef as Ptr{Void}
// return value: none
jl_value_t *module_activation;

// exception raising: emit LLVM instructions to raise an exception
// parameters: LLVMBasicBlockRef as Ptr{Void}, LLVMValueRef as Ptr{Void}
// return value: none
jl_value_t *raise_exception;
} jl_cghooks_t;

typedef struct {
int cached; // can the compiler use/populate the compilation cache?

Expand All @@ -1780,8 +1799,10 @@ typedef struct {
int code_coverage; // can we measure coverage (don't if disallowed)?
int static_alloc; // is the compiler allowed to allocate statically?
int dynamic_alloc; // is the compiler allowed to allocate dynamically (requires runtime)?

jl_cghooks_t hooks;
} jl_cgparams_t;
extern JL_DLLEXPORT const jl_cgparams_t jl_default_cgparams;
extern JL_DLLEXPORT jl_cgparams_t jl_default_cgparams;

#ifdef __cplusplus
}
Expand Down