Skip to content

Commit

Permalink
compress AST in memory, not just on disk
Browse files Browse the repository at this point in the history
sysimg size will be exactly the same,
but seems to be pretty significant savings in memory and initial GC time
(about 10% on each)
  • Loading branch information
vtjnash committed Mar 7, 2017
1 parent 8dc7064 commit 2a19b1a
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 110 deletions.
66 changes: 31 additions & 35 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,19 @@ end
# create copies of the CodeInfo definition, and any fields that type-inference might modify
# TODO: post-inference see if we can swap back to the original arrays
function get_source(li::MethodInstance)
src = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), li.def.source)
if isa(src.code, Array{UInt8,1})
src.code = ccall(:jl_uncompress_ast, Any, (Any, Any), li.def, src.code)
if isa(li.def.source, Array{UInt8,1})
src = ccall(:jl_uncompress_ast, Any, (Any, Any, Cint), li.def, li.def.source, true)
else
src = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), li.def.source)
src.code = copy_exprargs(src.code)
src.slotnames = copy(src.slotnames)
src.slotflags = copy(src.slotflags)
end
src.slotnames = copy(src.slotnames)
src.slotflags = copy(src.slotflags)
return src
end

function get_staged(li::MethodInstance)
src = ccall(:jl_code_for_staged, Any, (Any,), li)::CodeInfo
if isa(src.code, Array{UInt8,1})
src.code = ccall(:jl_uncompress_ast, Any, (Any, Any), li.def, src.code)
end
return src
return ccall(:jl_code_for_staged, Any, (Any,), li)::CodeInfo
end


Expand Down Expand Up @@ -1565,7 +1561,7 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype::ANY, sv::InferenceState)
meth = meth[1]::SimpleVector
method = meth[3]::Method
# TODO: check pure on the inferred thunk
if method.isstaged || !method.source.pure
if method.isstaged || !method.pure
return false
end

Expand Down Expand Up @@ -2501,7 +2497,6 @@ function typeinf_code(linfo::MethodInstance, optimize::Bool, cached::Bool,
tree.slottypes = nothing
tree.ssavaluetypes = 0
tree.inferred = true
tree.pure = true
tree.inlineable = true
i == 2 && ccall(:jl_typeinf_end, Void, ())
return svec(linfo, tree, linfo.rettype)
Expand Down Expand Up @@ -2947,9 +2942,6 @@ function optimize(me::InferenceState)
end
end
end
if proven_pure
me.src.pure = true
end

if proven_pure && !coverage_enabled()
# use constant calling convention
Expand Down Expand Up @@ -3011,7 +3003,7 @@ function finish(me::InferenceState)
end
if me.const_api
# use constant calling convention
inferred_result = inferred_const
inferred_result = nothing
else
inferred_result = me.src
end
Expand All @@ -3024,7 +3016,7 @@ function finish(me::InferenceState)
inferred_result = nothing
else
# compress code for non-toplevel thunks
inferred_result.code = ccall(:jl_compress_ast, Any, (Any, Any), me.linfo.def, inferred_result.code)
inferred_result = ccall(:jl_compress_ast, Any, (Any, Any), me.linfo.def, inferred_result)
end
end
end
Expand Down Expand Up @@ -3797,10 +3789,10 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
if f === return_type
if isconstType(e.typ)
return inline_as_constant(e.typ.parameters[1], argexprs, sv, invoke_data)
elseif isa(e.typ,Const)
elseif isa(e.typ, Const)
return inline_as_constant(e.typ.val, argexprs, sv, invoke_data)
end
elseif method.source.pure && isa(e.typ,Const) && e.typ.actual
elseif method.pure && isa(e.typ, Const) && e.typ.actual
return inline_as_constant(e.typ.val, argexprs, sv, invoke_data)
end
end
Expand Down Expand Up @@ -3857,13 +3849,13 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
if linfo.jlcall_api == 2
# in this case function can be inlined to a constant
add_backedge(linfo, sv)
return inline_as_constant(linfo.inferred, argexprs, sv, invoke_data)
return inline_as_constant(linfo.inferred_const, argexprs, sv, invoke_data)
end

# see if the method has a current InferenceState frame
# or existing inferred code info
frame = nothing # Union{Void, InferenceState}
src = nothing # Union{Void, CodeInfo}
inferred = nothing # Union{Void, CodeInfo}
if force_infer && isa(atypes[3], Const)
# Since we inferred this with the information that atypes[3]::Const,
# must inline with that same information.
Expand All @@ -3877,9 +3869,9 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
frame.stmt_types[1][3] = VarState(atypes[3], false)
typeinf_loop(frame)
else
if isdefined(linfo, :inferred) && isa(linfo.inferred, CodeInfo) && (linfo.inferred::CodeInfo).inferred
if isdefined(linfo, :inferred) && linfo.inferred !== nothing
# use cache
src = linfo.inferred
inferred = linfo.inferred
elseif linfo.inInference
# use WIP
frame = typeinf_active(linfo)
Expand All @@ -3896,7 +3888,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
if isa(frame, InferenceState)
frame = frame::InferenceState
linfo = frame.linfo
src = frame.src
inferred = frame.src
if frame.const_api # handle like jlcall_api == 2
if frame.inferred || !frame.cached
add_backedge(frame.linfo, sv)
Expand All @@ -3916,16 +3908,27 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
rettype = linfo.rettype
end

inferred === nothing && return invoke_NF(argexprs0, e.typ, atypes, sv,
atype_unlimited, invoke_data)

# check that the code is inlineable
isa(src, CodeInfo) || return invoke_NF(argexprs0, e.typ, atypes, sv,
atype_unlimited, invoke_data)
src = src::CodeInfo
ast = src.code
if isa(inferred, CodeInfo)
src = inferred
else
src = ccall(:jl_uncompress_ast, Any, (Any, Any, Cint), method, inferred, false)::CodeInfo
end
if !src.inferred || !src.inlineable
return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited,
invoke_data)
end

if isa(inferred, CodeInfo)
ast = copy_exprargs(inferred.code)
else
ast = (ccall(:jl_uncompress_ast, Any, (Any, Any, Cint), method, inferred, true)::CodeInfo).code
end
ast = ast::Array{Any,1}

# create the backedge
if isa(frame, InferenceState) && !frame.inferred && frame.cached
# in this case, the actual backedge linfo hasn't been computed
Expand All @@ -3948,13 +3951,6 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference

nm = length(unwrap_unionall(metharg).parameters)

if !isa(ast, Array{Any,1})
ast = ccall(:jl_uncompress_ast, Any, (Any, Any), method, ast)
else
ast = copy_exprargs(ast)
end
ast = ast::Array{Any,1}

body = Expr(:block)
body.args = ast
propagate_inbounds = src.propagate_inbounds
Expand Down
10 changes: 5 additions & 5 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function arg_decl_parts(m::Method)
sig = sig.body
end
if isdefined(m, :source)
src = m.source
src = uncompressed_ast(m, m.source)
else
src = nothing
end
Expand All @@ -75,14 +75,14 @@ function kwarg_decl(m::Method, kwtype::DataType)
kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, max_world(m))
if kwli !== nothing
kwli = kwli::Method
src = kwli.source
kws = filter(x->!('#' in string(x)), src.slotnames[kwli.nargs+1:end])
src = uncompressed_ast(kwli, kwli.source)
kws = filter(x -> !('#' in string(x)), src.slotnames[(kwli.nargs + 1):end])
# ensure the kwarg... is always printed last. The order of the arguments are not
# necessarily the same as defined in the function
i = findfirst(x -> endswith(string(x), "..."), kws)
i==0 && return kws
i == 0 && return kws
push!(kws, kws[i])
return deleteat!(kws,i)
return deleteat!(kws, i)
end
return ()
end
Expand Down
12 changes: 3 additions & 9 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,7 @@ Returns an array of lowered ASTs for the methods matching the given generic func
"""
function code_lowered(f::ANY, t::ANY=Tuple)
asts = map(methods(f, t)) do m
m = m::Method
return uncompressed_ast(m, m.source)
return uncompressed_ast(m::Method)
end
return asts
end
Expand Down Expand Up @@ -639,13 +638,8 @@ end
isempty(mt::MethodTable) = (mt.defs === nothing)

uncompressed_ast(m::Method) = uncompressed_ast(m, m.source)
function uncompressed_ast(m::Method, s::CodeInfo)
if isa(s.code, Array{UInt8,1})
s = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), s)
s.code = ccall(:jl_uncompress_ast, Array{Any,1}, (Any, Any), m, s.code)
end
return s
end
uncompressed_ast(m::Method, s::CodeInfo) = s
uncompressed_ast(m::Method, s::Array{UInt8,1}) = ccall(:jl_uncompress_ast, Any, (Any, Any, Cint), m, s, true)::CodeInfo

# this type mirrors jl_cghooks_t (documented in julia.h)
struct CodegenHooks
Expand Down
1 change: 1 addition & 0 deletions base/serialize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method})
meth.isva = isva
# TODO: compress template
meth.source = template
meth.pure = template.pure
if isstaged
linfo = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ())
linfo.specTypes = Tuple
Expand Down
6 changes: 3 additions & 3 deletions doc/src/devdocs/ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ A unique'd container describing the shared metadata for a single method.

* `source`

The original source code (compressed).
The original source code (usually compressed).

* `roots`

Pointers to non-AST things that have been interpolated into the AST, required by
compression of the AST, type-inference, or the generation of native code.

* `nargs`, `isva`, `called`, `isstaged`
* `nargs`, `isva`, `called`, `isstaged`, `pure`

Descriptive bit-fields for the source code of this Method.

Expand Down Expand Up @@ -288,7 +288,7 @@ A temporary container for holding lowered source code.

* `code`

An `Any` array of statements, or a `UInt8` array with a compressed representation of the code.
An `Any` array of statements

* `slotnames`

Expand Down
2 changes: 1 addition & 1 deletion doc/src/devdocs/debuggingtips.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ While the addresses of many variables, like singletons, can be be useful to prin
there are a number of additional variables (see julia.h for a complete list) that are even more
useful.

* (when in `jl_apply_generic`) `f->linfo` and `jl_uncompress_ast(f->linfo, f->linfo->ast)` :: for
* (when in `jl_apply_generic`) `mfunc` and `jl_uncompress_ast(mfunc->def, mfunc->code)` :: for
figuring out a bit about the call-stack
* `jl_lineno` and `jl_filename` :: for figuring out what line in a test to go start debugging from
(or figure out how far into a file has been parsed)
Expand Down
52 changes: 35 additions & 17 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,8 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
// we waited at the lock).
if (li->def == NULL) {
src = (jl_code_info_t*)li->inferred;
if (src && (jl_value_t*)src != jl_nothing)
src = jl_uncompress_ast(li->def, (jl_array_t*)src, 1);
if (decls.functionObject != NULL || !src || !jl_is_code_info(src) || li->jlcall_api == 2) {
goto locked_out;
}
Expand All @@ -1158,6 +1160,8 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
// see if it is inferred
src = (jl_code_info_t*)li->inferred;
if (src) {
if ((jl_value_t*)src != jl_nothing)
src = jl_uncompress_ast(li->def, (jl_array_t*)src, 1);
if (!jl_is_code_info(src)) {
src = jl_type_infer(pli, world, 0);
li = *pli;
Expand All @@ -1175,6 +1179,10 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
// decl (unless compile fails), even if jlcall_api == 2
goto locked_out;
}
else {
if ((jl_value_t*)src != jl_nothing)
src = jl_uncompress_ast(li->def, (jl_array_t*)src, 1);
}
assert(jl_is_code_info(src));

// Step 2: setup global state
Expand Down Expand Up @@ -1240,14 +1248,23 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t
jl_finalize_module(m.release(), !toplevel);
}

if (world && li->jlcall_api != 2) {
// if not inlineable, code won't be needed again
if (JL_DELETE_NON_INLINEABLE && jl_options.debug_level <= 1 &&
li->def && li->inferred && jl_is_code_info(li->inferred) &&
!((jl_code_info_t*)li->inferred)->inlineable &&
li != li->def->unspecialized && !imaging_mode) {
li->inferred = jl_nothing;
}
// if not inlineable, code won't be needed again
if (JL_DELETE_NON_INLINEABLE &&
// don't delete code when debugging level >= 2
jl_options.debug_level <= 1 &&
// don't delete toplevel code
li->def &&
// don't change inferred state
li->inferred &&
// don't delete the code for the generator
li != li->def->generator &&
// don't delete inlineable code, unless it is constant
(!src->inlineable || li->jlcall_api == 2) &&
// don't delete code when generating a precompile file
!imaging_mode &&
// don't delete code when it's not actually directly being used
world) {
li->inferred = jl_nothing;
}

// Step 6: Done compiling: Restore global state
Expand Down Expand Up @@ -1412,7 +1429,7 @@ jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *_F, size_t wo
// and return its fptr instead
if (!unspec)
unspec = jl_get_unspecialized(li); // get-or-create the unspecialized version to cache the result
jl_code_info_t *src = unspec->def->isstaged ? jl_code_for_staged(unspec) : unspec->def->source;
jl_code_info_t *src = unspec->def->isstaged ? jl_code_for_staged(unspec) : (jl_code_info_t*)unspec->def->source;
fptr.fptr = unspec->fptr;
fptr.jlcall_api = unspec->jlcall_api;
if (fptr.fptr && fptr.jlcall_api) {
Expand Down Expand Up @@ -1546,11 +1563,14 @@ void *jl_get_llvmf_defn(jl_method_instance_t *linfo, size_t world, bool getwrapp

jl_code_info_t *src = (jl_code_info_t*)linfo->inferred;
JL_GC_PUSH1(&src);
if (!src || !jl_is_code_info(src)) {
if (!src || (jl_value_t*)src == jl_nothing) {
src = jl_type_infer(&linfo, world, 0);
if (!src)
src = linfo->def->isstaged ? jl_code_for_staged(linfo) : linfo->def->source;
src = linfo->def->isstaged ? jl_code_for_staged(linfo) : (jl_code_info_t*)linfo->def->source;
}
if (!src || (jl_value_t*)src == jl_nothing)
jl_error("source not found for function");
src = jl_uncompress_ast(linfo->def, (jl_array_t*)src, 1);

// Backup the info for the nested compile
JL_LOCK(&codegen_lock);
Expand Down Expand Up @@ -1633,7 +1653,7 @@ void *jl_get_llvmf_decl(jl_method_instance_t *linfo, size_t world, bool getwrapp
jl_code_info_t *src = NULL;
src = jl_type_infer(&linfo, world, 0);
if (!src) {
src = linfo->def->isstaged ? jl_code_for_staged(linfo) : linfo->def->source;
src = linfo->def->isstaged ? jl_code_for_staged(linfo) : (jl_code_info_t*)linfo->def->source;
}
decls = jl_compile_linfo(&linfo, src, world, &params);
linfo->functionObjectsDecls = decls;
Expand Down Expand Up @@ -3286,8 +3306,8 @@ static jl_cgval_t emit_invoke(jl_expr_t *ex, jl_codectx_t *ctx)
assert(jl_is_method_instance(li));
jl_llvm_functions_t decls = jl_compile_linfo(&li, NULL, ctx->world, ctx->params);
if (li->jlcall_api == 2) {
assert(li->inferred);
return mark_julia_const(li->inferred);
assert(li->inferred_const);
return mark_julia_const(li->inferred_const);
}
if (decls.functionObject) {
int jlcall_api = jl_jlcall_api(decls.functionObject);
Expand Down Expand Up @@ -4467,7 +4487,7 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t
Function *gf_thunk = NULL;
if (specsig) {
if (lam->jlcall_api == 2) {
retval = mark_julia_const(lam->inferred);
retval = mark_julia_const(lam->inferred_const);
}
else {
assert(theFptr);
Expand Down Expand Up @@ -5031,8 +5051,6 @@ static std::unique_ptr<Module> emit_function(
jl_codectx_t ctx = {};
JL_GC_PUSH2(&ctx.code, &ctx.roots);
ctx.code = (jl_array_t*)src->code;
if (!jl_typeis(ctx.code, jl_array_any_type))
ctx.code = jl_uncompress_ast(lam->def, ctx.code);

//jl_static_show(JL_STDOUT, (jl_value_t*)ast);
//jl_printf(JL_STDOUT, "\n");
Expand Down
Loading

0 comments on commit 2a19b1a

Please sign in to comment.