From 49572a549983c8d84575a379ccf764558e1893c3 Mon Sep 17 00:00:00 2001 From: Sari Sakse Dalum Date: Fri, 19 Aug 2022 11:56:38 +0200 Subject: [PATCH 01/21] Add mutating `stat!` function for non-allocating filesystem `stat` --- base/stat.jl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/base/stat.jl b/base/stat.jl index 13dbca7780b61..99c24608daca0 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -144,14 +144,13 @@ show(io::IO, ::MIME"text/plain", st::StatStruct) = show_statstruct(io, st, false # stat & lstat functions -macro stat_call(sym, arg1type, arg) +macro stat_call!(stat_buf, sym, arg1type, arg) return quote - stat_buf = zeros(UInt8, Int(ccall(:jl_sizeof_stat, Int32, ()))) - r = ccall($(Expr(:quote, sym)), Int32, ($(esc(arg1type)), Ptr{UInt8}), $(esc(arg)), stat_buf) + r = ccall($(Expr(:quote, sym)), Int32, ($(esc(arg1type)), Ptr{UInt8}), $(esc(arg)), $(esc(stat_buf))) if !(r in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL)) uv_error(string("stat(", repr($(esc(arg))), ")"), r) end - st = StatStruct($(esc(arg)), stat_buf) + st = StatStruct($(esc(arg)), $(esc(stat_buf))) if ispath(st) != (r == 0) error("stat returned zero type for a valid path") end @@ -159,13 +158,18 @@ macro stat_call(sym, arg1type, arg) end end -stat(fd::OS_HANDLE) = @stat_call jl_fstat OS_HANDLE fd -stat(path::AbstractString) = @stat_call jl_stat Cstring path -lstat(path::AbstractString) = @stat_call jl_lstat Cstring path +stat!(stat_buf::Vector{UInt8}, fd::OS_HANDLE) = @stat_call! stat_buf jl_fstat OS_HANDLE fd +stat!(stat_buf::Vector{UInt8}, path::AbstractString) = @stat_call! stat_buf jl_stat Cstring path +lstat!(stat_buf::Vector{UInt8}, path::AbstractString) = @stat_call! stat_buf jl_lstat Cstring path if RawFD !== OS_HANDLE - global stat(fd::RawFD) = stat(Libc._get_osfhandle(fd)) + global stat!(stat_buf::Vector{UInt8}, fd::RawFD) = stat!(stat_buf, Libc._get_osfhandle(fd)) end -stat(fd::Integer) = stat(RawFD(fd)) +stat!(stat_buf::Vector{UInt8}, fd::Integer) = stat!(stat_buf, RawFD(fd)) + +stat(x) = stat!(get_stat_buf(), x) +lstat(x) = lstat!(get_stat_buf(), x) + +get_stat_buf() = zeros(UInt8, Int(ccall(:jl_sizeof_stat, Int32, ()))) """ stat(file) From 4ed7aec910cb8ef96c9bdbf510cd5d1cad391e61 Mon Sep 17 00:00:00 2001 From: Sari Sakse Dalum Date: Fri, 19 Aug 2022 11:57:16 +0200 Subject: [PATCH 02/21] Add docstrings --- base/stat.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/base/stat.jl b/base/stat.jl index 99c24608daca0..c12feb888f0bd 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -158,6 +158,13 @@ macro stat_call!(stat_buf, sym, arg1type, arg) end end +""" + stat!(stat_buf::Vector{UInt8}, file) + +Like [`stat`](@ref), but avoids internal allocations by using a pre-allocated buffer, +`stat_buf`. For a small performance gain over `stat`, consecutive calls to `stat!` can use +the same `stat_buf`. See also [`Base.Filesystem.get_stat_buf`](@ref). +""" stat!(stat_buf::Vector{UInt8}, fd::OS_HANDLE) = @stat_call! stat_buf jl_fstat OS_HANDLE fd stat!(stat_buf::Vector{UInt8}, path::AbstractString) = @stat_call! stat_buf jl_stat Cstring path lstat!(stat_buf::Vector{UInt8}, path::AbstractString) = @stat_call! stat_buf jl_lstat Cstring path @@ -169,6 +176,11 @@ stat!(stat_buf::Vector{UInt8}, fd::Integer) = stat!(stat_buf, RawFD(fd stat(x) = stat!(get_stat_buf(), x) lstat(x) = lstat!(get_stat_buf(), x) +""" + get_stat_buf() + +Return a buffer of bytes of the right size for [`stat!`](@ref). +""" get_stat_buf() = zeros(UInt8, Int(ccall(:jl_sizeof_stat, Int32, ()))) """ From 631d187b741ae0f391ba1bd067c32382f3122473 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Fri, 16 Jun 2023 10:33:32 -0300 Subject: [PATCH 03/21] codegen: pass the pgcstack as an argument to specsig calls (#50093) The safepoint at function entry made it so that every function call did a relatively expensive load from the PTLS, we can instead pass the PTLS as an argument to functions making it significantly cheaper. Also use the swift calling conventions, that together with the `swiftself` attribute makes it so it's very likely the argument is kept in a register between calls. Fixes: https://github.com/JuliaLang/julia/issues/50068 --- base/reflection.jl | 3 ++ src/codegen.cpp | 62 +++++++++++++++++++------ src/julia.h | 1 + src/llvm-ptls.cpp | 13 ++++++ stdlib/InteractiveUtils/src/codeview.jl | 2 +- test/compiler/codegen.jl | 2 +- test/llvmpasses/fastmath.jl | 2 +- test/llvmpasses/llvmcall.jl | 2 +- test/llvmpasses/loopinfo.jl | 34 +++++++------- test/llvmpasses/pipeline-o0.jl | 1 - 10 files changed, 87 insertions(+), 35 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index bcfc39d2bd3a8..96b7a832cc575 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1194,6 +1194,7 @@ struct CodegenParams gnu_pubnames::Cint debug_info_kind::Cint safepoint_on_entry::Cint + gcstack_arg::Cint lookup::Ptr{Cvoid} @@ -1203,6 +1204,7 @@ struct CodegenParams prefer_specsig::Bool=false, gnu_pubnames=true, debug_info_kind::Cint = default_debug_info_kind(), safepoint_on_entry::Bool=true, + gcstack_arg::Bool=true, lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid})), generic_context = nothing) return new( @@ -1210,6 +1212,7 @@ struct CodegenParams Cint(prefer_specsig), Cint(gnu_pubnames), debug_info_kind, Cint(safepoint_on_entry), + Cint(gcstack_arg), lookup, generic_context) end end diff --git a/src/codegen.cpp b/src/codegen.cpp index 26304c7350c5c..37281ed3038ec 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1296,6 +1296,7 @@ extern "C" { #endif (int) DICompileUnit::DebugEmissionKind::FullDebug, 1, + 1, jl_rettype_inferred_addr, NULL }; } @@ -1719,7 +1720,7 @@ jl_aliasinfo_t jl_aliasinfo_t::fromTBAA(jl_codectx_t &ctx, MDNode *tbaa) { } static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed = NULL); -static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure); +static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure, bool gcstack_arg); static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1); static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, jl_binding_t **pbnd, bool assign); @@ -4107,7 +4108,8 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos { ++EmittedSpecfunCalls; // emit specialized call site - jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, callee, specFunctionObject, specTypes, jlretty, is_opaque_closure); + bool gcstack_arg = JL_FEAT_TEST(ctx, gcstack_arg); + jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, callee, specFunctionObject, specTypes, jlretty, is_opaque_closure, gcstack_arg); FunctionType *cft = returninfo.decl.getFunctionType(); *cc = returninfo.cc; *return_roots = returninfo.return_roots; @@ -4141,7 +4143,10 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos argvals[idx] = return_roots; idx++; } - + if (gcstack_arg) { + argvals[idx] = ctx.pgcstack; + idx++; + } for (size_t i = 0; i < nargs; i++) { jl_value_t *jt = jl_nth_slot_type(specTypes, i); // n.b.: specTypes is required to be a datatype by construction for specsig @@ -4205,6 +4210,8 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos } CallInst *call = ctx.builder.CreateCall(cft, TheCallee, argvals); call->setAttributes(returninfo.attrs); + if (gcstack_arg) + call->setCallingConv(CallingConv::Swift); jl_cgval_t retval; switch (returninfo.cc) { @@ -5273,7 +5280,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met specF = closure_m.getModuleUnlocked()->getFunction(closure_decls.specFunctionObject); if (specF) { jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, NULL, - closure_decls.specFunctionObject, sigtype, rettype, true); + closure_decls.specFunctionObject, sigtype, rettype, true, JL_FEAT_TEST(ctx,gcstack_arg)); specF = cast(returninfo.decl.getCallee()); } } @@ -5786,13 +5793,15 @@ static void emit_cfunc_invalidate( DebugLoc noDbg; ctx.builder.SetCurrentDebugLocation(noDbg); allocate_gc_frame(ctx, b0); - Function::arg_iterator AI = gf_thunk->arg_begin(); SmallVector myargs(nargs); if (cc == jl_returninfo_t::SRet || cc == jl_returninfo_t::Union) ++AI; if (return_roots) ++AI; + if (JL_FEAT_TEST(ctx,gcstack_arg)){ + ++AI; // gcstack_arg + } for (size_t i = 0; i < nargs; i++) { jl_value_t *jt = jl_nth_slot_type(calltype, i); // n.b. specTypes is required to be a datatype by construction for specsig @@ -6258,8 +6267,9 @@ static Function* gen_cfun_wrapper( bool is_opaque_closure = jl_is_method(lam->def.value) && lam->def.method->is_for_opaque_closure; assert(calltype == 3); // emit a specsig call + bool gcstack_arg = JL_FEAT_TEST(ctx, gcstack_arg); StringRef protoname = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)callptr, codeinst); - jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, protoname, lam->specTypes, astrt, is_opaque_closure); + jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, protoname, lam->specTypes, astrt, is_opaque_closure, gcstack_arg); FunctionType *cft = returninfo.decl.getFunctionType(); jlfunc_sret = (returninfo.cc == jl_returninfo_t::SRet); @@ -6286,6 +6296,8 @@ static Function* gen_cfun_wrapper( AllocaInst *return_roots = emit_static_alloca(ctx, get_returnroots_type(ctx, returninfo.return_roots)); args.push_back(return_roots); } + if (gcstack_arg) + args.push_back(ctx.pgcstack); for (size_t i = 0; i < nargs + 1; i++) { // figure out how to repack the arguments jl_cgval_t &inputarg = inputargs[i]; @@ -6332,11 +6344,15 @@ static Function* gen_cfun_wrapper( emit_cfunc_invalidate(gf_thunk, returninfo.cc, returninfo.return_roots, lam->specTypes, codeinst->rettype, is_opaque_closure, nargs + 1, ctx.emission_context); theFptr = ctx.builder.CreateSelect(age_ok, theFptr, gf_thunk); } + assert(cast(theFptr->getType())->isOpaqueOrPointeeTypeMatches(returninfo.decl.getFunctionType())); CallInst *call = ctx.builder.CreateCall( returninfo.decl.getFunctionType(), theFptr, ArrayRef(args)); call->setAttributes(returninfo.attrs); + if (gcstack_arg) + call->setCallingConv(CallingConv::Swift); + switch (returninfo.cc) { case jl_returninfo_t::Boxed: retval = mark_julia_type(ctx, call, true, astrt); @@ -6710,7 +6726,11 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret args[idx] = return_roots; idx++; } - + bool gcstack_arg = JL_FEAT_TEST(ctx, gcstack_arg); + if (gcstack_arg) { + args[idx] = ctx.pgcstack; + idx++; + } bool is_opaque_closure = jl_is_method(lam->def.value) && lam->def.method->is_for_opaque_closure; for (size_t i = 0; i < jl_nparams(lam->specTypes) && idx < nfargs; ++i) { jl_value_t *ty = ((i == 0) && is_opaque_closure) ? (jl_value_t*)jl_any_type : @@ -6748,7 +6768,8 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret } CallInst *call = ctx.builder.CreateCall(f.decl, args); call->setAttributes(f.attrs); - + if (gcstack_arg) + call->setCallingConv(CallingConv::Swift); jl_cgval_t retval; if (retarg != -1) { Value *theArg; @@ -6790,7 +6811,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret return w; } -static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure) +static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure, bool gcstack_arg) { jl_returninfo_t props = {}; SmallVector fsig; @@ -6875,6 +6896,14 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value fsig.push_back(get_returnroots_type(ctx, props.return_roots)->getPointerTo(0)); } + if (gcstack_arg){ + AttrBuilder param(ctx.builder.getContext()); + param.addAttribute(Attribute::SwiftSelf); + param.addAttribute(Attribute::NonNull); + attrs.push_back(AttributeSet::get(ctx.builder.getContext(), param)); + fsig.push_back(PointerType::get(JuliaType::get_ppjlvalue_ty(ctx.builder.getContext()), 0)); + } + for (size_t i = 0; i < jl_nparams(sig); i++) { jl_value_t *jt = jl_tparam(sig, i); bool isboxed = false; @@ -6936,7 +6965,8 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value else fval = emit_bitcast(ctx, fval, ftype->getPointerTo()); } - + if (gcstack_arg && isa(fval)) + cast(fval)->setCallingConv(CallingConv::Swift); props.decl = FunctionCallee(ftype, fval); props.attrs = attributes; return props; @@ -7163,7 +7193,8 @@ static jl_llvm_functions_t Function *f = NULL; bool has_sret = false; if (specsig) { // assumes !va and !needsparams - returninfo = get_specsig_function(ctx, M, NULL, declarations.specFunctionObject, lam->specTypes, jlrettype, ctx.is_opaque_closure); + returninfo = get_specsig_function(ctx, M, NULL, declarations.specFunctionObject, lam->specTypes, + jlrettype, ctx.is_opaque_closure, JL_FEAT_TEST(ctx,gcstack_arg)); f = cast(returninfo.decl.getCallee()); has_sret = (returninfo.cc == jl_returninfo_t::SRet || returninfo.cc == jl_returninfo_t::Union); jl_init_function(f, ctx.emission_context.TargetTriple); @@ -7348,7 +7379,6 @@ static jl_llvm_functions_t ctx.spvals_ptr = &*AI++; } } - // step 6. set up GC frame allocate_gc_frame(ctx, b0); Value *last_age = NULL; @@ -7554,6 +7584,12 @@ static jl_llvm_functions_t param.addAlignmentAttr(Align(sizeof(jl_value_t*))); attrs.at(Arg->getArgNo()) = AttributeSet::get(Arg->getContext(), param); // function declaration attributes } + if (specsig && JL_FEAT_TEST(ctx, gcstack_arg)){ + Argument *Arg = &*AI; + ++AI; + AttrBuilder param(ctx.builder.getContext()); + attrs.at(Arg->getArgNo()) = AttributeSet::get(Arg->getContext(), param); + } for (i = 0; i < nreq; i++) { jl_sym_t *s = slot_symbol(ctx, i); jl_value_t *argType = jl_nth_slot_type(lam->specTypes, i); @@ -8564,7 +8600,7 @@ static jl_llvm_functions_t jl_emit_oc_wrapper(orc::ThreadSafeModule &m, jl_codeg jl_llvm_functions_t declarations; declarations.functionObject = "jl_f_opaque_closure_call"; if (uses_specsig(mi->specTypes, false, true, rettype, true)) { - jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, funcName, mi->specTypes, rettype, 1); + jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, funcName, mi->specTypes, rettype, true, JL_FEAT_TEST(ctx,gcstack_arg)); Function *gf_thunk = cast(returninfo.decl.getCallee()); jl_init_function(gf_thunk, ctx.emission_context.TargetTriple); size_t nrealargs = jl_nparams(mi->specTypes); diff --git a/src/julia.h b/src/julia.h index 2140b0ad0ab90..694a8d81b06e9 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2344,6 +2344,7 @@ typedef struct { // limited, standalone int safepoint_on_entry; // Emit a safepoint on entry to each function + int gcstack_arg; // Pass the ptls value as an argument with swiftself // Cache access. Default: jl_rettype_inferred. jl_codeinstance_lookup_t lookup; diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index 84f8d7121ff03..f69078433941f 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -314,6 +314,19 @@ bool LowerPTLS::run(bool *CFGModified) for (auto it = pgcstack_getter->user_begin(); it != pgcstack_getter->user_end();) { auto call = cast(*it); ++it; + auto f = call->getCaller(); + Value *pgcstack = NULL; + for (Function::arg_iterator arg = f->arg_begin(); arg != f->arg_end();++arg) { + if (arg->hasSwiftSelfAttr()){ + pgcstack = &*arg; + break; + } + } + if (pgcstack) { + call->replaceAllUsesWith(pgcstack); + call->eraseFromParent(); + continue; + } assert(call->getCalledOperand() == pgcstack_getter); fix_pgcstack_use(call, pgcstack_getter, or_new, CFGModified); } diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 9ce5be9706bac..646028575d052 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -172,7 +172,7 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe raw::Bool, dump_module::Bool, syntax::Symbol, optimize::Bool, debuginfo::Symbol, binary::Bool) params = CodegenParams(debug_info_kind=Cint(0), - safepoint_on_entry=raw) + safepoint_on_entry=raw, gcstack_arg=raw) _dump_function(f, t, native, wrapper, raw, dump_module, syntax, optimize, debuginfo, binary, params) end diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index c29f82bfd6008..e93ecd232498f 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -17,7 +17,7 @@ end # The tests below assume a certain format and safepoint_on_entry=true breaks that. function get_llvm(@nospecialize(f), @nospecialize(t), raw=true, dump_module=false, optimize=true) - params = Base.CodegenParams(safepoint_on_entry=false) + params = Base.CodegenParams(safepoint_on_entry=false, gcstack_arg = false) d = InteractiveUtils._dump_function(f, t, false, false, raw, dump_module, :att, optimize, :none, false, params) sprint(print, d) end diff --git a/test/llvmpasses/fastmath.jl b/test/llvmpasses/fastmath.jl index 76b048c19a2a0..7338d1c3ccc5a 100644 --- a/test/llvmpasses/fastmath.jl +++ b/test/llvmpasses/fastmath.jl @@ -14,7 +14,7 @@ include(joinpath("..", "testhelpers", "llvmpasses.jl")) import Base.FastMath -# CHECK: call fast float @llvm.sqrt.f32(float %0) +# CHECK: call fast float @llvm.sqrt.f32(float %{{[0-9]+}}) emit(FastMath.sqrt_fast, Float32) diff --git a/test/llvmpasses/llvmcall.jl b/test/llvmpasses/llvmcall.jl index 687abe0a8cd46..a55201c3e3bc3 100644 --- a/test/llvmpasses/llvmcall.jl +++ b/test/llvmpasses/llvmcall.jl @@ -28,5 +28,5 @@ emit(foo, Core.LLVMPtr{Float32, 3}) # CHECK: call { i32, i32 } @foo({ i32, i32 } %{{[0-9]+}}) emit(foo, Foo) -# CHECK: define <2 x half> @julia_bar_{{[0-9]+}}([2 x half] +# CHECK: define {{(swiftcc )?}}<2 x half> @julia_bar_{{[0-9]+}}( emit(bar, NTuple{2, Float16}) diff --git a/test/llvmpasses/loopinfo.jl b/test/llvmpasses/loopinfo.jl index c970e07f8a125..18661ea6fde67 100644 --- a/test/llvmpasses/loopinfo.jl +++ b/test/llvmpasses/loopinfo.jl @@ -64,10 +64,10 @@ end # CHECK: call void @julia.loopinfo_marker(), {{.*}}, !julia.loopinfo [[LOOPINFO3:![0-9]+]] # LOWER-NOT: call void @julia.loopinfo_marker() # LOWER: br {{.*}}, !llvm.loop [[LOOPID3:![0-9]+]] -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL-NOT: call void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL-NOT: call {{(swiftcc )?}}void @j_iteration # FINAL: br end end @@ -90,17 +90,17 @@ end # CHECK: call void @julia.loopinfo_marker(), {{.*}}, !julia.loopinfo [[LOOPINFO4:![0-9]+]] # LOWER-NOT: call void @julia.loopinfo_marker() # LOWER: br {{.*}}, !llvm.loop [[LOOPID4:![0-9]+]] -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL: call void @j_iteration -# FINAL-NOT: call void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL-NOT: call {{(swiftcc )?}}void @j_iteration end end @@ -111,8 +111,8 @@ end 1 <= j <= I && continue @show (i,j) iteration(i) -# FINAL: call void @j_iteration -# FINAL-NOT: call void @j_iteration +# FINAL: call {{(swiftcc )?}}void @j_iteration +# FINAL-NOT: call {{(swiftcc )?}}void @j_iteration end $(Expr(:loopinfo, (Symbol("llvm.loop.unroll.disable"),))) end diff --git a/test/llvmpasses/pipeline-o0.jl b/test/llvmpasses/pipeline-o0.jl index 1b5d1df3c9f36..1075d126c59ca 100644 --- a/test/llvmpasses/pipeline-o0.jl +++ b/test/llvmpasses/pipeline-o0.jl @@ -9,7 +9,6 @@ include(joinpath("..", "testhelpers", "llvmpasses.jl")) # CHECK-LABEL: @julia_simple # CHECK-NOT: julia.get_pgcstack -# CHECK: asm # CHECK-NOT: julia.gc_alloc_obj # CHECK: ijl_gc_pool_alloc # COM: we want something vaguely along the lines of asm load from the fs register -> allocate bytes From c5b0a6cbf54e0a70ecf537984bd6063a1135b04d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Fri, 16 Jun 2023 16:32:46 -0400 Subject: [PATCH 04/21] pidlock cache file precompilation (#49052) --- base/loading.jl | 42 +++++++++++++++++++++++-- stdlib/FileWatching/docs/src/index.md | 1 + stdlib/FileWatching/src/FileWatching.jl | 10 ++++-- stdlib/FileWatching/src/pidfile.jl | 32 +++++++++++++++++-- stdlib/FileWatching/test/pidfile.jl | 8 ++--- 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index b345293e3bafc..f5c7aa28395ef 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1902,8 +1902,17 @@ function _require(pkg::PkgId, env=nothing) @goto load_from_cache end # spawn off a new incremental pre-compile task for recursive `require` calls - cachefile = compilecache(pkg, path) - if isa(cachefile, Exception) + cachefile_or_module = maybe_cachefile_lock(pkg, path) do + # double-check now that we have lock + m = _require_search_from_serialized(pkg, path, UInt128(0)) + m isa Module && return m + compilecache(pkg, path) + end + cachefile_or_module isa Module && return cachefile_or_module::Module + cachefile = cachefile_or_module + if isnothing(cachefile) # maybe_cachefile_lock returns nothing if it had to wait for another process + @goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search + elseif isa(cachefile, Exception) if precompilableerror(cachefile) verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug @logmsg verbosity "Skipping precompilation since __precompile__(false). Importing $pkg." @@ -2805,6 +2814,35 @@ function show(io::IO, cf::CacheFlags) print(io, ", opt_level = ", cf.opt_level) end +# Set by FileWatching.__init__() +global mkpidlock_hook +global trymkpidlock_hook +global parse_pidfile_hook + +# allows processes to wait if another process is precompiling a given source already +function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String) + if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook) + pidfile = string(srcpath, ".pidlock") + cachefile = invokelatest(trymkpidlock_hook, f, pidfile) + if cachefile === false + pid, hostname, age = invokelatest(parse_pidfile_hook, pidfile) + verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug + if isempty(hostname) || hostname == gethostname() + @logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $pkg" + else + @logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $pkg" + end + # wait until the lock is available, but don't actually acquire it + # returning nothing indicates a process waited for another + return invokelatest(mkpidlock_hook, Returns(nothing), pidfile) + end + return cachefile + else + # for packages loaded before FileWatching.__init__() + f() + end +end + # returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey # otherwise returns the list of dependencies to also check @constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false) diff --git a/stdlib/FileWatching/docs/src/index.md b/stdlib/FileWatching/docs/src/index.md index 6c332511f578f..a420d49232345 100644 --- a/stdlib/FileWatching/docs/src/index.md +++ b/stdlib/FileWatching/docs/src/index.md @@ -20,6 +20,7 @@ A simple utility tool for creating advisory pidfiles (lock files). ```@docs mkpidlock +trymkpidlock close(lock::LockMonitor) ``` diff --git a/stdlib/FileWatching/src/FileWatching.jl b/stdlib/FileWatching/src/FileWatching.jl index 17ae24460db6b..2a654547ae6e3 100644 --- a/stdlib/FileWatching/src/FileWatching.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -18,7 +18,8 @@ export PollingFileWatcher, FDWatcher, # pidfile: - mkpidlock + mkpidlock, + trymkpidlock import Base: @handle_as, wait, close, eventloop, notify_error, IOError, _sizeof_uv_poll, _sizeof_uv_fs_poll, _sizeof_uv_fs_event, _uv_hook_close, uv_error, _UVError, @@ -462,6 +463,11 @@ function __init__() global uv_jl_fspollcb = @cfunction(uv_fspollcb, Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid})) global uv_jl_fseventscb_file = @cfunction(uv_fseventscb_file, Cvoid, (Ptr{Cvoid}, Ptr{Int8}, Int32, Int32)) global uv_jl_fseventscb_folder = @cfunction(uv_fseventscb_folder, Cvoid, (Ptr{Cvoid}, Ptr{Int8}, Int32, Int32)) + + Base.mkpidlock_hook = mkpidlock + Base.trymkpidlock_hook = trymkpidlock + Base.parse_pidfile_hook = Pidfile.parse_pidfile + nothing end @@ -885,6 +891,6 @@ function poll_file(s::AbstractString, interval_seconds::Real=5.007, timeout_s::R end include("pidfile.jl") -import .Pidfile: mkpidlock +import .Pidfile: mkpidlock, trymkpidlock end diff --git a/stdlib/FileWatching/src/pidfile.jl b/stdlib/FileWatching/src/pidfile.jl index b78f7ef070018..6d40414e20db2 100644 --- a/stdlib/FileWatching/src/pidfile.jl +++ b/stdlib/FileWatching/src/pidfile.jl @@ -1,7 +1,7 @@ module Pidfile -export mkpidlock +export mkpidlock, trymkpidlock using Base: IOError, UV_EEXIST, UV_ESRCH, @@ -41,6 +41,16 @@ Optional keyword arguments: """ function mkpidlock end +""" + trymkpidlock([f::Function], at::String, [pid::Cint, proc::Process]; kwopts...) + +Like `mkpidlock` except returns `false` instead of waiting if the file is already locked. + +!!! compat "Julia 1.10" + This function requires at least Julia 1.10. + +""" +function trymkpidlock end # mutable only because we want to add a finalizer mutable struct LockMonitor @@ -95,6 +105,18 @@ function mkpidlock(at::String, proc::Process; kwopts...) return lock end +function trymkpidlock(args...; kwargs...) + try + mkpidlock(args...; kwargs..., wait=false) + catch ex + if ex isa PidlockedError + return false + else + rethrow() + end + end +end + """ Base.touch(::Pidfile.LockMonitor) @@ -192,8 +214,12 @@ function tryopen_exclusive(path::String, mode::Integer = 0o444) return nothing end +struct PidlockedError <: Exception + msg::AbstractString +end + """ - open_exclusive(path::String; mode, poll_interval, stale_age) :: File + open_exclusive(path::String; mode, poll_interval, wait, stale_age) :: File Create a new a file for read-write advisory-exclusive access. If `wait` is `false` then error out if the lock files exist @@ -218,7 +244,7 @@ function open_exclusive(path::String; file = tryopen_exclusive(path, mode) end if file === nothing - error("Failed to get pidfile lock for $(repr(path)).") + throw(PidlockedError("Failed to get pidfile lock for $(repr(path)).")) else return file end diff --git a/stdlib/FileWatching/test/pidfile.jl b/stdlib/FileWatching/test/pidfile.jl index 94621f6af78e3..c2cb0c88a1b1e 100644 --- a/stdlib/FileWatching/test/pidfile.jl +++ b/stdlib/FileWatching/test/pidfile.jl @@ -180,14 +180,14 @@ end Base.errormonitor(rmtask) t1 = time() - @test_throws ErrorException open_exclusive("pidfile", wait=false) + @test_throws Pidfile.PidlockedError open_exclusive("pidfile", wait=false) @test time()-t1 ≈ 0 atol=1 sleep(1) @test !deleted t1 = time() - @test_throws ErrorException open_exclusive("pidfile", wait=false) + @test_throws Pidfile.PidlockedError open_exclusive("pidfile", wait=false) @test time()-t1 ≈ 0 atol=1 wait(rmtask) @@ -246,7 +246,7 @@ end Base.errormonitor(waittask) # mkpidlock with no waiting - t = @elapsed @test_throws ErrorException mkpidlock("pidfile", wait=false) + t = @elapsed @test_throws Pidfile.PidlockedError mkpidlock("pidfile", wait=false) @test t ≈ 0 atol=1 t = @elapsed lockf1 = mkpidlock(joinpath(dir, "pidfile")) @@ -354,7 +354,7 @@ end @test lockf.update === nothing sleep(1) - t = @elapsed @test_throws ErrorException mkpidlock("pidfile-2", wait=false, stale_age=1, poll_interval=1, refresh=0) + t = @elapsed @test_throws Pidfile.PidlockedError mkpidlock("pidfile-2", wait=false, stale_age=1, poll_interval=1, refresh=0) @test t ≈ 0 atol=1 sleep(5) From ba251e8d551dfa0b4d8a86932e06b4d1162d8b9f Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 16 Jun 2023 15:49:24 -0500 Subject: [PATCH 05/21] Fix sorting bugs (esp `MissingOptimization`) that come up when using SortingAlgorithms.TimSort (#50171) --- base/sort.jl | 28 ++++++++++++++-------------- test/sorting.jl | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 99f2ed3e1aeb8..90f8755d3b1a4 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -44,6 +44,7 @@ export # not exported by Base SMALL_ALGORITHM, SMALL_THRESHOLD +abstract type Algorithm end ## functions requiring only ordering ## @@ -436,7 +437,7 @@ for (sym, exp, type) in [ (:mn, :(throw(ArgumentError("mn is needed but has not been computed"))), :(eltype(v))), (:mx, :(throw(ArgumentError("mx is needed but has not been computed"))), :(eltype(v))), (:scratch, nothing, :(Union{Nothing, Vector})), # could have different eltype - (:allow_legacy_dispatch, true, Bool)] + (:legacy_dispatch_entry, nothing, Union{Nothing, Algorithm})] usym = Symbol(:_, sym) @eval function $usym(v, o, kw) # using missing instead of nothing because scratch could === nothing. @@ -499,8 +500,6 @@ internal or recursive calls. """ function _sort! end -abstract type Algorithm end - """ MissingOptimization(next) <: Algorithm @@ -524,12 +523,12 @@ struct WithoutMissingVector{T, U} <: AbstractVector{T} new{nonmissingtype(eltype(data)), typeof(data)}(data) end end -Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i) +Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i::Integer) out = v.data[i] @assert !(out isa Missing) out::eltype(v) end -Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector, x, i) +Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector, x, i::Integer) v.data[i] = x v end @@ -590,8 +589,9 @@ function _sort!(v::AbstractVector, a::MissingOptimization, o::Ordering, kw) # we can assume v is equal to eachindex(o.data) which allows a copying partition # without allocations. lo_i, hi_i = lo, hi - for i in eachindex(o.data) # equal to copy(v) - x = o.data[i] + cv = eachindex(o.data) # equal to copy(v) + for i in lo:hi + x = o.data[cv[i]] if ismissing(x) == (o.order == Reverse) # should x go at the beginning/end? v[lo_i] = i lo_i += 1 @@ -2149,25 +2149,25 @@ end # Support 3-, 5-, and 6-argument versions of sort! for calling into the internals in the old way sort!(v::AbstractVector, a::Algorithm, o::Ordering) = sort!(v, firstindex(v), lastindex(v), a, o) function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering) - _sort!(v, a, o, (; lo, hi, allow_legacy_dispatch=false)) + _sort!(v, a, o, (; lo, hi, legacy_dispatch_entry=a)) v end sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, _) = sort!(v, lo, hi, a, o) function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, scratch::Vector) - _sort!(v, a, o, (; lo, hi, scratch, allow_legacy_dispatch=false)) + _sort!(v, a, o, (; lo, hi, scratch, legacy_dispatch_entry=a)) v end # Support dispatch on custom algorithms in the old way # sort!(::AbstractVector, ::Integer, ::Integer, ::MyCustomAlgorithm, ::Ordering) = ... function _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw) - @getkw lo hi scratch allow_legacy_dispatch - if allow_legacy_dispatch + @getkw lo hi scratch legacy_dispatch_entry + if legacy_dispatch_entry === a + # This error prevents infinite recursion for unknown algorithms + throw(ArgumentError("Base.Sort._sort!(::$(typeof(v)), ::$(typeof(a)), ::$(typeof(o)), ::Any) is not defined")) + else sort!(v, lo, hi, a, o) scratch - else - # This error prevents infinite recursion for unknown algorithms - throw(ArgumentError("Base.Sort._sort!(::$(typeof(v)), ::$(typeof(a)), ::$(typeof(o))) is not defined")) end end diff --git a/test/sorting.jl b/test/sorting.jl index cf98182307088..147a70a5db7d9 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -1025,6 +1025,46 @@ Base.similar(A::MyArray49392, ::Type{T}, dims::Dims{N}) where {T, N} = MyArray49 @test all(sort!(y, dims=2) .== sort!(x,dims=2)) end +@testset "MissingOptimization fastpath for Perm ordering when lo:hi ≠ eachindex(v)" begin + v = [rand() < .5 ? missing : rand() for _ in 1:100] + ix = collect(1:100) + sort!(ix, 1, 10, Base.Sort.DEFAULT_STABLE, Base.Order.Perm(Base.Order.Forward, v)) + @test issorted(v[ix[1:10]]) +end + +struct NonScalarIndexingOfWithoutMissingVectorAlg <: Base.Sort.Algorithm end +function Base.Sort._sort!(v::AbstractVector, ::NonScalarIndexingOfWithoutMissingVectorAlg, o::Base.Order.Ordering, kw) + Base.Sort.@getkw lo hi + first_half = v[lo:lo+(hi-lo)÷2] + second_half = v[lo+(hi-lo)÷2+1:hi] + whole = v[lo:hi] + all(vcat(first_half, second_half) .=== whole) || error() + out = Base.Sort._sort!(whole, Base.Sort.DEFAULT_STABLE, o, (;kw..., lo=1, hi=length(whole))) + v[lo:hi] .= whole + out +end + +@testset "Non-scaler indexing of WithoutMissingVector" begin + @testset "Unit test" begin + wmv = Base.Sort.WithoutMissingVector(Union{Missing, Int}[1, 7, 2, 9]) + @test wmv[[1, 3]] == [1, 2] + @test wmv[1:3] == [1, 7, 2] + end + @testset "End to end" begin + alg = Base.Sort.InitialOptimizations(NonScalarIndexingOfWithoutMissingVectorAlg()) + @test issorted(sort(rand(100); alg)) + @test issorted(sort([rand() < .5 ? missing : randstring() for _ in 1:100]; alg)) + end +end + +struct DispatchLoopTestAlg <: Base.Sort.Algorithm end +function Base.sort!(v::AbstractVector, lo::Integer, hi::Integer, ::DispatchLoopTestAlg, order::Base.Order.Ordering) + sort!(view(v, lo:hi); order) +end +@testset "Support dispatch from the old style to the new style and back" begin + @test issorted(sort!(rand(100), Base.Sort.InitialOptimizations(DispatchLoopTestAlg()), Base.Order.Forward)) +end + # This testset is at the end of the file because it is slow. @testset "searchsorted" begin numTypes = [ Int8, Int16, Int32, Int64, Int128, From f0881ef625712727d44ac9645b6cb66660d6cb70 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 16 Jun 2023 22:38:00 +0000 Subject: [PATCH 06/21] Pass through world age for kwargs MethodError Fixes #50200 --- base/errorshow.jl | 2 +- test/errorshow.jl | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 176cae4b5251a..4d32743f1af52 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -243,7 +243,7 @@ function showerror(io::IO, ex::MethodError) ft = typeof(f) arg_types_param = arg_types_param[3:end] kwargs = pairs(ex.args[1]) - ex = MethodError(f, ex.args[3:end::Int]) + ex = MethodError(f, ex.args[3:end::Int], ex.world) end name = ft.name.mt.name if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types diff --git a/test/errorshow.jl b/test/errorshow.jl index 9be3e675cede3..404b8e6843a83 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -578,7 +578,7 @@ let end end -@testset "show for manually thrown MethodError" begin +@testset "show for MethodError with world age issue" begin global f21006 f21006() = nothing @@ -620,6 +620,31 @@ end end end +# Issue #50200 +using Base.Experimental: @opaque +@testset "show for MethodError with world age issue (kwarg)" begin + test_no_error(f) = @test f() === nothing + function test_worldage_error(f) + ex = try; f(); error("Should not have been reached") catch ex; ex; end + @test occursin("The applicable method may be too new", sprint(Base.showerror, ex)) + end + + global callback50200 + + # First the no-kwargs version + callback50200 = (args...)->nothing + f = @opaque ()->callback50200() + test_no_error(f) + callback50200 = (args...)->nothing + test_worldage_error(f) + + callback50200 = (args...; kwargs...)->nothing + f = @opaque ()->callback50200(;a=1) + test_no_error(f) + callback50200 = (args...; kwargs...)->nothing + test_worldage_error(f) +end + # Custom hints struct HasNoOne end function recommend_oneunit(io, ex, arg_types, kwargs) From 18dd7a2d282a36cd672ec48b59bc7b0a959028c2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 16 Jun 2023 22:50:28 +0000 Subject: [PATCH 07/21] Don't color Any... red if the method actually matched Fixes the drive-by observation I made in #50200. --- base/errorshow.jl | 6 +++++- test/errorshow.jl | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 4d32743f1af52..24bd3a37d5298 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -490,7 +490,11 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() if !((min(length(t_i), length(sig)) == 0) && k==1) print(iob, ", ") end - if get(io, :color, false)::Bool + if k == 1 && Base.isvarargtype(sigtype) + # There wasn't actually a mismatch - the method match failed for + # some other reason, e.g. world age. Just print the sigstr. + print(iob, sigstr...) + elseif get(io, :color, false)::Bool let sigstr=sigstr Base.with_output_color(Base.error_color(), iob) do iob print(iob, "::", sigstr...) diff --git a/test/errorshow.jl b/test/errorshow.jl index 404b8e6843a83..28ae3fd32365a 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -627,6 +627,7 @@ using Base.Experimental: @opaque function test_worldage_error(f) ex = try; f(); error("Should not have been reached") catch ex; ex; end @test occursin("The applicable method may be too new", sprint(Base.showerror, ex)) + @test !occursin("!Matched::", sprint(Base.showerror, ex)) end global callback50200 From fd1cec2de1a34d49782f3a1b8628bfa6f45a0500 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 17 Jun 2023 01:03:37 -0400 Subject: [PATCH 08/21] Improve effects for Base.fieldindex (#50199) Split out the error path into a function with separate effects assumptions, so that constant propagation on `err` can conclude that the `err=false` case does not throw. Fixes #50198. --- base/reflection.jl | 12 +++++++++++- test/compiler/effects.jl | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/base/reflection.jl b/base/reflection.jl index 96b7a832cc575..5268beddeb8eb 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -820,9 +820,19 @@ julia> Base.fieldindex(Foo, :z, false) ``` """ function fieldindex(T::DataType, name::Symbol, err::Bool=true) + return err ? _fieldindex_maythrow(T, name) : _fieldindex_nothrow(T, name) +end + +function _fieldindex_maythrow(T::DataType, name::Symbol) @_foldable_meta @noinline - return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) + return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, true)+1) +end + +function _fieldindex_nothrow(T::DataType, name::Symbol) + @_total_meta + @noinline + return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, false)+1) end function fieldindex(t::UnionAll, name::Symbol, err::Bool=true) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 99e788c0cff12..65719f4a5f27d 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -988,3 +988,8 @@ isassigned_effects(s) = isassigned(Ref(s)) @test fully_eliminated(; retval=true) do isassigned_effects(:foo) end + +# Effects of Base.hasfield (#50198) +hf50198(s) = hasfield(typeof((;x=1, y=2)), s) +f50198() = (hf50198(Ref(:x)[]); nothing) +@test fully_eliminated(f50198) From b6902aeddc3bfba887c734d87012d56d49e2d710 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Thu, 25 May 2023 07:10:58 +1000 Subject: [PATCH 09/21] Make incomplete_tag extensible This allows `incomplete_tag` to work when Expr(:incomplete) holds a Meta.ParseError as its child rather than a plain string, as it will when JuliaSyntax is enabled. --- base/client.jl | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/base/client.jl b/base/client.jl index dd529dad5281e..6e30c9991e45e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -202,10 +202,7 @@ parse_input_line(s::AbstractString) = parse_input_line(String(s)) # detect the reason which caused an :incomplete expression # from the error message # NOTE: the error messages are defined in src/julia-parser.scm -incomplete_tag(ex) = :none -function incomplete_tag(ex::Expr) - Meta.isexpr(ex, :incomplete) || return :none - msg = ex.args[1] +function fl_incomplete_tag(msg::AbstractString) occursin("string", msg) && return :string occursin("comment", msg) && return :comment occursin("requires end", msg) && return :block @@ -214,6 +211,20 @@ function incomplete_tag(ex::Expr) return :other end +incomplete_tag(ex) = :none +function incomplete_tag(ex::Expr) + if ex.head !== :incomplete + return :none + elseif isempty(ex.args) + return :other + elseif ex.args[1] isa String + return fl_incomplete_tag(ex.args[1]) + else + return incomplete_tag(ex.args[1]) + end +end +incomplete_tag(exc::Meta.ParseError) = incomplete_tag(exc.detail) + function exec_options(opts) quiet = (opts.quiet != 0) startup = (opts.startupfile != 2) From 9e7bb1290999b1170a4977785743220b26a81c92 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Thu, 25 May 2023 07:11:01 +1000 Subject: [PATCH 10/21] Add `Meta.ParseError` detail field Here we add a `detail` field to `Meta.ParseError`, but retain the `msg::String` field for compatibility. `showerror(::ParseError)` defers to the `detail` field if it's present. This allows us to still throw `Meta.ParseError` from `Meta.parse` for compatibility, but allow more expressivity when `detail` is set to an exception type like `JuliaSyntax.ParseError`. --- base/errorshow.jl | 7 +++++++ base/meta.jl | 3 +++ 2 files changed, 10 insertions(+) diff --git a/base/errorshow.jl b/base/errorshow.jl index 176cae4b5251a..ca583cfe071b3 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -35,6 +35,13 @@ show_index(io::IO, x::LogicalIndex) = summary(io, x.mask) show_index(io::IO, x::OneTo) = print(io, "1:", x.stop) show_index(io::IO, x::Colon) = print(io, ':') +function showerror(io::IO, ex::Meta.ParseError) + if isnothing(ex.detail) + print(io, "ParseError(", repr(ex.msg), ")") + else + showerror(io, ex.detail) + end +end function showerror(io::IO, ex::BoundsError) print(io, "BoundsError") diff --git a/base/meta.jl b/base/meta.jl index b0e0dc371b26c..5dba11ac442eb 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -187,8 +187,11 @@ expression. """ struct ParseError <: Exception msg::String + detail::Any end +ParseError(msg::AbstractString) = ParseError(msg, nothing) + function _parse_string(text::AbstractString, filename::AbstractString, lineno::Integer, index::Integer, options) if index < 1 || index > ncodeunits(text) + 1 From 1a6cd971df3752f7f90b69f0c61104a3a19ea5ad Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Thu, 25 May 2023 09:13:33 +1000 Subject: [PATCH 11/21] Only wrap Strings in Meta.ParseError String errors come from the flisp parser as Expr(:error). But other than that, allow the parser library to choose its own error type. --- base/meta.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/meta.jl b/base/meta.jl index 5dba11ac442eb..5d1cfe9c4a1a6 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -236,7 +236,11 @@ function parse(str::AbstractString, pos::Integer; greedy::Bool=true, raise::Bool depwarn::Bool=true) ex, pos = _parse_string(str, "none", 1, pos, greedy ? :statement : :atom) if raise && isa(ex,Expr) && ex.head === :error - throw(ParseError(ex.args[1])) + err = ex.args[1] + if err isa String + err = ParseError(err) # For flisp parser + end + throw(err) end return ex, pos end From 8caba95bb02656b7774c00c5e419d08845ad9b70 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Sat, 27 May 2023 06:33:00 +1000 Subject: [PATCH 12/21] Show top level location for any `exc` in `eval(Expr(:error, exc))` Previously this only worked when `exc` was a `String`. --- src/toplevel.c | 21 +++++++++++++++------ test/backtrace.jl | 9 ++++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/toplevel.c b/src/toplevel.c index cf0104879a7b0..51ff93488426f 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -656,19 +656,28 @@ static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword jl_errorf("cannot rename non-macro \"%s\" to macro \"%s\" in \"%s\"", n1, n2, keyword); } -// Format msg and eval `throw(ErrorException(msg)))` in module `m`. -// Used in `jl_toplevel_eval_flex` instead of `jl_errorf` so that the error +// Eval `throw(ErrorException(msg)))` in module `m`. +// Used in `jl_toplevel_eval_flex` instead of `jl_throw` so that the error // location in julia code gets into the backtrace. -static void jl_eval_errorf(jl_module_t *m, const char* fmt, ...) +static void jl_eval_throw(jl_module_t *m, jl_value_t *exc) { jl_value_t *throw_ex = (jl_value_t*)jl_exprn(jl_call_sym, 2); JL_GC_PUSH1(&throw_ex); jl_exprargset(throw_ex, 0, jl_builtin_throw); + jl_exprargset(throw_ex, 1, exc); + jl_toplevel_eval_flex(m, throw_ex, 0, 0); + JL_GC_POP(); +} + +// Format error message and call jl_eval +static void jl_eval_errorf(jl_module_t *m, const char* fmt, ...) +{ va_list args; va_start(args, fmt); - jl_exprargset(throw_ex, 1, jl_vexceptionf(jl_errorexception_type, fmt, args)); + jl_value_t *exc = jl_vexceptionf(jl_errorexception_type, fmt, args); va_end(args); - jl_toplevel_eval_flex(m, throw_ex, 0, 0); + JL_GC_PUSH1(&exc); + jl_eval_throw(m, exc); JL_GC_POP(); } @@ -875,7 +884,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int jl_eval_errorf(m, "malformed \"%s\" expression", jl_symbol_name(head)); if (jl_is_string(jl_exprarg(ex, 0))) jl_eval_errorf(m, "syntax: %s", jl_string_data(jl_exprarg(ex, 0))); - jl_throw(jl_exprarg(ex, 0)); + jl_eval_throw(m, jl_exprarg(ex, 0)); } else if (jl_is_symbol(ex)) { JL_GC_POP(); diff --git a/test/backtrace.jl b/test/backtrace.jl index 38019880da35d..50a50100488c4 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -195,6 +195,13 @@ let bt, found = false end # Syntax error locations appear in backtraces +let trace = try + eval(Expr(:error, 1)) + catch + stacktrace(catch_backtrace()) + end + @test trace[1].func === Symbol("top-level scope") +end let trace = try include_string(@__MODULE__, """ @@ -221,7 +228,7 @@ let trace = try end @test trace[1].func === Symbol("top-level scope") @test trace[1].file === :a_filename - @test trace[1].line == 2 + @test trace[1].line == 3 end # issue #45171 From 964f0d63259aebb0598c678a01af2a02d332f557 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Sun, 28 May 2023 17:12:42 +1000 Subject: [PATCH 13/21] Pass Int rather than UInt as lengths to core parser hook This is more consistent with the way we're likely call it from the Julia side via Meta.parse(). --- src/ast.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast.c b/src/ast.c index bd1ffee5b76b1..06727b453d6a3 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1348,8 +1348,8 @@ jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, jl_svecset(args[1], 0, jl_box_uint8pointer((uint8_t*)text)); jl_svecset(args[1], 1, jl_box_long(text_len)); args[2] = filename; - args[3] = jl_box_ulong(lineno); - args[4] = jl_box_ulong(offset); + args[3] = jl_box_long(lineno); + args[4] = jl_box_long(offset); args[5] = options; jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; From 2d68286d40aa01bc6c5132d560a4a496316a3e2f Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Sun, 28 May 2023 07:23:10 +1000 Subject: [PATCH 14/21] Fix string escaping in REPL completion of paths REPL completion of paths within strings need to be escaped according to the usual escaping rules, and delimited by the starting " rather than whitespace. This differs from completion of paths within cmd backticks which need to be escaped according to shell escaping rules. Separate these cases and fix string escaping. This was found because JuliaSyntax emits an Expr(:error) rather than Expr(:incomplete) for paths inside strings with invalid escape sequences before whitespace. --- stdlib/REPL/src/REPLCompletions.jl | 58 +++++++++++++++++------ stdlib/REPL/test/replcompletions.jl | 71 ++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 6ec7074f105fd..20d26953eb22b 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -232,7 +232,10 @@ function complete_keyword(s::Union{String,SubString{String}}) Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]] end -function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false) +function complete_path(path::AbstractString, pos::Int; + use_envpath=false, shell_escape=false, + string_escape=false) + @assert !(shell_escape && string_escape) if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) # if the path is just "~", don't consider the expanded username as a prefix if path == "~" @@ -259,9 +262,9 @@ function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_ matches = Set{String}() for file in files if startswith(file, prefix) - id = try isdir(joinpath(dir, file)) catch; false end - # joinpath is not used because windows needs to complete with double-backslash - push!(matches, id ? file * (@static Sys.iswindows() ? "\\\\" : "/") : file) + p = joinpath(dir, file) + is_dir = try isdir(p) catch; false end + push!(matches, is_dir ? joinpath(file, "") : file) end end @@ -307,8 +310,14 @@ function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_ end end - matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches] - startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix) + function do_escape(s) + return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") : + string_escape ? escape_string(s, ('\"','$')) : + s + end + + matchList = Completion[PathCompletion(do_escape(s)) for s in matches] + startpos = pos - lastindex(do_escape(prefix)) + 1 # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`, # hence we need to add one to get the first index. This is also correct when considering # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`. @@ -767,7 +776,7 @@ end function close_path_completion(str, startpos, r, paths, pos) length(paths) == 1 || return false # Only close if there's a single choice... _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path - path = expanduser(replace(_path, r"\\ " => " ")) + path = expanduser(unescape_string(replace(_path, "\\\$"=>"\$", "\\\""=>"\""))) # ...except if it's a directory... try isdir(path) @@ -1039,23 +1048,44 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0) return complete_identifiers!(Completion[], ffunc, context_module, string, string[startpos:pos], pos, dotpos, startpos) - # otherwise... - elseif inc_tag in [:cmd, :string] + elseif inc_tag === :cmd m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) startpos = nextind(partial, reverseind(partial, m.offset)) r = startpos:pos + # This expansion with "\\ "=>' ' replacement and shell_escape=true + # assumes the path isn't further quoted within the cmd backticks. expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r) expanded[3] && return expanded # If user expansion available, return it - paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos) + paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos, + shell_escape=true) + + return sort!(paths, by=p->p.path), r, success + elseif inc_tag === :string + # Find first non-escaped quote + m = match(r"\"(?!\\)", reverse(partial)) + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + + expanded = complete_expanduser(string[r], r) + expanded[3] && return expanded # If user expansion available, return it - if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos) - paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + path_prefix = try + unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\"")) + catch + nothing end + if !isnothing(path_prefix) + paths, r, success = complete_path(path_prefix, pos, string_escape=true) - #Latex symbols can be completed for strings - (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success + if close_path_completion(string, startpos, r, paths, pos) + paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + end + + # Fallthrough allowed so that Latex symbols can be completed in strings + success && return sort!(paths, by=p->p.path), r, success + end end ok, ret = bslash_completions(string, pos) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index b0d1ff4b5237a..b2199e10bef55 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -1177,7 +1177,7 @@ let current_dir, forbidden catch e e isa Base.IOError && occursin("ELOOP", e.msg) end - c, r = test_complete("\"$(joinpath(path, "selfsym"))") + c, r = test_complete("\""*escape_string(joinpath(path, "selfsym"))) @test c == ["selfsymlink"] end end @@ -1207,26 +1207,62 @@ end mktempdir() do path space_folder = randstring() * " α" dir = joinpath(path, space_folder) - dir_space = replace(space_folder, " " => "\\ ") - mkdir(dir) cd(path) do - open(joinpath(space_folder, "space .file"),"w") do f - s = Sys.iswindows() ? "rm $dir_space\\\\space" : "cd $dir_space/space" - c, r = test_scomplete(s) - @test r == lastindex(s)-4:lastindex(s) - @test "space\\ .file" in c + touch(joinpath(space_folder, "space .file")) + + dir_space = replace(space_folder, " " => "\\ ") + s = Sys.iswindows() ? "cd $dir_space\\\\space" : "cd $dir_space/space" + c, r = test_scomplete(s) + @test s[r] == "space" + @test "space\\ .file" in c + # Also use shell escape rules within cmd backticks + s = "`$s" + c, r = test_scomplete(s) + @test s[r] == "space" + @test "space\\ .file" in c + + # escape string according to Julia escaping rules + julia_esc(str) = escape_string(str, ('\"','$')) + + # For normal strings the string should be properly escaped according to + # the usual rules for Julia strings. + s = "cd(\"" * julia_esc(joinpath(path, space_folder, "space")) + c, r = test_complete(s) + @test s[r] == "space" + @test "space .file\"" in c + + # '$' is the only character which can appear in a windows filename and + # which needs to be escaped in Julia strings (on unix we could do this + # test with all sorts of special chars) + touch(joinpath(space_folder, "needs_escape\$.file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape\$")) + s = "cd(\"$escpath" + c, r = test_complete(s) + @test s[r] == "needs_escape\\\$" + @test "needs_escape\\\$.file\"" in c - s = Sys.iswindows() ? "cd(\"β $dir_space\\\\space" : "cd(\"β $dir_space/space" + if !Sys.iswindows() + touch(joinpath(space_folder, "needs_escape2\n\".file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape2\n\"")) + s = "cd(\"$escpath" c, r = test_complete(s) - @test r == lastindex(s)-4:lastindex(s) - @test "space .file\"" in c + @test s[r] == "needs_escape2\\n\\\"" + @test "needs_escape2\\n\\\".file\"" in c + + touch(joinpath(space_folder, "needs_escape3\\.file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape3\\")) + s = "cd(\"$escpath" + c, r = test_complete(s) + @test s[r] == "needs_escape3\\\\" + @test "needs_escape3\\\\.file\"" in c end + # Test for issue #10324 - s = "cd(\"$dir_space" + s = "cd(\"$space_folder" c, r = test_complete(s) - @test r == 5:15 - @test s[r] == dir_space + @test r == 5:14 + @test s[r] == space_folder #Test for #18479 for c in "'`@\$;&" @@ -1240,8 +1276,9 @@ mktempdir() do path @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") @test res end - c, r, res = test_complete("\""*test_dir) - @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") + escdir = julia_esc(test_dir) + c, r, res = test_complete("\""*escdir) + @test c[1] == escdir*(Sys.iswindows() ? "\\\\" : "/") @test res finally rm(joinpath(path, test_dir), recursive=true) @@ -1285,7 +1322,7 @@ if Sys.iswindows() @test r == length(s)-1:length(s) @test file in c - s = "cd(\"..\\" + s = "cd(\"..\\\\" c,r = test_complete(s) @test r == length(s)+1:length(s) @test temp_name * "\\\\" in c From 60bf0b684d49076227115e721c52a47d02878e49 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Thu, 25 May 2023 07:10:29 +1000 Subject: [PATCH 15/21] Enable JuliaSyntax.jl as the defult parser * Vendor JuliaSyntax into Base via deps directory * Install JuliaSyntax as the Julia parser unless the environment variable JULIA_USE_NEW_PARSER=0 is set. * Add a function to set the Core._parse binding. Required because we'd like to set the binding during Base.__init__. This can be done with `Core.eval` but that doesn't work well in incremental compilation mode. Also accommodate JuliaSyntax within tests: * When JuliaSyntax is enabled, ignore error messages in parser tests which are tested separately upstream - error messages are inherently expressed a bit differently when they go alongside full source location info. * Accommodate a small number of incompatibilities where in JuliaSyntax - `import .Mod.x as (a.b)` is a syntax not lowering error - `f(2x for x=1:10, y` is `Expr(:incomplete)` not `Expr(:error)` - `incomplete_tag` is more precise for `:block` vs `:other` - `global const` without an assignment is a syntax error, in keeping with plain `const` without assignment being a syntax error (not lowering error). * Adjust a few tests to be more precise about testing lowering vs the parser. * Make Meta.parse doctest compatible with JuliaSyntax errors --- Makefile | 4 + NEWS.md | 3 + base/.gitignore | 1 + base/Base.jl | 7 + base/boot.jl | 4 +- base/compiler/compiler.jl | 2 +- base/meta.jl | 16 +- contrib/generate_precompile.jl | 1 - deps/JuliaSyntax.mk | 16 + deps/JuliaSyntax.version | 4 + deps/Makefile | 7 +- .../md5 | 1 + .../sha512 | 1 + test/cmdlineargs.jl | 2 +- test/show.jl | 2 +- test/strings/basic.jl | 7 - test/syntax.jl | 419 +++++++++--------- 17 files changed, 272 insertions(+), 225 deletions(-) create mode 100644 deps/JuliaSyntax.mk create mode 100644 deps/JuliaSyntax.version create mode 100644 deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 create mode 100644 deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 diff --git a/Makefile b/Makefile index 046f18492bc3e..eb6e54ae70b34 100644 --- a/Makefile +++ b/Makefile @@ -365,6 +365,10 @@ endif # Remove various files which should not be installed -rm -f $(DESTDIR)$(datarootdir)/julia/base/version_git.sh -rm -f $(DESTDIR)$(datarootdir)/julia/test/Makefile + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/source-extracted + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-configured + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-compiled + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-checked -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/source-extracted -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/build-configured -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/build-compiled diff --git a/NEWS.md b/NEWS.md index 6c60b56b7a028..d73373d95d26e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ Julia v1.10 Release Notes New language features --------------------- +* JuliaSyntax.jl is now used as the default parser, providing better diagnostics and faster + parsing. Set environment variable `JULIA_USE_NEW_PARSER` to `0` to switch back to the old + parser if necessary (and if you find this necessary, please file an issue) ([#46372]). * `⥺` (U+297A, `\leftarrowsubset`) and `⥷` (U+2977, `\leftarrowless`) may now be used as binary operators with arrow precedence. ([#45962]) diff --git a/base/.gitignore b/base/.gitignore index e572b8ea229d0..0fab5b41fda08 100644 --- a/base/.gitignore +++ b/base/.gitignore @@ -8,3 +8,4 @@ /version_git.jl /version_git.jl.phony /userimg.jl +/JuliaSyntax diff --git a/base/Base.jl b/base/Base.jl index 1a677bf508977..1fc20293aa384 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -489,6 +489,10 @@ a_method_to_overwrite_in_test() = inferencebarrier(1) include(mod::Module, _path::AbstractString) = _include(identity, mod, _path) include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path) +# External libraries vendored into Base +Core.println("JuliaSyntax/src/JuliaSyntax.jl") +include(@__MODULE__, "JuliaSyntax/src/JuliaSyntax.jl") + end_base_include = time_ns() const _sysimage_modules = PkgId[] @@ -600,6 +604,9 @@ function __init__() _require_world_age[] = get_world_counter() # Prevent spawned Julia process from getting stuck waiting on Tracy to connect. delete!(ENV, "JULIA_WAIT_FOR_TRACY") + if get_bool_env("JULIA_USE_NEW_PARSER", true) === true + JuliaSyntax.enable_in_core!() + end nothing end diff --git a/base/boot.jl b/base/boot.jl index ec25fa2bc0b6d..6698d4360cc7d 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -825,9 +825,11 @@ Integer(x::Union{Float16, Float32, Float64}) = Int(x) # `_parse` must return an `svec` containing an `Expr` and the new offset as an # `Int`. # -# The internal jl_parse which will call into Core._parse if not `nothing`. +# The internal jl_parse will call into Core._parse if not `nothing`. _parse = nothing +_setparser!(parser) = setglobal!(Core, :_parse, parser) + # support for deprecated uses of internal _apply function _apply(x...) = Core._apply_iterate(Main.Base.iterate, x...) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 58f77078ddb5e..04b0791d9a79e 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -171,7 +171,7 @@ include("compiler/bootstrap.jl") ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel) include("compiler/parsing.jl") -Core.eval(Core, :(_parse = Compiler.fl_parse)) +Core._setparser!(fl_parse) end # baremodule Compiler )) diff --git a/base/meta.jl b/base/meta.jl index 5d1cfe9c4a1a6..ba2a5eeb6858b 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -254,20 +254,22 @@ syntax errors will raise an error; otherwise, `parse` will return an expression raise an error upon evaluation. If `depwarn` is `false`, deprecation warnings will be suppressed. -```jldoctest +```jldoctest; filter=r"(?<=Expr\\(:error).*|(?<=Expr\\(:incomplete).*" julia> Meta.parse("x = 3") :(x = 3) -julia> Meta.parse("x = ") -:($(Expr(:incomplete, "incomplete: premature end of input"))) - julia> Meta.parse("1.0.2") -ERROR: Base.Meta.ParseError("invalid numeric constant \\\"1.0.\\\"") -Stacktrace: +ERROR: ParseError: +# Error @ none:1:1 +1.0.2 +└──┘ ── invalid numeric constant [...] julia> Meta.parse("1.0.2"; raise = false) -:($(Expr(:error, "invalid numeric constant \"1.0.\""))) +:(\$(Expr(:error, "invalid numeric constant \"1.0.\""))) + +julia> Meta.parse("x = ") +:(\$(Expr(:incomplete, "incomplete: premature end of input"))) ``` """ function parse(str::AbstractString; raise::Bool=true, depwarn::Bool=true) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 8fa40e4920eea..7312726fe2eaa 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -153,7 +153,6 @@ if Artifacts !== nothing """ end - Pkg = get(Base.loaded_modules, Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"), nothing) diff --git a/deps/JuliaSyntax.mk b/deps/JuliaSyntax.mk new file mode 100644 index 0000000000000..e9cc0c942dbe0 --- /dev/null +++ b/deps/JuliaSyntax.mk @@ -0,0 +1,16 @@ +$(eval $(call git-external,JuliaSyntax,JULIASYNTAX,,,$(BUILDDIR))) + +$(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/source-extracted + @# no build steps + echo 1 > $@ + +$(eval $(call symlink_install,JuliaSyntax,$$(JULIASYNTAX_SRC_DIR),$$(JULIAHOME)/base)) + +clean-JuliaSyntax: + -rm -f $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled +get-JuliaSyntax: $(JULIASYNTAX_SRC_FILE) +extract-JuliaSyntax: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/source-extracted +configure-JuliaSyntax: extract-JuliaSyntax +compile-JuliaSyntax: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled +fastcheck-JuliSyntax: check-JuliSyntax +check-JuliSyntax: compile-JuliSyntax diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version new file mode 100644 index 0000000000000..2bd765e6f4535 --- /dev/null +++ b/deps/JuliaSyntax.version @@ -0,0 +1,4 @@ +JULIASYNTAX_BRANCH = main +JULIASYNTAX_SHA1 = ec51994833d78f8c5525bc1647f448dfadc370c1 +JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git +JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/Makefile b/deps/Makefile index 62bb85e72c492..ac899b634a3fa 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -36,7 +36,7 @@ BUILDDIR := $(BUILDDIR)$(MAYBE_HOST) # prevent installing libs into usr/lib64 on opensuse unexport CONFIG_SITE -DEP_LIBS := +DEP_LIBS := JuliaSyntax ifeq ($(USE_SYSTEM_LIBBLASTRAMPOLINE), 0) DEP_LIBS += blastrampoline @@ -188,7 +188,7 @@ DEP_LIBS_STAGED := $(DEP_LIBS) DEP_LIBS_STAGED_ALL := llvm llvm-tools clang llvmunwind unwind libuv pcre \ openlibm dsfmt blastrampoline openblas lapack gmp mpfr patchelf utf8proc \ objconv mbedtls libssh2 nghttp2 curl libgit2 libwhich zlib p7zip csl \ - libsuitesparse lld libtracyclient ittapi + libsuitesparse lld libtracyclient ittapi JuliaSyntax DEP_LIBS_ALL := $(DEP_LIBS_STAGED_ALL) ifneq ($(USE_BINARYBUILDER_OPENBLAS),0) @@ -248,4 +248,7 @@ include $(SRCDIR)/libwhich.mk include $(SRCDIR)/p7zip.mk include $(SRCDIR)/libtracyclient.mk +# vendored Julia libs +include $(SRCDIR)/JuliaSyntax.mk + include $(SRCDIR)/tools/uninstallers.mk diff --git a/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 new file mode 100644 index 0000000000000..e1f51dd3d711a --- /dev/null +++ b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 @@ -0,0 +1 @@ +b1d1ccb00e422eb8b70b2120d7083bf3 diff --git a/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 new file mode 100644 index 0000000000000..2ac2b9ed7c903 --- /dev/null +++ b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 @@ -0,0 +1 @@ +e6df6dc2b5d2a5618da0d553eed793e1192147175d84d51f725c0ea8f7b6be92fbeb37de9abee2b2f548b0f0736f836ec7e3e20e93c12f77e1a2b2058bbfd6db diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 9c8c0ac553c24..13a68be2927de 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -924,7 +924,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` close(in) close(err.in) txt = readline(err) - @test startswith(txt, "ERROR: syntax: incomplete") + @test startswith(txt, r"ERROR: (syntax: incomplete|ParseError:)") end # Issue #29855 diff --git a/test/show.jl b/test/show.jl index f2c553b3ff49a..25c5a49372054 100644 --- a/test/show.jl +++ b/test/show.jl @@ -633,7 +633,7 @@ end @test_repr "::@m(x, y) + z" @test_repr "[@m(x) y z]" @test_repr "[@m(x) y; z]" -@test_repr "let @m(x), y=z; end" +test_repr("let @m(x), y=z; end", true) @test repr(:(@m x y)) == ":(#= $(@__FILE__):$(@__LINE__) =# @m x y)" @test string(:(@m x y)) == "#= $(@__FILE__):$(@__LINE__) =# @m x y" diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 7151a4d4fd60a..13f2f5197187a 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -250,8 +250,6 @@ end @test string(sym) == string(Char(0xdcdb)) @test String(sym) == string(Char(0xdcdb)) @test Meta.lower(Main, sym) === sym - @test Meta.parse(string(Char(0xe0080)," = 1"), 1, raise=false)[1] == - Expr(:error, "invalid character \"\Ue0080\" near column 1") end @testset "Symbol and gensym" begin @@ -761,11 +759,6 @@ function getData(dic) end @test getData(Dict()) == ",,,,,,,,,,,,,,,,,," -@testset "unrecognized escapes in string/char literals" begin - @test_throws Meta.ParseError Meta.parse("\"\\.\"") - @test_throws Meta.ParseError Meta.parse("\'\\.\'") -end - @testset "thisind" begin let strs = Any["∀α>β:α+1>β", s"∀α>β:α+1>β", SubString("123∀α>β:α+1>β123", 4, 18), diff --git a/test/syntax.jl b/test/syntax.jl index 119f6d427a15a..4d1b167693adb 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -5,22 +5,29 @@ using Random using Base: remove_linenums! -import Base.Meta.ParseError - -function parseall(str) - pos = firstindex(str) - exs = [] - while pos <= lastindex(str) - ex, pos = Meta.parse(str, pos) - push!(exs, ex) - end - if length(exs) == 0 - throw(ParseError("end of input")) - elseif length(exs) == 1 - return exs[1] +using_JuliaSyntax = parentmodule(Core._parse) != Core.Compiler + +macro test_parseerror(str, msg) + if using_JuliaSyntax + # Diagnostics are tested separately in JuliaSyntax + ex = :(@test_throws Meta.ParseError Meta.parse($(esc(str)))) else - return Expr(:block, exs...) + ex = :(@test_throws Meta.ParseError($(esc(msg))) Meta.parse($(esc(str)))) end + ex.args[2] = __source__ + return ex +end + +macro test_parseerror(str) + ex = :(@test_throws Meta.ParseError Meta.parse($(esc(str)))) + ex.args[2] = __source__ + return ex +end + +function parseall_nolines(str) + ex = Meta.parseall(str) + filter!(e->!(e isa LineNumberNode), ex.args) + return ex end # issue #9684 @@ -60,19 +67,19 @@ macro test999_str(args...); args; end @test test999"foo"123 == ("foo", 123) # issue #5997 -@test_throws ParseError Meta.parse(": x") -@test_throws ParseError Meta.parse("""begin +@test_parseerror ": x" +@test_parseerror """begin : - x""") -@test_throws ParseError Meta.parse("d[: 2]") + x""" +@test_parseerror "d[: 2]" # issue #6770 -@test_throws ParseError Meta.parse("x.3") +@test_parseerror "x.3" # issue #8763 -@test_throws ParseError Meta.parse("sqrt(16)2") -@test_throws ParseError Meta.parse("x' y") -@test_throws ParseError Meta.parse("x 'y") +@test_parseerror "sqrt(16)2" +@test_parseerror "x' y" +@test_parseerror "x 'y" @test Meta.parse("x'y") == Expr(:call, :*, Expr(Symbol("'"), :x), :y) # issue #18851 @@ -84,22 +91,22 @@ macro test999_str(args...); args; end @test Meta.parse("-2(m)") == Expr(:call, :*, -2, :m) # issue #8301 -@test_throws ParseError Meta.parse("&*s") +@test_parseerror "&*s" # issue #10677 -@test_throws ParseError Meta.parse("/1") -@test_throws ParseError Meta.parse("/pi") +@test_parseerror "/1" +@test_parseerror "/pi" @test Meta.parse("- = 2") == Expr(:(=), :(-), 2) @test Meta.parse("/ = 2") == Expr(:(=), :(/), 2) -@test_throws ParseError Meta.parse("< : 2") -@test_throws ParseError Meta.parse("+ : 2") -@test_throws ParseError Meta.parse("< :2") +@test_parseerror "< : 2" +@test_parseerror "+ : 2" +@test_parseerror "< :2" @test Meta.parse("+ :2") == Expr(:call, :(+), QuoteNode(2)) # issue #10900 -@test_throws ParseError Meta.parse("+=") -@test_throws ParseError Meta.parse(".") -@test_throws ParseError Meta.parse("...") +@test_parseerror "+=" +@test_parseerror "." +@test_parseerror "..." # issue #10901 @test Meta.parse("/([1], 1)[1]") == :(([1] / 1)[1]) @@ -152,35 +159,35 @@ macro test999_str(args...); args; end Expr(:., Expr(:$, :c), Expr(:$, :d)))) # fix pr #11338 and test for #11497 -@test parseall("using \$\na") == Expr(:block, Expr(:using, Expr(:., :$)), :a) -@test parseall("using \$,\na") == Expr(:using, Expr(:., :$), Expr(:., :a)) -@test parseall("using &\na") == Expr(:block, Expr(:using, Expr(:., :&)), :a) +@test parseall_nolines("using \$\na") == Expr(:toplevel, Expr(:using, Expr(:., :$)), :a) +@test parseall_nolines("using \$,\na") == Expr(:toplevel, Expr(:using, Expr(:., :$), Expr(:., :a))) +@test parseall_nolines("using &\na") == Expr(:toplevel, Expr(:using, Expr(:., :&)), :a) -@test parseall("a = &\nb") == Expr(:block, Expr(:(=), :a, :&), :b) -@test parseall("a = \$\nb") == Expr(:block, Expr(:(=), :a, :$), :b) -@test parseall(":(a = &\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:&, :b))) -@test parseall(":(a = \$\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:$, :b))) +@test parseall_nolines("a = &\nb") == Expr(:toplevel, Expr(:(=), :a, :&), :b) +@test parseall_nolines("a = \$\nb") == Expr(:toplevel, Expr(:(=), :a, :$), :b) +@test parseall_nolines(":(a = &\nb)") == Expr(:toplevel, Expr(:quote, Expr(:(=), :a, Expr(:&, :b)))) +@test parseall_nolines(":(a = \$\nb)") == Expr(:toplevel, Expr(:quote, Expr(:(=), :a, Expr(:$, :b)))) # issue 12027 - short macro name parsing vs _str suffix -@test parseall(""" - macro f(args...) end; @f "macro argument" +@test parseall_nolines(""" + macro f(args...) end\n@f "macro argument" """) == Expr(:toplevel, Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block, LineNumberNode(1, :none), LineNumberNode(1, :none))), - Expr(:macrocall, Symbol("@f"), LineNumberNode(1, :none), "macro argument")) + Expr(:macrocall, Symbol("@f"), LineNumberNode(2, :none), "macro argument")) # blocks vs. tuples @test Meta.parse("()") == Expr(:tuple) @test Meta.parse("(;)") == Expr(:tuple, Expr(:parameters)) @test Meta.parse("(;;)") == Expr(:block) @test Meta.parse("(;;;;)") == Expr(:block) -@test_throws ParseError Meta.parse("(,)") -@test_throws ParseError Meta.parse("(;,)") -@test_throws ParseError Meta.parse("(,;)") +@test_parseerror "(,)" +@test_parseerror "(;,)" +@test_parseerror "(,;)" # TODO: would be nice to make these errors, but needed to parse e.g. `(x;y,)->x` -#@test_throws ParseError Meta.parse("(1;2,)") -#@test_throws ParseError Meta.parse("(1;2,;)") -#@test_throws ParseError Meta.parse("(1;2,;3)") +#@test_parseerror "(1;2,)" +#@test_parseerror "(1;2,;)" +#@test_parseerror "(1;2,;3)" @test Meta.parse("(x;)") == Expr(:block, :x) @test Meta.parse("(;x)") == Expr(:tuple, Expr(:parameters, :x)) @test Meta.parse("(;x,)") == Expr(:tuple, Expr(:parameters, :x)) @@ -197,7 +204,7 @@ macro test999_str(args...); args; end @test Meta.parse("(x,a;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x, :a) @test Meta.parse("(x,a;y=1,z=2)") == Expr(:tuple, Expr(:parameters, Expr(:kw,:y,1), Expr(:kw,:z,2)), :x, :a) @test Meta.parse("(a=1, b=2)") == Expr(:tuple, Expr(:(=), :a, 1), Expr(:(=), :b, 2)) -@test_throws ParseError Meta.parse("(1 2)") # issue #15248 +@test_parseerror "(1 2)" # issue #15248 @test Meta.parse("f(x;)") == Expr(:call, :f, Expr(:parameters), :x) @@ -268,13 +275,16 @@ end @test_throws BoundsError Meta.parse("x = 1", 7) # issue #14683 -@test_throws ParseError Meta.parse("'\\A\"'") +@test_parseerror "'\\A\"'" @test Meta.parse("'\"'") == Meta.parse("'\\\"'") == '"' == "\""[1] == '\42' # issue #24558 @test '\u2200' == "\u2200"[1] -@test_throws ParseError Meta.parse("f(2x for x=1:10, y") +if !using_JuliaSyntax + # This should be Expr(:incomplete) + @test_parseerror "f(2x for x=1:10, y" +end # issue #15223 call0(f) = f() @@ -310,11 +320,6 @@ let p = 15 @test 2p+1 == 31 # not a hex float literal end -macro test_parseerror(str, msg) - ex = :(@test_throws ParseError($(esc(msg))) Meta.parse($(esc(str)))) - ex.args[2] = __source__ - return ex -end @test_parseerror("0x", "invalid numeric constant \"0x\"") @test_parseerror("0b", "invalid numeric constant \"0b\"") @test_parseerror("0o", "invalid numeric constant \"0o\"") @@ -322,9 +327,8 @@ end @test_parseerror("0x1.0p", "invalid numeric constant \"0x1.0\"") # issue #15798 -@test Meta.lower(Main, Base.parse_input_line(""" - try = "No" - """)) == Expr(:error, "unexpected \"=\"") +# lowering preserves Expr(:error) +@test Meta.lower(Main, Expr(:error, "no")) == Expr(:error, "no") # issue #19861 make sure macro-expansion happens in the newest world for top-level expression @test eval(Base.parse_input_line(""" @@ -368,9 +372,9 @@ add_method_to_glob_fn!() @test f15844(Int64(1)) == 3 # issue #15661 -@test_throws ParseError Meta.parse("function catch() end") -@test_throws ParseError Meta.parse("function end() end") -@test_throws ParseError Meta.parse("function finally() end") +@test_parseerror "function catch() end" +@test_parseerror "function end() end" +@test_parseerror "function finally() end" # PR #16170 @test Meta.lower(Main, Meta.parse("true(x) = x")) == Expr(:error, "invalid function name \"true\"") @@ -421,18 +425,18 @@ end :y)) # test that pre 0.5 deprecated syntax is a parse error -@test_throws ParseError Meta.parse("Int [1,2,3]") -@test_throws ParseError Meta.parse("Int [x for x in 1:10]") -@test_throws ParseError Meta.parse("foo (x) = x") -@test_throws ParseError Meta.parse("foo {T<:Int}(x::T) = x") +@test_parseerror "Int [1,2,3]" +@test_parseerror "Int [x for x in 1:10]" +@test_parseerror "foo (x) = x" +@test_parseerror "foo {T<:Int}(x::T) = x" -@test_throws ParseError Meta.parse("Foo .bar") +@test_parseerror "Foo .bar" -@test_throws ParseError Meta.parse("import x .y") -@test_throws ParseError Meta.parse("using x .y") +@test_parseerror "import x .y" +@test_parseerror "using x .y" -@test_throws ParseError Meta.parse("--x") -@test_throws ParseError Meta.parse("stagedfunction foo(x); end") +@test_parseerror "--x" +@test_parseerror "stagedfunction foo(x); end" @test Meta.parse("A=>B") == Expr(:call, :(=>), :A, :B) @@ -448,7 +452,7 @@ end @test Meta.parse("[a,;c]") == Expr(:vect, Expr(:parameters, :c), :a) @test Meta.parse("a[b,c;d]") == Expr(:ref, :a, Expr(:parameters, :d), :b, :c) @test Meta.parse("a[b,;d]") == Expr(:ref, :a, Expr(:parameters, :d), :b) -@test_throws ParseError Meta.parse("[a,;,b]") +@test_parseerror "[a,;,b]" @test Meta.parse("{a,b;c}") == Expr(:braces, Expr(:parameters, :c), :a, :b) @test Meta.parse("{a,;c}") == Expr(:braces, Expr(:parameters, :c), :a) @test Meta.parse("a{b,c;d}") == Expr(:curly, :a, Expr(:parameters, :d), :b, :c) @@ -534,10 +538,13 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c "let;" => :block, "for i=1;" => :block, "function f();" => :block, "f() do x;" => :block, "module X;" => :block, "mutable struct X;" => :block, "struct X;" => :block, "(" => :other, "[" => :other, - "begin" => :other, "quote" => :other, - "let" => :other, "for" => :other, "function" => :other, + "for" => :other, "function" => :other, "f() do" => :other, "module" => :other, "mutable struct" => :other, - "struct" => :other) + "struct" => :other, + "quote" => using_JuliaSyntax ? :block : :other, + "let" => using_JuliaSyntax ? :block : :other, + "begin" => using_JuliaSyntax ? :block : :other, + ) @test Base.incomplete_tag(Meta.parse(str, raise=false)) == tag end @@ -622,7 +629,7 @@ end # issue 10046 for op in ["+", "-", "\$", "|", ".+", ".-", "*", ".*"] - @test_throws ParseError Meta.parse("$op in [+, -]") + @test_parseerror "$op in [+, -]" end # issue #17701 @@ -634,7 +641,7 @@ end # PR #15592 let str = "[1] [2]" - @test_throws ParseError Meta.parse(str) + @test_parseerror str end # issue 15896 and PR 15913 @@ -997,14 +1004,14 @@ end @test Test21604.X(1.0) === Test21604.X(1.0) # issue #20575 -@test_throws ParseError Meta.parse("\"a\"x") -@test_throws ParseError Meta.parse("\"a\"begin end") -@test_throws ParseError Meta.parse("\"a\"begin end\"b\"") +@test_parseerror "\"a\"x" +@test_parseerror "\"a\"begin end" +@test_parseerror "\"a\"begin end\"b\"" # issue #16427 -@test_throws ParseError Meta.parse("for i=1:1 end(3)") -@test_throws ParseError Meta.parse("begin end(3)") -@test_throws ParseError Meta.parse("while false end(3)") +@test_parseerror "for i=1:1 end(3)" +@test_parseerror "begin end(3)" +@test_parseerror "while false end(3)" # comment 298107224 on pull #21607 module Test21607 @@ -1065,7 +1072,7 @@ end === (3, String) @test Meta.parse("3 +⁽¹⁾ 4") == Expr(:call, :+⁽¹⁾, 3, 4) @test Meta.parse("3 +₍₀₎ 4") == Expr(:call, :+₍₀₎, 3, 4) for bad in ('=', '$', ':', "||", "&&", "->", "<:") - @test_throws ParseError Meta.parse("3 $(bad)⁽¹⁾ 4") + @test_parseerror "3 $(bad)⁽¹⁾ 4" end @test Base.operator_precedence(:+̂) == Base.operator_precedence(:+) @@ -1080,20 +1087,20 @@ end Expr(:tuple, :x, :y), Expr(:tuple, 1, 2))) -@test_throws ParseError Meta.parse("[2for i=1:10]") -@test_throws ParseError Meta.parse("[1 for i in 1:2for j in 2]") -@test_throws ParseError Meta.parse("(1 for i in 1:2for j in 2)") +@test_parseerror "[2for i=1:10]" +@test_parseerror "[1 for i in 1:2for j in 2]" +@test_parseerror "(1 for i in 1:2for j in 2)" # issue #20441 -@test_throws ParseError Meta.parse("[x.2]") -@test_throws ParseError Meta.parse("x.2") +@test_parseerror "[x.2]" +@test_parseerror "x.2" @test Meta.parse("[x;.2]") == Expr(:vcat, :x, 0.2) # issue #22840 @test Meta.parse("[:a :b]") == Expr(:hcat, QuoteNode(:a), QuoteNode(:b)) # issue #22868 -@test_throws ParseError Meta.parse("x@time 2") -@test_throws ParseError Meta.parse("@ time") +@test_parseerror "x@time 2" +@test_parseerror "@ time" # issue #7479 @test Meta.lower(Main, Meta.parse("(true &&& false)")) == Expr(:error, "invalid syntax &false") @@ -1102,9 +1109,9 @@ end @test Meta.lower(Main, :(&(1, 2))) == Expr(:error, "invalid syntax &(1, 2)") # if an indexing expression becomes a cat expression, `end` is not special -@test_throws ParseError Meta.parse("a[end end]") -@test_throws ParseError Meta.parse("a[end;end]") -#@test_throws ParseError Meta.parse("a[end;]") # this is difficult to fix +@test_parseerror "a[end end]" +@test_parseerror "a[end;end]" +#@test_parseerror "a[end;]" # this is difficult to fix let a = rand(8), i = 3 @test a[[1:i-1; i+1:end]] == a[[1,2,4,5,6,7,8]] end @@ -1115,12 +1122,12 @@ end end for i = 1:5] == fill(nothing, 5) # issue #18912 -@test_throws ParseError Meta.parse("(::)") +@test_parseerror "(::)" @test Meta.parse(":(::)") == QuoteNode(Symbol("::")) -@test_throws ParseError Meta.parse("f(::) = ::") +@test_parseerror "f(::) = ::" @test Meta.parse("(::A)") == Expr(Symbol("::"), :A) -@test_throws ParseError Meta.parse("(::, 1)") -@test_throws ParseError Meta.parse("(1, ::)") +@test_parseerror "(::, 1)" +@test_parseerror "(1, ::)" # issue #18650 let ex = Meta.parse("maximum(@elapsed sleep(1) for k = 1:10)") @@ -1192,10 +1199,10 @@ M24289.@m24289 # parsing numbers with _ and . @test Meta.parse("1_2.3_4") == 12.34 -@test_throws ParseError Meta.parse("1._") -@test_throws ParseError Meta.parse("1._5") -@test_throws ParseError Meta.parse("1e.3") -@test_throws ParseError Meta.parse("1e3.") +@test_parseerror "1._" +@test_parseerror "1._5" +@test_parseerror "1e.3" +@test_parseerror "1e3." @test Meta.parse("2e_1") == Expr(:call, :*, 2, :e_1) # issue #17705 @test Meta.parse("2e3_") == Expr(:call, :*, 2e3, :_) @@ -1261,8 +1268,10 @@ end @test raw"x \\\ y" == "x \\\\\\ y" end -@test_throws ParseError("expected \"}\" or separator in arguments to \"{ }\"; got \"V)\"") Meta.parse("f(x::V) where {V) = x") -@test_throws ParseError("expected \"]\" or separator in arguments to \"[ ]\"; got \"1)\"") Meta.parse("[1)") +@test_parseerror("f(x::V) where {V) = x", + "expected \"}\" or separator in arguments to \"{ }\"; got \"V)\"") +@test_parseerror("[1)", + "expected \"]\" or separator in arguments to \"[ ]\"; got \"1)\"") # issue #9972 @test Meta.lower(@__MODULE__, :(f(;3))) == Expr(:error, "invalid keyword argument syntax \"3\"") @@ -1310,7 +1319,7 @@ let getindex = 0, setindex! = 1, colon = 2, vcat = 3, hcat = 4, hvcat = 5 end # issue #25020 -@test_throws ParseError Meta.parse("using Colors()") +@test_parseerror "using Colors()" let ex = Meta.parse("md\"x\" f(x) = x", 1)[1] # custom string literal is not a docstring @@ -1364,18 +1373,18 @@ end @test Meta.parse("-(x;;;)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block, :x), 2)) @test Meta.parse("+((1,2))") == Expr(:call, :+, Expr(:tuple, 1, 2)) -@test_throws ParseError("space before \"(\" not allowed in \"+ (\" at none:1") Meta.parse("1 -+ (a=1, b=2)") +@test_parseerror "1 -+ (a=1, b=2)" "space before \"(\" not allowed in \"+ (\" at none:1" # issue #29781 -@test_throws ParseError("space before \"(\" not allowed in \"sin. (\" at none:1") Meta.parse("sin. (1)") +@test_parseerror "sin. (1)" "space before \"(\" not allowed in \"sin. (\" at none:1" # Parser errors for disallowed space contain line numbers -@test_throws ParseError("space before \"[\" not allowed in \"f() [\" at none:2") Meta.parse("\nf() [i]") -@test_throws ParseError("space before \"(\" not allowed in \"f() (\" at none:2") Meta.parse("\nf() (i)") -@test_throws ParseError("space before \".\" not allowed in \"f() .\" at none:2") Meta.parse("\nf() .i") -@test_throws ParseError("space before \"{\" not allowed in \"f() {\" at none:2") Meta.parse("\nf() {i}") -@test_throws ParseError("space before \"m\" not allowed in \"@ m\" at none:2") Meta.parse("\n@ m") -@test_throws ParseError("space before \".\" not allowed in \"a .\" at none:2") Meta.parse("\nusing a .b") -@test_throws ParseError("space before \".\" not allowed in \"a .\" at none:2") Meta.parse("\nusing a .b") -@test_throws ParseError("space before \"(\" not allowed in \"+ (\" at none:2") Meta.parse("\n+ (x, y)") +@test_parseerror "\nf() [i]" "space before \"[\" not allowed in \"f() [\" at none:2" +@test_parseerror "\nf() (i)" "space before \"(\" not allowed in \"f() (\" at none:2" +@test_parseerror "\nf() .i" "space before \".\" not allowed in \"f() .\" at none:2" +@test_parseerror "\nf() {i}" "space before \"{\" not allowed in \"f() {\" at none:2" +@test_parseerror "\n@ m" "space before \"m\" not allowed in \"@ m\" at none:2" +@test_parseerror "\nusing a .b" "space before \".\" not allowed in \"a .\" at none:2" +@test_parseerror "\nusing a .b" "space before \".\" not allowed in \"a .\" at none:2" +@test_parseerror "\n+ (x, y)" "space before \"(\" not allowed in \"+ (\" at none:2" @test Meta.parse("1 -+(a=1, b=2)") == Expr(:call, :-, 1, Expr(:call, :+, Expr(:kw, :a, 1), Expr(:kw, :b, 2))) @@ -1397,7 +1406,7 @@ end @test Meta.parse("-√2") == Expr(:call, :-, Expr(:call, :√, 2)) @test Meta.parse("√3x^2") == Expr(:call, :*, Expr(:call, :√, 3), Expr(:call, :^, :x, 2)) @test Meta.parse("-3x^2") == Expr(:call, :*, -3, Expr(:call, :^, :x, 2)) -@test_throws ParseError Meta.parse("2!3") +@test_parseerror "2!3" # issue #27914 @test Meta.parse("2f(x)") == Expr(:call, :*, 2, Expr(:call, :f, :x)) @@ -1407,7 +1416,7 @@ end @test Meta.parse("2(x)") == Expr(:call, :*, 2, :x) @test Meta.parse("2(x)y") == Expr(:call, :*, 2, :x, :y) -@test_throws ParseError Meta.parse("a.: b") +@test_parseerror "a.: b" @test Meta.parse("a.:end") == Expr(:., :a, QuoteNode(:end)) @test Meta.parse("a.:catch") == Expr(:., :a, QuoteNode(:catch)) @test Meta.parse("a.end") == Expr(:., :a, QuoteNode(:end)) @@ -1423,7 +1432,7 @@ let len = 10 end # Module name cannot be a reserved word. -@test_throws ParseError Meta.parse("module module end") +@test_parseerror "module module end" @test Meta.lower(@__MODULE__, :(global true)) == Expr(:error, "invalid syntax in \"global\" declaration") @test Meta.lower(@__MODULE__, :(let ccall end)) == Expr(:error, "invalid identifier name \"ccall\"") @@ -1440,7 +1449,7 @@ end # issue #27690 # previously, this was allowed since it thought `end` was being used for indexing. # however the quote should disable that context. -@test_throws ParseError Meta.parse("Any[:(end)]") +@test_parseerror "Any[:(end)]" # issue #17781 let ex = Meta.lower(@__MODULE__, Meta.parse(" @@ -1671,20 +1680,20 @@ let x = @macroexpand @foo28244(var"let") end # #16356 -@test_throws ParseError Meta.parse("0xapi") +@test_parseerror "0xapi" # #22523 #22712 -@test_throws ParseError Meta.parse("a?b:c") -@test_throws ParseError Meta.parse("a ?b:c") -@test_throws ParseError Meta.parse("a ? b:c") -@test_throws ParseError Meta.parse("a ? b :c") -@test_throws ParseError Meta.parse("?") +@test_parseerror "a?b:c" +@test_parseerror "a ?b:c" +@test_parseerror "a ? b:c" +@test_parseerror "a ? b :c" +@test_parseerror "?" # #13079 @test Meta.parse("1<<2*3") == :((1<<2)*3) # #19987 -@test_throws ParseError Meta.parse("try ; catch f() ; end") +@test_parseerror "try ; catch f() ; end" # #23076 @test :([1,2;]) == Expr(:vect, Expr(:parameters), 1, 2) @@ -1721,8 +1730,8 @@ end @test Meta.lower(@__MODULE__, :(f(x) = (y = x + 1; ccall((:a, y), Cvoid, ())))) == Expr(:error, "ccall function name and library expression cannot reference local variables") -@test_throws ParseError Meta.parse("x.'") -@test_throws ParseError Meta.parse("0.+1") +@test_parseerror "x.'" +@test_parseerror "0.+1" # #24221 @test Meta.isexpr(Meta.lower(@__MODULE__, :(a=_)), :error) @@ -1816,7 +1825,7 @@ end @test Meta.parse("1⁝2") == Expr(:call, :⁝, 1, 2) @test Meta.parse("1..2") == Expr(:call, :.., 1, 2) # we don't parse chains of these since the associativity and meaning aren't clear -@test_throws ParseError Meta.parse("1..2..3") +@test_parseerror "1..2..3" # issue #30048 @test Meta.isexpr(Meta.lower(@__MODULE__, :(for a in b @@ -1990,9 +1999,9 @@ end @test Meta.parse("var\"#\"") === Symbol("#") @test Meta.parse("var\"true\"") === Symbol("true") @test Meta.parse("var\"false\"") === Symbol("false") -@test_throws ParseError Meta.parse("var\"#\"x") # Reject string macro-like suffix -@test_throws ParseError Meta.parse("var \"#\"") -@test_throws ParseError Meta.parse("var\"for\" i = 1:10; end") +@test_parseerror "var\"#\"x" # Reject string macro-like suffix +@test_parseerror "var \"#\"" +@test_parseerror "var\"for\" i = 1:10; end" # A few cases which would be ugly to deal with if var"#" were a string macro: @test Meta.parse("var\"#\".var\"a-b\"") == Expr(:., Symbol("#"), QuoteNode(Symbol("a-b"))) @test Meta.parse("export var\"#\"") == Expr(:export, Symbol("#")) @@ -2217,7 +2226,7 @@ end end # line break in : expression disallowed -@test_throws Meta.ParseError Meta.parse("[1 :\n2] == [1:2]") +@test_parseerror "[1 :\n2] == [1:2]" # added ⟂ to operator precedence (#24404) @test Meta.parse("a ⟂ b ⟂ c") == Expr(:comparison, :a, :⟂, :b, :⟂, :c) @@ -2238,7 +2247,8 @@ end end # only allow certain characters after interpolated vars (#25231) -@test Meta.parse("\"\$x෴ \"",raise=false) == Expr(:error, "interpolated variable \$x ends with invalid character \"෴\"; use \"\$(x)\" instead.") +@test_parseerror("\"\$x෴ \"", + "interpolated variable \$x ends with invalid character \"෴\"; use \"\$(x)\" instead.") @test Base.incomplete_tag(Meta.parse("\"\$foo", raise=false)) === :string @testset "issue #30341" begin @@ -2277,14 +2287,11 @@ end err = Expr( :error, - "\":\" in \"$imprt\" syntax can only be used when importing a single module. " * - "Split imports into multiple lines." ) - ex = Meta.parse("$imprt A, B: x, y", raise=false) - @test ex == err - - ex = Meta.parse("$imprt A: x, B: y", raise=false) - @test ex == err + @test_parseerror("$imprt A, B: x, y", + "\":\" in \"$imprt\" syntax can only be used when importing a single module. Split imports into multiple lines.") + @test_parseerror("$imprt A: x, B: y", + "\":\" in \"$imprt\" syntax can only be used when importing a single module. Split imports into multiple lines.") end end @@ -2304,24 +2311,31 @@ let exc = try eval(:(f(x,x)=1)) catch e ; e ; end @test !occursin("incorrect_file", exc.msg) end -# issue #34967 -@test_throws LoadError("string", 2, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - "x34967 = 1\n# Halloa\xf5b\nx34967 = 2") -@test x34967 == 1 -@test_throws LoadError("string", 1, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - "x\xf5 = 3\n# Halloa\xf5b\nx34967 = 4") -@test_throws LoadError("string", 3, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - """ - # line 1 - # line 2 - # Hello\xf5b - x34967 = 6 - """) - -@test Meta.parse("aa\u200b_", raise=false) == - Expr(:error, "invisible character \\u200b near column 3") -@test Meta.parse("aa\UE0080", raise=false) == - Expr(:error, "invalid character \"\Ue0080\" near column 3") +@testset "issue #34967" begin + @test_parseerror "#\xf5b\nx" "invalid UTF-8 sequence" + + # Test line UTF-8 errors with line numbers + let ex = Meta.parseall("x\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 4) && Meta.isexpr(last(ex.args), :error) + @test ex.args[3] == LineNumberNode(2,:none) + end + let ex = Meta.parseall("x\xf5\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 2) && Meta.isexpr(last(ex.args), :error) + @test ex.args[1] == LineNumberNode(1,:none) + end + let ex = Meta.parseall("#line1\n#line2\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 2) && Meta.isexpr(last(ex.args), :error) + @test ex.args[1] == LineNumberNode(3,:none) + end +end + +@test_parseerror "aa\u200b_" "invisible character \\u200b near column 3" +@test_parseerror "aa\UE0080" "invalid character \"\Ue0080\" near column 3" + +@testset "unrecognized escapes in string/char literals" begin + @test_parseerror "\"\\.\"" + @test_parseerror "\'\\.\'" +end # issue #31238 a31238, b31238 = let x @@ -2390,8 +2404,8 @@ end @test x == 6 # issue #36196 -@test_throws ParseError("\"for\" at none:1 expected \"end\", got \")\"") Meta.parse("(for i=1; println())") -@test_throws ParseError("\"try\" at none:1 expected \"end\", got \")\"") Meta.parse("(try i=1; println())") +@test_parseerror "(for i=1; println())" "\"for\" at none:1 expected \"end\", got \")\"" +@test_parseerror "(try i=1; println())" "\"try\" at none:1 expected \"end\", got \")\"" # issue #36272 macro m36272() @@ -2438,10 +2452,10 @@ end let (-->) = (+) @test (40 --> 2) == 42 end -@test_throws ParseError("invalid operator \"<---\"") Meta.parse("1<---2") -@test_throws ParseError("invalid operator \".<---\"") Meta.parse("1 .<--- 2") -@test_throws ParseError("invalid operator \"--\"") Meta.parse("a---b") -@test_throws ParseError("invalid operator \".--\"") Meta.parse("a.---b") +@test_parseerror("1<---2", "invalid operator \"<---\"") +@test_parseerror("1 .<--- 2", "invalid operator \".<---\"") +@test_parseerror("a---b", "invalid operator \"--\"") +@test_parseerror("a.---b", "invalid operator \".--\"") # issue #37228 # NOTE: the `if` needs to be at the top level @@ -2476,15 +2490,14 @@ end @test :(if true 'a' else 1 end) == Expr(:if, true, quote 'a' end, quote 1 end) # issue #37664 -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a#==#b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a #==#b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a#==# b") - -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1 2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1#==#2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1 #==#2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1#==# 2") +@test_parseerror("a b", "extra token \"b\" after end of expression") +@test_parseerror("a#==#b", "extra token \"b\" after end of expression") +@test_parseerror("a #==#b", "extra token \"b\" after end of expression") +@test_parseerror("a#==# b", "extra token \"b\" after end of expression") +@test_parseerror("1 2", "extra token \"2\" after end of expression") +@test_parseerror("1#==#2", "extra token \"2\" after end of expression") +@test_parseerror("1 #==#2", "extra token \"2\" after end of expression") +@test_parseerror("1#==# 2", "extra token \"2\" after end of expression") @test size([1#==#2#==#3]) == size([1 2 3]) @test size([1#==#2#==#3]) == size([1 2 3]) # tabs @@ -2507,9 +2520,7 @@ end Meta.parse("if#==#x0#==#y+1#==#else#==#z#==#end") @test Meta.parse("function(x) x end") == Meta.parse("function(x)#==#x#==#end") @test Meta.parse("a ? b : c") == Meta.parse("a#==#?#==#b#==#:#==#c") -@test_throws ParseError("space before \"(\" not allowed in \"f (\" at none:1") begin - Meta.parse("f#==#(x)=x") -end +@test_parseerror("f#==#(x)=x", "space before \"(\" not allowed in \"f (\" at none:1") @test Meta.parse("try f() catch e g() finally h() end") == Meta.parse("try#==#f()#==#catch#==#e#==#g()#==#finally#==#h()#==#end") @test Meta.parse("@m a b") == Meta.parse("@m#==#a#==#b") @@ -2541,11 +2552,11 @@ end @test B37890(1.0, 2.0f0) isa B37890{Int, Int8} # import ... as -@test_throws ParseError("invalid syntax \"using A as ...\"") Meta.parse("using A as B") -@test_throws ParseError("invalid syntax \"using A.b as ...\"") Meta.parse("using A.b as B") -@test_throws ParseError("invalid syntax \"using A.b as ...\"") Meta.parse("using X, A.b as B") -@test_throws ParseError("invalid syntax \"import A as B:\"") Meta.parse("import A as B: c") -@test_throws ParseError("invalid syntax \"import A.b as B:\"") Meta.parse("import A.b as B: c") +@test_parseerror("using A as B", "invalid syntax \"using A as ...\"") +@test_parseerror("using A.b as B", "invalid syntax \"using A.b as ...\"") +@test_parseerror("using X, A.b as B", "invalid syntax \"using A.b as ...\"") +@test_parseerror("import A as B: c", "invalid syntax \"import A as B:\"") +@test_parseerror("import A.b as B: c", "invalid syntax \"import A.b as B:\"") module TestImportAs using Test @@ -2584,7 +2595,9 @@ import .Mod2.y as y2 @test y2 == 2 @test !@isdefined(y) -@test_throws ErrorException eval(:(import .Mod.x as (a.b))) +# Test that eval rejects the invalid syntax `import .Mod.x as (a.b)` +@test_throws ErrorException eval( + Expr(:import, Expr(:as, Expr(:., :., :Mod, :x), Expr(:., :a, QuoteNode(:b))))) import .Mod.maybe_undef as mu @test_throws UndefVarError mu @@ -2662,10 +2675,10 @@ end @test Meta.isexpr(Meta.parse(""" f(i for i in 1:3)""").args[2], :generator) - @test_throws Meta.ParseError Meta.parse(""" + @test_parseerror """ for i in 1:3 - end""") + end""" end # PR #37973 @@ -2820,7 +2833,7 @@ end Expr(:nrow, 1, Expr(:row, 0, 9, 3), Expr(:row, 4, 5, 4))) @test :([1 ; 2 ;; 3 ; 4]) == Expr(:ncat, 2, Expr(:nrow, 1, 1, 2), Expr(:nrow, 1, 3, 4)) - @test_throws ParseError Meta.parse("[1 2 ;; 3 4]") # cannot mix spaces and ;; except as line break + @test_parseerror "[1 2 ;; 3 4]" # cannot mix spaces and ;; except as line break @test :([1 2 ;; 3 4]) == :([1 2 3 4]) @test :([1 2 ;; @@ -2830,8 +2843,8 @@ end @test Meta.parse("[1;\n\n]") == :([1;]) @test Meta.parse("[1\n;]") == :([1;]) # semicolons following a linebreak are fine @test Meta.parse("[1\n;;; 2]") == :([1;;; 2]) - @test_throws ParseError Meta.parse("[1;\n;2]") # semicolons cannot straddle a line break - @test_throws ParseError Meta.parse("[1; ;2]") # semicolons cannot be separated by a space + @test_parseerror "[1;\n;2]" # semicolons cannot straddle a line break + @test_parseerror "[1; ;2]" # semicolons cannot be separated by a space end # issue #25652 @@ -3104,10 +3117,10 @@ end @test fails(error) @test !fails(() -> 1 + 2) - @test_throws ParseError Meta.parse("try foo() else bar() end") - @test_throws ParseError Meta.parse("try foo() else bar() catch; baz() end") - @test_throws ParseError Meta.parse("try foo() catch; baz() finally foobar() else bar() end") - @test_throws ParseError Meta.parse("try foo() finally foobar() else bar() catch; baz() end") + @test_parseerror "try foo() else bar() end" + @test_parseerror "try foo() else bar() catch; baz() end" + @test_parseerror "try foo() catch; baz() finally foobar() else bar() end" + @test_parseerror "try foo() finally foobar() else bar() catch; baz() end" err = try try @@ -3172,23 +3185,23 @@ end @test x == 1 end -@test_throws ParseError Meta.parse(""" +@test_parseerror """ function checkUserAccess(u::User) if u.accessLevel != "user\u202e \u2066# users are not allowed\u2069\u2066" return true end return false end -""") +""" -@test_throws ParseError Meta.parse(""" +@test_parseerror """ function checkUserAccess(u::User) #=\u202e \u2066if (u.isAdmin)\u2069 \u2066 begin admins only =# return true #= end admin only \u202e \u2066end\u2069 \u2066=# return false end -""") +""" @testset "empty nd arrays" begin @test :([]) == Expr(:vect) @@ -3219,9 +3232,9 @@ end ;; ]) == Expr(:ncat, 2) - @test_throws ParseError Meta.parse("[; ;]") - @test_throws ParseError Meta.parse("[;; ;]") - @test_throws ParseError Meta.parse("[;\n;]") + @test_parseerror "[; ;]" + @test_parseerror "[;; ;]" + @test_parseerror "[;\n;]" end @test Meta.parseatom("@foo", 1; filename="foo", lineno=7) == (Expr(:macrocall, :var"@foo", LineNumberNode(7, :foo)), 5) @@ -3415,14 +3428,12 @@ f45162(f) = f(x=1) @test first(methods(f45162)).called != 0 # issue #45024 -@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x") -@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x::Int") +@test_parseerror "const x" "expected assignment after \"const\"" +@test_parseerror "const x::Int" "expected assignment after \"const\"" # these cases have always been caught during lowering, since (const (global x)) is not # ambiguous with the lowered form (const x), but that could probably be changed. -@test Meta.lower(@__MODULE__, :(global const x)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(global const x::Int)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(const global x)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(const global x::Int)) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, Expr(:const, Expr(:global, :x))) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, Expr(:const, Expr(:global, Expr(:(::), :x, :Int)))) == Expr(:error, "expected assignment after \"const\"") @testset "issue 25072" begin @test '\xc0\x80' == reinterpret(Char, 0xc0800000) From 65523e459daa455500202196abedc64bce77bef3 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Sat, 17 Jun 2023 20:46:45 +0000 Subject: [PATCH 16/21] Add some JIT docs (#50168) --- doc/src/devdocs/jit.md | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 doc/src/devdocs/jit.md diff --git a/doc/src/devdocs/jit.md b/doc/src/devdocs/jit.md new file mode 100644 index 0000000000000..f33b968ad3948 --- /dev/null +++ b/doc/src/devdocs/jit.md @@ -0,0 +1,78 @@ +# JIT Design and Implementation + +This document explains the design and implementation of Julia's JIT, after codegen has finished and unoptimized LLVM IR has been produced. The JIT is responsible for optimizing and compiling this IR to machine code, and for linking it into the current process and making the code available for execution. + +## Introduction + +The JIT is responsible for managing compilation resources, looking up previously compiled code, and compiling new code. It is primarily built on LLVM's [On-Request-Compilation](https://llvm.org/docs/ORCv2.html) (ORCv2) technology, which provides support for a number of useful features such as concurrent compilation, lazy compilation, and the ability to compile code in a separate process. Though LLVM provides a basic JIT compiler in the form of LLJIT, Julia uses many ORCv2 APIs directly to create its own custom JIT compiler. + +## Overview + +![Diagram of the compiler flow](./img/compiler_diagram.png) + +Codegen produces an LLVM module containing IR for one or more Julia functions from the original Julia SSA IR produced by type inference (labeled as translate on the compiler diagram above). It also produces a mapping of code-instance to LLVM function name. However, though some optimizations have been applied by the Julia-based compiler on Julia IR, the LLVM IR produced by codegen still contains many opportunities for optimization. Thus, the first step the JIT takes is to run a target-independent optimization pipeline[^tdp] on the LLVM module. Then, the JIT runs a target-dependent optimization pipeline, which includes target-specific optimizations and code generation, and outputs an object file. Finally, the JIT links the resulting object file into the current process and makes the code available for execution. All of this is controlled by code in `src/jitlayers.cpp`. + +[^tdp]: This is not a totally-target independent pipeline, as transformations such as vectorization rely upon target information such as vector register width and cost modeling. Additionally, codegen itself makes a few target-dependent assumptions, and the optimization pipeline will take advantage of that knowledge. + +Currently, only one thread at a time is permitted to enter the optimize-compile-link pipeline at a time, due to restrictions imposed by one of our linkers (RuntimeDyld). However, the JIT is designed to support concurrent optimization and compilation, and the linker restriction is expected to be lifted in the future when RuntimeDyld has been fully superseded on all platforms. + +## Optimization Pipeline + +The optimization pipeline is based off LLVM's new pass manager, but the pipeline is customized for Julia's needs. The pipeline is defined in `src/pipeline.cpp`, and broadly proceeds through a number of stages as detailed below. + +1. Early Simplification + 1. These passes are mainly used to simplify the IR and canonicalize patterns so that later passes can identify those patterns more easily. Additionally, various intrinsic calls such as branch prediction hints and annotations are lowered into other metadata or other IR features. [`SimplifyCFG`](https://llvm.org/docs/Passes.html#simplifycfg-simplify-the-cfg) (simplify control flow graph), [`DCE`](https://llvm.org/docs/Passes.html#dce-dead-code-elimination) (dead code elimination), and [`SROA`](https://llvm.org/docs/Passes.html#sroa-scalar-replacement-of-aggregates) (scalar replacement of aggregates) are some of the key players here. +2. Early Optimization + 1. These passes are typically cheap and are primarily focused around reducing the number of instructions in the IR and propagating knowledge to other instructions. For example, [`EarlyCSE`](https://en.wikipedia.org/wiki/Common_subexpression_elimination) is used to perform common subexpression elimination, and [`InstCombine`](https://llvm.org/docs/Passes.html#instcombine-combine-redundant-instructions) and [`InstSimplify`](https://llvm.org/doxygen/classllvm_1_1InstSimplifyPass.html#details) perform a number of small peephole optimizations to make operations less expensive. +3. Loop Optimization + 1. These passes canonicalize and simplify loops. Loops are often hot code, which makes loop optimization extremely important for performance. Key players here include [`LoopRotate`](https://llvm.org/docs/Passes.html#loop-rotate-rotate-loops), [`LICM`](https://llvm.org/docs/Passes.html#licm-loop-invariant-code-motion), and [`LoopFullUnroll`](https://llvm.org/docs/Passes.html#loop-unroll-unroll-loops). Some bounds check elimination also happens here, as a result of the [`IRCE`](https://llvm.org/doxygen/InductiveRangeCheckElimination_8cpp_source.html) pass which can prove certain bounds are never exceeded. +4. Scalar Optimization + 1. The scalar optimization pipeline contains a number of more expensive, but more powerful passes such as [`GVN`](https://llvm.org/docs/Passes.html#gvn-global-value-numbering) (global value numbering), [`SCCP`](https://llvm.org/docs/Passes.html#sccp-sparse-conditional-constant-propagation) (sparse conditional constant propagation), and another round of bounds check elimination. These passes are expensive, but they can often remove large amounts of code and make vectorization much more successful and effective. Several other simplification and optimization passes intersperse the more expensive ones to reduce the amount of work they have to do. +5. Vectorization + 1. [Automatic vectorization](https://en.wikipedia.org/wiki/Automatic_vectorization) is an extremely powerful transformation for CPU-intensive code. Briefly, vectorization allows execution of a [single instruction on multiple data](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) (SIMD), e.g. performing 8 addition operations at the same time. However, proving code to be both capable of vectorization and profitable to vectorize is difficult, and this relies heavily on the prior optimization passes to massage the IR into a state where vectorization is worth it. +6. Intrinsic Lowering + 1. Julia inserts a number of custom intrinsics, for reasons such as object allocation, garbage collection, and exception handling. These intrinsics were originally placed to make optimization opportunities more obvious, but they are now lowered into LLVM IR to enable the IR to be emitted as machine code. +7. Cleanup + 1. These passes are last-chance optimizations, and perform small optimizations such as fused multiply-add propagation and division-remainder simplification. Additionally, targets that do not support half-precision floating point numbers will have their half-precision instructions lowered into single-precision instructions here, and passes are added to provide sanitizer support. + +## Target-Dependent Optimization and Code Generation + +LLVM provides target-dependent optimization and machine code generation in the same pipeline, located in the TargetMachine for a given platform. These passes include instruction selection, instruction scheduling, register allocation, and machine code emission. The LLVM documentation provides a good overview of the process, and the LLVM source code is the best place to look for details on the pipeline and passes. + +## Linking + +Currently, Julia is transitioning between two linkers: the older RuntimeDyld linker, and the newer [JITLink](https://llvm.org/docs/JITLink.html) linker. JITLink contains a number of features that RuntimeDyld does not have, such as concurrent and reentrant linking, but currently lacks good support for profiling integrations and does not yet support all of the platforms that RuntimeDyld supports. Over time, JITLink is expected to replace RuntimeDyld entirely. Further details on JITLink can be found in the LLVM documentation. + +## Execution + +Once the code has been linked into the current process, it is available for execution. This fact is made known to the generating codeinst by updating the `invoke`, `specsigflags`, and `specptr` fields appropriately. Codeinsts support upgrading `invoke`, `specsigflags`, and `specptr` fields, so long as every combination of these fields that exists at any given point in time is valid to be called. This allows the JIT to update these fields without invalidating existing codeinsts, supporting a potential future concurrent JIT. Specifically, the following states may be valid: +1. `invoke` is NULL, `specsigflags` is 0b00, `specptr` is NULL + 1. This is the initial state of a codeinst, and indicates that the codeinst has not yet been compiled. +2. `invoke` is non-null, `specsigflags` is 0b00, `specptr` is NULL + 1. This indicates that the codeinst was not compiled with any specialization, and that the codeinst should be invoked directly. Note that in this instance, `invoke` does not read either the `specsigflags` or `specptr` fields, and therefore they may be modified without invalidating the `invoke` pointer. +3. `invoke` is non-null, `specsigflags` is 0b10, `specptr` is non-null + 1. This indicates that the codeinst was compiled, but a specialized function signature was deemed unnecessary by codegen. +4. `invoke` is non-null, `specsigflags` is 0b11, `specptr` is non-null + 1. This indicates that the codeinst was compiled, and a specialized function signature was deemed necessary by codegen. The `specptr` field contains a pointer to the specialized function signature. The `invoke` pointer is permitted to read both `specsigflags` and `specptr` fields. + +In addition, there are a number of different transitional states that occur during the update process. To account for these potential situations, the following write and read patterns should be used when dealing with these codeinst fields. + +1. When writing `invoke`, `specsigflags`, and `specptr`: + 1. Perform an atomic compare-exchange operation of specptr assuming the old value was NULL. This compare-exchange operation should have at least acquire-release ordering, to provide ordering guarantees of the remaining memory operations in the write. + 2. If `specptr` was non-null, cease the write operation and wait for bit 0b10 of `specsigflags` to be written. + 3. Write the new low bit of `specsigflags` to its final value. This may be a relaxed write. + 4. Write the new `invoke` pointer to its final value. This must have at least a release memory ordering to synchronize with reads of `invoke`. + 5. Set the second bit of `specsigflags` to 1. This must be at least a release memory ordering to synchronize with reads of `specsigflags`. This step completes the write operation and announces to all other threads that all fields have been set. +2. When reading all of `invoke`, `specsigflags`, and `specptr`: + 1. Read the `invoke` field with at least an acquire memory ordering. This load will be referred to as `initial_invoke`. + 2. If `initial_invoke` is NULL, the codeinst is not yet executable. `invoke` is NULL, `specsigflags` may be treated as 0b00, `specptr` may be treated as NULL. + 3. Read the `specptr` field with at least an acquire memory ordering. + 4. If `specptr` is NULL, then the `initial_invoke` pointer must not be relying on `specptr` to guarantee correct execution. Therefore, `invoke` is non-null, `specsigflags` may be treated as 0b00, `specptr` may be treated as NULL. + 5. If `specptr` is non-null, then `initial_invoke` might not be the final `invoke` field that uses `specptr`. This can occur if `specptr` has been written, but `invoke` has not yet been written. Therefore, spin on the second bit of `specsigflags` until it is set to 1 with at least acquire memory ordering. + 6. Re-read the `invoke` field with at least an acquire memory ordering. This load will be referred to as `final_invoke`. + 7. Read the `specsigflags` field with any memory ordering. + 8. `invoke` is `final_invoke`, `specsigflags` is the value read in step 7, `specptr` is the value read in step 3. +3. When updating a `specptr` to a different but equivalent function pointer: + 1. Perform a release store of the new function pointer to `specptr`. Races here must be benign, as the old function pointer is required to still be valid, and any new ones are also required to be valid as well. Once a pointer has been written to `specptr`, it must always be callable whether or not it is later overwritten. + +Although these write, read, and update steps are complicated, they ensure that the JIT can update codeinsts without invalidating existing codeinsts, and that the JIT can update codeinsts without invalidating existing `invoke` pointers. This allows the JIT to potentially reoptimize functions at higher optimization levels in the future, and also will allow the JIT to support concurrent compilation of functions in the future. From 90521dd63913aafc08eabbe2f4f8e0806cfeecb9 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Mon, 19 Jun 2023 03:54:21 +0000 Subject: [PATCH 17/21] Emphasize the default base of multiversioning in the documentation, and discourage the implicit default base (#50211) --- doc/src/devdocs/sysimg.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/src/devdocs/sysimg.md b/doc/src/devdocs/sysimg.md index 6706e30ce97b1..40fcd3fa602f8 100644 --- a/doc/src/devdocs/sysimg.md +++ b/doc/src/devdocs/sysimg.md @@ -42,6 +42,9 @@ All features supported by LLVM are supported and a feature can be disabled with (`+` prefix is also allowed and ignored to be consistent with LLVM syntax). Additionally, a few special features are supported to control the function cloning behavior. +!!! note + It is good practice to specify either `clone_all` or `base()` for every target apart from the first one. This makes it explicit which targets have all functions cloned, and which targets are based on other targets. If this is not done, the default behavior is to not clone every function, and to use the first target's function definition as the fallback when not cloning a function. + 1. `clone_all` By default, only functions that are the most likely to benefit from From f555b4b67d44986dc471f08fd66db40805f2f22d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 19 Jun 2023 12:03:23 -0400 Subject: [PATCH 18/21] Tweak cache pidlocking (#50214) --- base/loading.jl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index f5c7aa28395ef..b9742ec045b19 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2819,11 +2819,20 @@ global mkpidlock_hook global trymkpidlock_hook global parse_pidfile_hook -# allows processes to wait if another process is precompiling a given source already -function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String) +# The preferences hash is only known after precompilation so just assume no preferences +# meaning that if all other conditions are equal, the same package cannot be precompiled +# with different preferences at the same time. +compilecache_pidfile_path(pkg::PkgId) = compilecache_path(pkg, UInt64(0)) * ".pidfile" + +# Allows processes to wait if another process is precompiling a given source already. +# The lock file is deleted and precompilation will proceed after `stale_age` seconds if +# - the locking process no longer exists +# - the lock is held by another host, since processes cannot be checked remotely +# or after `stale_age * 25` seconds if it does still exist. +function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=60) if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook) - pidfile = string(srcpath, ".pidlock") - cachefile = invokelatest(trymkpidlock_hook, f, pidfile) + pidfile = compilecache_pidfile_path(pkg) + cachefile = invokelatest(trymkpidlock_hook, f, pidfile; stale_age) if cachefile === false pid, hostname, age = invokelatest(parse_pidfile_hook, pidfile) verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug @@ -2834,7 +2843,7 @@ function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String) end # wait until the lock is available, but don't actually acquire it # returning nothing indicates a process waited for another - return invokelatest(mkpidlock_hook, Returns(nothing), pidfile) + return invokelatest(mkpidlock_hook, Returns(nothing), pidfile; stale_age) end return cachefile else From da9cc1a39276451f7934b360fa4e68c936643482 Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:45:41 +0000 Subject: [PATCH 19/21] Expose the newpm pipeline to a C api (#50210) --- src/codegen-stubs.c | 3 +++ src/jl_exported_funcs.inc | 1 + src/pipeline.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 0853a090183dd..f2c8c705bd3dc 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -109,6 +109,9 @@ JL_DLLEXPORT uint64_t jl_getUnwindInfo_fallback(uint64_t dwAddr) JL_DLLEXPORT void jl_add_optimization_passes_fallback(void *PM, int opt_level, int lower_intrinsics) UNAVAILABLE +JL_DLLEXPORT void jl_build_newpm_pipeline_fallback(void *MPM, void *PB, int Speedup, int Size, + int lower_intrinsics, int dump_native, int external_use, int llvm_only) UNAVAILABLE + JL_DLLEXPORT void LLVMExtraAddLowerSimdLoopPass_fallback(void *PM) UNAVAILABLE JL_DLLEXPORT void LLVMExtraAddFinalLowerGCPass_fallback(void *PM) UNAVAILABLE diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 898656c142480..33b431fe12a76 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -558,6 +558,7 @@ YY(jl_getUnwindInfo) \ YY(jl_get_libllvm) \ YY(jl_add_optimization_passes) \ + YY(jl_build_newpm_pipeline) \ YY(LLVMExtraAddLowerSimdLoopPass) \ YY(LLVMExtraAddFinalLowerGCPass) \ YY(LLVMExtraAddPropagateJuliaAddrspaces) \ diff --git a/src/pipeline.cpp b/src/pipeline.cpp index 6afcac5ea8b7c..06d5aa2d3bfa8 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -571,6 +571,37 @@ static void buildPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationL MPM.addPass(AfterOptimizationMarkerPass()); } +extern "C" JL_DLLEXPORT_CODEGEN void jl_build_newpm_pipeline_impl(void *MPM, void *PB, int Speedup, int Size, + int lower_intrinsics, int dump_native, int external_use, int llvm_only) JL_NOTSAFEPOINT +{ + OptimizationLevel O; + switch (Size) { + case 1: + O = OptimizationLevel::Os; + break; + default: + O = OptimizationLevel::Oz; + break; + case 0: + switch (Speedup) { + case 0: + O = OptimizationLevel::O0; + break; + case 1: + O = OptimizationLevel::O1; + break; + case 2: + O = OptimizationLevel::O2; + break; + default: + O = OptimizationLevel::O3; + break; + } + } + buildPipeline(*reinterpret_cast(MPM), reinterpret_cast(PB), O, + OptimizationOptions{!!lower_intrinsics, !!dump_native, !!external_use, !!llvm_only}); +} + #undef JULIA_PASS namespace { From 690a5f67c13fd23c7b48e60c31bfa565c0eee861 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 19 Jun 2023 15:23:17 -0400 Subject: [PATCH 20/21] Thread through lattice in a couple more places (#50216) Now that the lattice code has stabilized, we should probably consider getting rid of the fallback methods to catch all the remaining places where we're relying on it (and to prevent any new ones from being introduced), but for now, just fix two cases I happened to run into. --- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/typelimits.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5fe0014ef3e60..3aa2366b48aa3 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1588,7 +1588,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: call = abstract_call(interp, ArgInfo(nothing, ct), si, sv, max_methods) seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) - res = tmerge(res, call.rt) + res = tmerge(typeinf_lattice(interp), res, call.rt) effects = merge_effects(effects, call.effects) if bail_out_apply(interp, InferenceLoopState(ct, res, effects), sv) add_remark!(interp, sv, "_apply_iterate inference reached maximally imprecise information. Bailing on.") diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 957796f6f5c49..11b09aeacead5 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -385,7 +385,7 @@ function tmerge(lattice::OptimizerLattice, @nospecialize(typea), @nospecialize(t # type-lattice for MaybeUndef wrapper if isa(typea, MaybeUndef) || isa(typeb, MaybeUndef) - return MaybeUndef(tmerge( + return MaybeUndef(tmerge(widenlattice(lattice), isa(typea, MaybeUndef) ? typea.typ : typea, isa(typeb, MaybeUndef) ? typeb.typ : typeb)) end From 0da46e25c865a390b5c2de20c2d40afb41fcac0a Mon Sep 17 00:00:00 2001 From: pchintalapudi <34727397+pchintalapudi@users.noreply.github.com> Date: Tue, 20 Jun 2023 02:23:18 +0000 Subject: [PATCH 21/21] Name LLVM variables from codegen (#50094) --- src/aotcompile.cpp | 11 +++ src/ccall.cpp | 57 +++++++++++++- src/cgutils.cpp | 145 ++++++++++++++++++++++++++++++------ src/codegen.cpp | 138 ++++++++++++++++++++++++++++------ src/intrinsics.cpp | 110 +++++++++++++++++++-------- src/jitlayers.cpp | 4 + src/jitlayers.h | 1 + test/cmdlineargs.jl | 51 +++++++------ test/llvmpasses/llvmcall.jl | 101 +++++++++++++++++++++++-- 9 files changed, 506 insertions(+), 112 deletions(-) diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index cf6378b4f926b..b1bef232e0915 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -305,6 +305,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm jl_codegen_params_t params(ctxt, std::move(target_info.first), std::move(target_info.second)); params.params = cgparams; params.imaging = imaging; + params.debug_level = jl_options.debug_level; params.external_linkage = _external_linkage; size_t compile_for[] = { jl_typeinf_world, _world }; for (int worlds = 0; worlds < 2; worlds++) { @@ -2082,6 +2083,16 @@ void jl_get_llvmf_defn_impl(jl_llvmf_dump_t* dump, jl_method_instance_t *mi, siz jl_codegen_params_t output(*ctx, std::move(target_info.first), std::move(target_info.second)); output.world = world; output.params = ¶ms; + output.imaging = imaging_default(); + // This would be nice, but currently it causes some assembly regressions that make printed output + // differ very significantly from the actual non-imaging mode code. + // // Force imaging mode for names of pointers + // output.imaging = true; + // This would also be nice, but it seems to cause OOMs on the windows32 builder + // // Force at least medium debug info for introspection + // No debug info = no variable names, + // max debug info = llvm.dbg.declare/value intrinsics which clutter IR output + output.debug_level = jl_options.debug_level; auto decls = jl_emit_code(m, mi, src, jlrettype, output); JL_UNLOCK(&jl_codegen_lock); // Might GC diff --git a/src/ccall.cpp b/src/ccall.cpp index 90f7417c03524..47496a3a91ba6 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -135,6 +135,7 @@ static Value *runtime_sym_lookup( BasicBlock *ccall_bb = BasicBlock::Create(irbuilder.getContext(), "ccall"); Constant *initnul = ConstantPointerNull::get(T_pvoidfunc); LoadInst *llvmf_orig = irbuilder.CreateAlignedLoad(T_pvoidfunc, llvmgv, Align(sizeof(void*))); + setName(emission_context, llvmf_orig, f_name + StringRef(".cached")); // This in principle needs a consume ordering so that load from // this pointer sees a valid value. However, this is not supported by // LLVM (or agreed on in the C/C++ standard FWIW) and should be @@ -143,8 +144,10 @@ static Value *runtime_sym_lookup( // invalid load from the `cglobal` but doesn't depend on the `cglobal` // value for this to happen. llvmf_orig->setAtomic(AtomicOrdering::Unordered); + auto nonnull = irbuilder.CreateICmpNE(llvmf_orig, initnul); + setName(emission_context, nonnull, "is_cached"); irbuilder.CreateCondBr( - irbuilder.CreateICmpNE(llvmf_orig, initnul), + nonnull, ccall_bb, dlsym_lookup); @@ -170,6 +173,7 @@ static Value *runtime_sym_lookup( llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlsym_func), { libname, nameval, libptrgv }); } + setName(emission_context, llvmf, f_name + StringRef(".found")); StoreInst *store = irbuilder.CreateAlignedStore(llvmf, llvmgv, Align(sizeof(void*))); store->setAtomic(AtomicOrdering::Release); irbuilder.CreateBr(ccall_bb); @@ -179,6 +183,7 @@ static Value *runtime_sym_lookup( PHINode *p = irbuilder.CreatePHI(T_pvoidfunc, 2); p->addIncoming(llvmf_orig, enter_bb); p->addIncoming(llvmf, llvmf->getParent()); + setName(emission_context, p, f_name); return irbuilder.CreateBitCast(p, funcptype); } @@ -320,6 +325,7 @@ static Value *emit_plt( } GlobalVariable *got = prepare_global_in(jl_Module, sharedgot); LoadInst *got_val = ctx.builder.CreateAlignedLoad(got->getValueType(), got, Align(sizeof(void*))); + setName(ctx.emission_context, got_val, f_name); // See comment in `runtime_sym_lookup` above. This in principle needs a // consume ordering too. This is even less likely to cause issues though // since the only thing we do to this loaded pointer is to call it @@ -442,16 +448,20 @@ static Value *llvm_type_rewrite( unsigned align = std::max(DL.getPrefTypeAlignment(target_type), DL.getPrefTypeAlignment(from_type)); if (DL.getTypeAllocSize(target_type) >= DL.getTypeAllocSize(from_type)) { to = emit_static_alloca(ctx, target_type); + setName(ctx.emission_context, to, "type_rewrite_buffer"); cast(to)->setAlignment(Align(align)); from = emit_bitcast(ctx, to, from_type->getPointerTo()); } else { from = emit_static_alloca(ctx, from_type); + setName(ctx.emission_context, from, "type_rewrite_buffer"); cast(from)->setAlignment(Align(align)); to = emit_bitcast(ctx, from, target_type->getPointerTo()); } ctx.builder.CreateAlignedStore(v, from, Align(align)); - return ctx.builder.CreateAlignedLoad(target_type, to, Align(align)); + auto pun = ctx.builder.CreateAlignedLoad(target_type, to, Align(align)); + setName(ctx.emission_context, pun, "type_rewrite"); + return pun; } // --- argument passing and scratch space utilities --- @@ -508,6 +518,7 @@ static void typeassert_input(jl_codectx_t &ctx, const jl_cgval_t &jvinfo, jl_val Value *istype = ctx.builder.CreateICmpNE( ctx.builder.CreateCall(prepare_call(jlisa_func), { vx, boxed(ctx, jlto_runtime) }), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); + setName(ctx.emission_context, istype, "istype"); BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass", ctx.f); ctx.builder.CreateCondBr(istype, passBB, failBB); @@ -545,6 +556,7 @@ static Value *julia_to_native( // pass the address of an alloca'd thing, not a box // since those are immutable. Value *slot = emit_static_alloca(ctx, to); + setName(ctx.emission_context, slot, "native_convert_buffer"); if (!jvinfo.ispointer()) { jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, jvinfo.tbaa); ai.decorateInst(ctx.builder.CreateStore(emit_unbox(ctx, to, jvinfo, jlto), slot)); @@ -1009,6 +1021,7 @@ static Value *box_ccall_result(jl_codectx_t &ctx, Value *result, Value *runtime_ unsigned nb = DL.getTypeStoreSize(result->getType()); MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; Value *strct = emit_allocobj(ctx, nb, runtime_dt); + setName(ctx.emission_context, strct, "ccall_result_box"); init_bits_value(ctx, strct, result, tbaa); return strct; } @@ -1253,6 +1266,7 @@ static bool verify_ref_type(jl_codectx_t &ctx, jl_value_t* ref, jl_unionall_t *u Value *notany = ctx.builder.CreateICmpNE( boxed(ctx, runtime_sp), track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_any_type))); + setName(ctx.emission_context, notany, "any_type.not"); error_unless(ctx, notany, make_errmsg(fname, n, rt_err_msg_notany)); always_error = false; } @@ -1587,7 +1601,9 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) Value *ptask_i16 = emit_bitcast(ctx, get_current_task(ctx), getInt16PtrTy(ctx.builder.getContext())); const int tid_offset = offsetof(jl_task_t, tid); Value *ptid = ctx.builder.CreateInBoundsGEP(getInt16Ty(ctx.builder.getContext()), ptask_i16, ConstantInt::get(ctx.types().T_size, tid_offset / sizeof(int16_t))); + setName(ctx.emission_context, ptid, "thread_id_ptr"); LoadInst *tid = ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), ptid, Align(sizeof(int16_t))); + setName(ctx.emission_context, tid, "thread_id"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); ai.decorateInst(tid); return mark_or_box_ccall_result(ctx, tid, retboxed, rt, unionall, static_rt); @@ -1601,15 +1617,19 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) Value *ptls_i32 = emit_bitcast(ctx, get_current_ptls(ctx), getInt32PtrTy(ctx.builder.getContext())); const int finh_offset = offsetof(jl_tls_states_t, finalizers_inhibited); Value *pfinh = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), ptls_i32, ConstantInt::get(ctx.types().T_size, finh_offset / 4)); + setName(ctx.emission_context, pfinh, "finalizers_inhibited_ptr"); LoadInst *finh = ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), pfinh, Align(sizeof(int32_t))); + setName(ctx.emission_context, finh, "finalizers_inhibited"); Value *newval; if (is_libjulia_func(jl_gc_disable_finalizers_internal)) { newval = ctx.builder.CreateAdd(finh, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1)); + setName(ctx.emission_context, newval, "finalizers_inhibited_inc"); } else { newval = ctx.builder.CreateSelect(ctx.builder.CreateICmpEQ(finh, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0), ctx.builder.CreateSub(finh, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1))); + setName(ctx.emission_context, newval, "finalizers_inhibited_dec"); } ctx.builder.CreateStore(newval, pfinh); return ghostValue(ctx, jl_nothing_type); @@ -1630,6 +1650,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) Value *ptls_pv = emit_bitcast(ctx, get_current_ptls(ctx), ctx.types().T_ppjlvalue); const int nt_offset = offsetof(jl_tls_states_t, next_task); Value *pnt = ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, ptls_pv, ConstantInt::get(ctx.types().T_size, nt_offset / sizeof(void*))); + setName(ctx.emission_context, pnt, "next_task_ptr"); ctx.builder.CreateStore(emit_pointer_from_objref(ctx, boxed(ctx, argv[0])), pnt); return ghostValue(ctx, jl_nothing_type); } @@ -1640,8 +1661,11 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) JL_GC_POP(); ctx.builder.CreateCall(prepare_call(gcroot_flush_func)); Value *pdefer_sig = emit_defer_signal(ctx); + setName(ctx.emission_context, pdefer_sig, "defer_signal_ptr"); Value *defer_sig = ctx.builder.CreateLoad(ctx.types().T_sigatomic, pdefer_sig); + setName(ctx.emission_context, defer_sig, "defer_signal"); defer_sig = ctx.builder.CreateAdd(defer_sig, ConstantInt::get(ctx.types().T_sigatomic, 1)); + setName(ctx.emission_context, defer_sig, "defer_signal_inc"); ctx.builder.CreateStore(defer_sig, pdefer_sig); emit_signal_fence(ctx); return ghostValue(ctx, jl_nothing_type); @@ -1653,7 +1677,9 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) JL_GC_POP(); ctx.builder.CreateCall(prepare_call(gcroot_flush_func)); Value *pdefer_sig = emit_defer_signal(ctx); + setName(ctx.emission_context, pdefer_sig, "defer_signal_ptr"); Value *defer_sig = ctx.builder.CreateLoad(ctx.types().T_sigatomic, pdefer_sig); + setName(ctx.emission_context, defer_sig, "defer_signal"); emit_signal_fence(ctx); error_unless(ctx, ctx.builder.CreateICmpNE(defer_sig, ConstantInt::get(ctx.types().T_sigatomic, 0)), @@ -1661,19 +1687,23 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) defer_sig = ctx.builder.CreateSub( defer_sig, ConstantInt::get(ctx.types().T_sigatomic, 1)); + setName(ctx.emission_context, defer_sig, "defer_signal_dec"); ctx.builder.CreateStore(defer_sig, pdefer_sig); BasicBlock *checkBB = BasicBlock::Create(ctx.builder.getContext(), "check", ctx.f); BasicBlock *contBB = BasicBlock::Create(ctx.builder.getContext(), "cont"); + auto not_deferred = ctx.builder.CreateICmpEQ(defer_sig, ConstantInt::get(ctx.types().T_sigatomic, 0)); + setName(ctx.emission_context, not_deferred, "deferred.not"); ctx.builder.CreateCondBr( - ctx.builder.CreateICmpEQ(defer_sig, ConstantInt::get(ctx.types().T_sigatomic, 0)), + not_deferred, checkBB, contBB); ctx.builder.SetInsertPoint(checkBB); - ctx.builder.CreateLoad( + auto signal_page_load = ctx.builder.CreateLoad( ctx.types().T_size, ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_size, get_current_signal_page_from_ptls(ctx.builder, ctx.types().T_size, get_current_ptls(ctx), ctx.tbaa().tbaa_const), -1), true); + setName(ctx.emission_context, signal_page_load, "signal_page_load"); ctx.builder.CreateBr(contBB); ctx.f->getBasicBlockList().push_back(contBB); ctx.builder.SetInsertPoint(contBB); @@ -1690,7 +1720,9 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } else { auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_size->getPointerTo()); + setName(ctx.emission_context, ptr, "svec_len_ptr"); len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, ptr, ctx.types().alignof_ptr); + setName(ctx.emission_context, len, "svec_len"); // Only mark with TBAA if we are sure about the type. // This could otherwise be in a dead branch if (svecv.typ == (jl_value_t*)jl_simplevector_type) { @@ -1713,11 +1745,15 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) const jl_cgval_t &idxv = argv[1]; Value *idx = emit_unbox(ctx, ctx.types().T_size, idxv, (jl_value_t*)jl_long_type); idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, idx, "svec_idx"); auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_pprjlvalue); + setName(ctx.emission_context, ptr, "svec_data_ptr"); Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, decay_derived(ctx, ptr), idx); + setName(ctx.emission_context, slot_addr, "svec_slot_addr"); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, Align(sizeof(void*))); + setName(ctx.emission_context, load, "svec_slot"); load->setAtomic(AtomicOrdering::Unordered); // Only mark with TBAA if we are sure about the type. // This could otherwise be in a dead branch @@ -1754,9 +1790,12 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if (stride != 1) idx = ctx.builder.CreateMul(idx, ConstantInt::get(ctx.types().T_size, stride)); idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, ((jl_datatype_t*)ety)->layout->first_ptr)); + setName(ctx.emission_context, idx, "array_idx"); } Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, arrayptr, idx); + setName(ctx.emission_context, slot_addr, "array_slot_addr"); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, Align(sizeof(void*))); + setName(ctx.emission_context, load, "array_slot"); load->setAtomic(AtomicOrdering::Unordered); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_ptrarraybuf); ai.decorateInst(load); @@ -1776,6 +1815,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) // a null pointer. auto strp = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, obj, 1); strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); + setName(ctx.emission_context, strp, "string_ptr"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } @@ -1790,6 +1830,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) auto strp = ctx.builder.CreateConstInBoundsGEP1_32( ctx.types().T_prjlvalue, obj, (sizeof(jl_sym_t) + sizeof(void*) - 1) / sizeof(void*)); strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); + setName(ctx.emission_context, strp, "symbol_name"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } @@ -1860,7 +1901,9 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) const int hash_offset = offsetof(jl_sym_t, hash); Value *ph1 = emit_bitcast(ctx, decay_derived(ctx, boxed(ctx, val)), ctx.types().T_size->getPointerTo()); Value *ph2 = ctx.builder.CreateInBoundsGEP(ctx.types().T_size, ph1, ConstantInt::get(ctx.types().T_size, hash_offset / ctx.types().sizeof_ptr)); + setName(ctx.emission_context, ph2, "object_id_ptr"); LoadInst *hashval = ctx.builder.CreateAlignedLoad(ctx.types().T_size, ph2, ctx.types().alignof_ptr); + setName(ctx.emission_context, hashval, "object_id"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); ai.decorateInst(hashval); return mark_or_box_ccall_result(ctx, hashval, retboxed, rt, unionall, static_rt); @@ -1879,6 +1922,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) T_pint8_derived) }; Value *ret = ctx.builder.CreateCall(prepare_call(jl_object_id__func), makeArrayRef(args)); + setName(ctx.emission_context, ret, "object_id"); JL_GC_POP(); return mark_or_box_ccall_result(ctx, ret, retboxed, rt, unionall, static_rt); } @@ -1967,6 +2011,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( assert(!retboxed && jl_is_datatype(rt) && "sret return type invalid"); if (jl_is_pointerfree(rt)) { result = emit_static_alloca(ctx, lrt); + setName(ctx.emission_context, result, "ccall_sret"); sretty = lrt; argvals[0] = ctx.builder.CreateBitCast(result, fargt_sig.at(0)); } @@ -1975,6 +2020,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( // and has incorrect write barriers. // instead this code path should behave like `unsafe_load` result = emit_allocobj(ctx, (jl_datatype_t*)rt); + setName(ctx.emission_context, result, "ccall_sret_box"); sretty = ctx.types().T_jlvalue; sretboxed = true; gc_uses.push_back(result); @@ -2123,6 +2169,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( return mark_julia_slot(result, rt, NULL, ctx.tbaa().tbaa_stack); ++SRetCCalls; result = ctx.builder.CreateLoad(sretty, result); + setName(ctx.emission_context, result, "returned"); } } else { @@ -2137,6 +2184,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( assert(jl_is_datatype(rt)); if (static_rt) { Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt); + setName(ctx.emission_context, strct, "ccall_ret_box"); MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; int boxalign = julia_alignment(rt); // copy the data from the return value to the new struct @@ -2147,6 +2195,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( // ARM and AArch64 can use a LLVM type larger than the julia type. // When this happens, cast through memory. auto slot = emit_static_alloca(ctx, resultTy); + setName(ctx.emission_context, slot, "type_pun_slot"); slot->setAlignment(Align(boxalign)); ctx.builder.CreateAlignedStore(result, slot, Align(boxalign)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index b400e510e0cde..8442ba99bb411 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -119,10 +119,12 @@ static Value *stringConstPtr( GlobalVariable *gv = get_pointer_to_constant(emission_context, Data, "_j_str", *M); Value *zero = ConstantInt::get(Type::getInt32Ty(irbuilder.getContext()), 0); Value *Args[] = { zero, zero }; - return irbuilder.CreateInBoundsGEP(gv->getValueType(), + auto gep = irbuilder.CreateInBoundsGEP(gv->getValueType(), // Addrspacecast in case globals are in non-0 AS irbuilder.CreateAddrSpaceCast(gv, gv->getValueType()->getPointerTo(0)), Args); + setName(emission_context, gep, "string_const_ptr"); + return gep; } @@ -515,9 +517,11 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p) return literal_static_pointer_val(p, ctx.types().T_pjlvalue); Value *pgv = literal_pointer_val_slot(ctx, p); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(maybe_mark_load_dereferenceable( + auto load = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, jl_typeof(p))); + setName(ctx.emission_context, load, pgv->getName()); + return load; } // Returns ctx.types().T_pjlvalue @@ -532,9 +536,11 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_binding_t *p) jl_globalref_t *gr = p->globalref; Value *pgv = gr ? julia_pgv(ctx, "jl_bnd#", gr->name, gr->mod, p) : julia_pgv(ctx, "jl_bnd#", p); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(maybe_mark_load_dereferenceable( + auto load = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, sizeof(jl_binding_t), alignof(jl_binding_t))); + setName(ctx.emission_context, load, pgv->getName()); + return load; } // bitcast a value, but preserve its address space when dealing with pointer types @@ -573,7 +579,9 @@ static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) jl_globalref_t *gr = b->globalref; Value *pgv = gr ? julia_pgv(ctx, "*", gr->name, gr->mod, b) : julia_pgv(ctx, "*jl_bnd#", b); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); + auto load = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); + setName(ctx.emission_context, load, pgv->getName()); + return load; } else { return literal_static_pointer_val(b, ctx.types().T_pjlvalue); @@ -976,6 +984,10 @@ static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const src = emit_bitcast(ctx, src, dstty); } if (directel) { + if (isa(src) && !src->hasName()) + setName(ctx.emission_context, src, "memcpy_refined_src"); + if (isa(dst) && !dst->hasName()) + setName(ctx.emission_context, dst, "memcpy_refined_dst"); auto val = src_ai.decorateInst(ctx.builder.CreateAlignedLoad(directel, src, Align(align), is_volatile)); dst_ai.decorateInst(ctx.builder.CreateAlignedStore(val, dst, Align(align), is_volatile)); ++SkippedMemcpys; @@ -1035,6 +1047,7 @@ static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, Value *idx, MDN ctx.types().T_prjlvalue, emit_bitcast(ctx, maybe_decay_tracked(ctx, v), ctx.types().T_pprjlvalue), idx); + setName(ctx.emission_context, vptr, "arraysize_ptr"); LoadInst *load = ctx.builder.CreateLoad(type, emit_bitcast(ctx, vptr, PointerType::get(type, 0))); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.decorateInst(load); @@ -1045,7 +1058,9 @@ static Value *emit_tagfrom(jl_codectx_t &ctx, jl_datatype_t *dt) { if (dt->smalltag) return ConstantInt::get(ctx.types().T_size, dt->smalltag << 4); - return ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, (jl_value_t*)dt), ctx.types().T_size); + auto tag = ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, (jl_value_t*)dt), ctx.types().T_size); + setName(ctx.emission_context, tag, jl_symbol_name(dt->name->name)); + return tag; } // Returns justtag ? ctx.types.T_size : ctx.types().T_prjlvalue @@ -1110,6 +1125,7 @@ static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull else ptr = ConstantExpr::getAddrSpaceCast(literal_static_pointer_val((jl_value_t*)jt, ctx.types().T_pjlvalue), expr_type); datatype_or_p = ctx.builder.CreateSelect(cmp, ptr, datatype_or_p); + setName(ctx.emission_context, datatype_or_p, "typetag_ptr"); }, p.typ, counter); @@ -1117,6 +1133,7 @@ static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); if (ctx.emission_context.imaging) { Value *datatype = ai.decorateInst(ctx.builder.CreateAlignedLoad(expr_type, datatype_or_p, Align(sizeof(void*)))); + setName(ctx.emission_context, datatype, "typetag"); return justtag ? datatype : track_pjlvalue(ctx, datatype); } return datatype_or_p; @@ -1124,6 +1141,7 @@ static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull Value *res; if (!allunboxed) { Value *isnull = ctx.builder.CreateIsNull(datatype_or_p); + setName(ctx.emission_context, isnull, "typetag_isnull"); BasicBlock *boxBB = BasicBlock::Create(ctx.builder.getContext(), "boxed", ctx.f); BasicBlock *unboxBB = BasicBlock::Create(ctx.builder.getContext(), "unboxed", ctx.f); BasicBlock *mergeBB = BasicBlock::Create(ctx.builder.getContext(), "merge", ctx.f); @@ -1141,6 +1159,7 @@ static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull phi->addIncoming(boxTy, boxBB); phi->addIncoming(unboxTy, unboxBB); res = phi; + setName(ctx.emission_context, res, "typetag"); } else { res = emit_unboxty(); @@ -1155,15 +1174,19 @@ static Value *emit_datatype_types(jl_codectx_t &ctx, Value *dt) Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), ctx.types().T_ppjlvalue); Value *Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_t, types) / sizeof(void*)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(ctx.builder.CreateAlignedLoad( + auto types = ai.decorateInst(ctx.builder.CreateAlignedLoad( ctx.types().T_pjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, Ptr, Idx), Align(sizeof(void*)))); + setName(ctx.emission_context, types, "datatype_types"); + return types; } static Value *emit_datatype_nfields(jl_codectx_t &ctx, Value *dt) { Value *type_svec = emit_bitcast(ctx, emit_datatype_types(ctx, dt), ctx.types().T_size->getPointerTo()); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_size, type_svec, Align(sizeof(void*)))); + auto nfields = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_size, type_svec, Align(sizeof(void*)))); + setName(ctx.emission_context, nfields, "datatype_nfields"); + return nfields; } static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) @@ -1175,7 +1198,9 @@ static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) Ptr = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32PtrTy(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t*)))); Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_layout_t, size) / sizeof(int32_t)); Ptr = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), Ptr, Idx); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); + auto Size = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); + setName(ctx.emission_context, Size, "datatype_size"); + return Size; } /* this is valid code, it's simply unused @@ -1250,6 +1275,7 @@ static Value *emit_datatype_isprimitivetype(jl_codectx_t &ctx, Value *typ) isprimitive = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isprimitive, Align(1))); isprimitive = ctx.builder.CreateLShr(isprimitive, 7); isprimitive = ctx.builder.CreateTrunc(isprimitive, getInt1Ty(ctx.builder.getContext())); + setName(ctx.emission_context, isprimitive, "datatype_isprimitive"); return isprimitive; } @@ -1261,7 +1287,9 @@ static Value *emit_datatype_name(jl_codectx_t &ctx, Value *dt) emit_bitcast(ctx, maybe_decay_tracked(ctx, dt), ctx.types().T_ppjlvalue), ConstantInt::get(ctx.types().T_size, n)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, vptr, Align(sizeof(void*)))); + auto name = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, vptr, Align(sizeof(void*)))); + setName(ctx.emission_context, name, "datatype_name"); + return name; } // --- generating various error checks --- @@ -1370,6 +1398,7 @@ static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Value *defval, PHINode *phi = ctx.builder.CreatePHI(defval->getType(), 2); phi->addIncoming(defval, currBB); phi->addIncoming(res, passBB); + setName(ctx.emission_context, phi, "guard_res"); return phi; } @@ -1414,7 +1443,7 @@ static Value *emit_typeof(jl_codectx_t &ctx, Value *v, bool maybenull, bool just assert(v != NULL && !isa(v) && "expected a conditionally boxed value"); Value *nonnull = maybenull ? null_pointer_cmp(ctx, v) : ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); Function *typeof = prepare_call(jl_typeof_func); - return emit_guarded_test(ctx, nonnull, Constant::getNullValue(justtag ? ctx.types().T_size : typeof->getReturnType()), [&] { + auto val = emit_guarded_test(ctx, nonnull, Constant::getNullValue(justtag ? ctx.types().T_size : typeof->getReturnType()), [&] { // e.g. emit_typeof(ctx, v) Value *typetag = ctx.builder.CreateCall(typeof, {v}); if (notag) @@ -1435,6 +1464,8 @@ static Value *emit_typeof(jl_codectx_t &ctx, Value *v, bool maybenull, bool just return ai.decorateInst(small); }); }); + setName(ctx.emission_context, val, "typeof"); + return val; } static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &v, bool is_promotable=false); @@ -1493,11 +1524,14 @@ static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_data if (tindex > 0) { // optimize more when we know that this is a split union-type where tindex = 0 is invalid Value *xtindex = ctx.builder.CreateAnd(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); - return ctx.builder.CreateICmpEQ(xtindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex)); + auto isa = ctx.builder.CreateICmpEQ(xtindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex)); + setName(ctx.emission_context, isa, "exactly_isa"); + return isa; } else if (arg.Vboxed) { // test for (arg.TIndex == 0x80 && typeof(arg.V) == type) Value *isboxed = ctx.builder.CreateICmpEQ(arg.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); + setName(ctx.emission_context, isboxed, "isboxed"); BasicBlock *currBB = ctx.builder.GetInsertBlock(); BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "isa", ctx.f); BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_isa", ctx.f); @@ -1510,13 +1544,16 @@ static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_data PHINode *istype = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); istype->addIncoming(ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), currBB); istype->addIncoming(istype_boxed, isaBB); + setName(ctx.emission_context, istype, "exactly_isa"); return istype; } else { // handle the case where we know that `arg` is unboxed (but of unknown type), but that concrete type `type` cannot be unboxed return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); } } - return ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt)); + auto isa = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt)); + setName(ctx.emission_context, isa, "exactly_isa"); + return isa; } static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, @@ -1586,6 +1623,7 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, ctx.builder.CreateOr( ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_unionall_type)), ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_typeofbottom_type)))); + setName(ctx.emission_context, val, "is_kind"); return std::make_pair(val, false); } // intersection with Type needs to be handled specially @@ -1686,6 +1724,7 @@ static Value *emit_isconcrete(jl_codectx_t &ctx, Value *typ) isconcrete = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isconcrete, Align(1))); isconcrete = ctx.builder.CreateLShr(isconcrete, 1); isconcrete = ctx.builder.CreateTrunc(isconcrete, getInt1Ty(ctx.builder.getContext())); + setName(ctx.emission_context, isconcrete, "isconcrete"); return isconcrete; } @@ -1719,6 +1758,7 @@ static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_v if (bounds_check_enabled(ctx, boundscheck)) { ++EmittedBoundschecks; Value *ok = ctx.builder.CreateICmpULT(im1, len); + setName(ctx.emission_context, ok, "boundscheck"); BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); ctx.builder.CreateCondBr(ok, passBB, failBB); @@ -1737,6 +1777,7 @@ static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_v else if (!ainfo.ispointer()) { // CreateAlloca is OK here since we are on an error branch Value *tempSpace = ctx.builder.CreateAlloca(a->getType()); + setName(ctx.emission_context, tempSpace, "errorbox"); ctx.builder.CreateStore(a, tempSpace); a = tempSpace; } @@ -1828,12 +1869,15 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j // note that nb == jl_Module->getDataLayout().getTypeAllocSize(elty) or getTypeStoreSize, depending on whether it is a struct or primitive type AllocaInst *intcast = NULL; if (Order == AtomicOrdering::NotAtomic) { - if (!isboxed && !aliasscope && elty->isAggregateType() && !CountTrackedPointers(elty).count) + if (!isboxed && !aliasscope && elty->isAggregateType() && !CountTrackedPointers(elty).count) { intcast = emit_static_alloca(ctx, elty); + setName(ctx.emission_context, intcast, "aggregate_load_box"); + } } else { if (!isboxed && !elty->isIntOrPtrTy()) { intcast = emit_static_alloca(ctx, elty); + setName(ctx.emission_context, intcast, "atomic_load_box"); elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb); } } @@ -1949,8 +1993,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, unsigned nb = isboxed ? sizeof(void*) : jl_datatype_size(jltype); AllocaInst *intcast = nullptr; if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy()) { - if (!issetfield) + if (!issetfield) { intcast = emit_static_alloca(ctx, elty); + setName(ctx.emission_context, intcast, "atomic_store_box"); + } elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb); } Type *realelty = elty; @@ -1988,6 +2034,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, if (issetfield || (Order == AtomicOrdering::NotAtomic && isswapfield)) { if (isswapfield) { auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); + setName(ctx.emission_context, load, "swapfield_load"); if (isboxed) load->setOrdering(AtomicOrdering::Unordered); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -2011,6 +2058,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else if (isswapfield && isStrongerThanMonotonic(Order)) { assert(Order != AtomicOrdering::NotAtomic && r); auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Align(alignment), Order); + setName(ctx.emission_context, store, "swapfield_atomicrmw"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); ai.decorateInst(store); @@ -2035,6 +2083,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, ctx.builder.CreateCondBr(SameType, BB, SkipBB); ctx.builder.SetInsertPoint(SkipBB); LoadInst *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); + setName(ctx.emission_context, load, "atomic_replacefield_initial"); load->setOrdering(FailOrder == AtomicOrdering::NotAtomic && isboxed ? AtomicOrdering::Monotonic : FailOrder); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); @@ -2065,6 +2114,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, else { // swap or modify LoadInst *Current = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); Current->setOrdering(Order == AtomicOrdering::NotAtomic && !isboxed ? Order : AtomicOrdering::Monotonic); + setName(ctx.emission_context, Current, "atomic_initial"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); Compare = ai.decorateInst(Current); @@ -2267,8 +2317,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, // Returns ctx.types().T_pjlvalue static Value *julia_bool(jl_codectx_t &ctx, Value *cond) { - return ctx.builder.CreateSelect(cond, literal_pointer_val(ctx, jl_true), + auto boolean = ctx.builder.CreateSelect(cond, literal_pointer_val(ctx, jl_true), literal_pointer_val(ctx, jl_false)); + setName(ctx.emission_context, boolean, "bool"); + return boolean; } // --- accessing the representations of built-in data types --- @@ -2354,6 +2406,7 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, ctx.builder.CreateExtractValue(strct.V, makeArrayRef(i)), fld); } + setName(ctx.emission_context, fld, "getfield"); jl_value_t *jft = issame ? jl_svecref(types, 0) : (jl_value_t*)jl_any_type; if (isboxed && maybe_null) null_pointer_check(ctx, fld); @@ -2387,7 +2440,9 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, ctx.types().T_prjlvalue, emit_bitcast(ctx, data_pointer(ctx, strct), ctx.types().T_pprjlvalue), idx0()); + setName(ctx.emission_context, fldptr, "getfield_ptr"); LoadInst *fld = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fldptr, Align(sizeof(void*))); + setName(ctx.emission_context, fld, "getfield"); fld->setOrdering(AtomicOrdering::Unordered); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, strct.tbaa); ai.decorateInst(fld); @@ -2439,6 +2494,7 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, // move value to an immutable stack slot (excluding tindex) Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (fsz + al - 1) / al); AllocaInst *lv = emit_static_alloca(ctx, AT); + setName(ctx.emission_context, lv, "immutable_union"); if (al > 1) lv->setAlignment(Align(al)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -2509,7 +2565,9 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st addr = ctx.builder.CreateConstInBoundsGEP2_32(lt, staddr, 0, idx); } if (jl_field_isptr(jt, idx)) { + setName(ctx.emission_context, addr, "getfield_addr"); LoadInst *Load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, maybe_bitcast(ctx, addr, ctx.types().T_pprjlvalue), Align(sizeof(void*))); + setName(ctx.emission_context, Load, "getfield"); Load->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); maybe_mark_load_dereferenceable(Load, maybe_null, jl_field_type(jt, idx)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -2572,6 +2630,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st IntegerType *ET = cast(T->getStructElementType(st_idx)); unsigned align = (ET->getBitWidth() + 7) / 8; lv = emit_static_alloca(ctx, ET); + setName(ctx.emission_context, lv, "union_split"); lv->setOperand(0, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), (fsz + align - 1) / align)); // emit all of the align-sized words unsigned i = 0; @@ -2715,6 +2774,7 @@ static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value * t, ctx.builder.CreateAdd(dim, ConstantInt::get(dim->getType(), o)), tbaa, ctx.types().T_size); + setName(ctx.emission_context, load, "arraysize"); MDBuilder MDB(ctx.builder.getContext()); auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, arraytype_maxsize(tinfo.typ))); load->setMetadata(LLVMContext::MD_range, rng); @@ -2750,7 +2810,9 @@ static Value *emit_arraylen_prim(jl_codectx_t &ctx, const jl_cgval_t &tinfo) Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), 1); //index (not offset) of length field in ctx.types().T_pjlarray + setName(ctx.emission_context, addr, "arraylen_ptr"); LoadInst *len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, addr, ctx.types().alignof_ptr); + setName(ctx.emission_context, len, "arraylen"); len->setOrdering(AtomicOrdering::NotAtomic); MDBuilder MDB(ctx.builder.getContext()); auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, arraytype_maxsize(tinfo.typ))); @@ -2769,6 +2831,7 @@ static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, ++EmittedArrayptr; Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, emit_bitcast(ctx, t, ctx.types().T_pjlarray), 0); + setName(ctx.emission_context, addr, "arrayptr_ptr"); // Normally allocated array of 0 dimension always have a inline pointer. // However, we can't rely on that here since arrays can also be constructed from C pointers. PointerType *PT = cast(addr->getType()); @@ -2787,6 +2850,7 @@ static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, } LoadInst *LI = ctx.builder.CreateAlignedLoad(LoadT, addr, Align(sizeof(char *))); + setName(ctx.emission_context, LI, "arrayptr"); LI->setOrdering(AtomicOrdering::NotAtomic); LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(ctx.builder.getContext(), None)); jl_aliasinfo_t aliasinfo = jl_aliasinfo_t::fromTBAA(ctx, arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr); @@ -2827,8 +2891,11 @@ static Value *emit_arrayflags(jl_codectx_t &ctx, const jl_cgval_t &tinfo) ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), arrayflag_field); + setName(ctx.emission_context, addr, "arrayflags_ptr"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayflags); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + auto flags = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + setName(ctx.emission_context, flags, "arrayflags"); + return flags; } static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) @@ -2838,6 +2905,7 @@ static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) cast(flags)->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(ctx.builder.getContext(), None)); flags = ctx.builder.CreateLShr(flags, 2); flags = ctx.builder.CreateAnd(flags, 0x1FF); // (1<<9) - 1 + setName(ctx.emission_context, flags, "arrayndims"); return flags; } @@ -2849,8 +2917,11 @@ static Value *emit_arrayelsize(jl_codectx_t &ctx, const jl_cgval_t &tinfo) Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), elsize_field); + setName(ctx.emission_context, addr, "arrayelsize_ptr"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + auto elsize = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + setName(ctx.emission_context, elsize, "arrayelsize"); + return elsize; } static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int nd) @@ -2865,8 +2936,11 @@ static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int n ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), offset_field); + setName(ctx.emission_context, addr, "arrayoffset_ptr"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayoffset); - return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); + auto offset = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); + setName(ctx.emission_context, offset, "arrayoffset"); + return offset; } // Returns the size of the array represented by `tinfo` for the given dimension `dim` if @@ -2909,12 +2983,15 @@ static Value *emit_array_nd_index( if (bc) { BasicBlock *okBB = BasicBlock::Create(ctx.builder.getContext(), "ib"); // if !(i < d) goto error - ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(ii, d), okBB, failBB); + auto bc = ctx.builder.CreateICmpULT(ii, d); + setName(ctx.emission_context, bc, "inbounds"); + ctx.builder.CreateCondBr(bc, okBB, failBB); ctx.f->getBasicBlockList().push_back(okBB); ctx.builder.SetInsertPoint(okBB); } #endif stride = ctx.builder.CreateMul(stride, d); + setName(ctx.emission_context, stride, "stride"); } } #if CHECK_BOUNDS==1 @@ -2924,20 +3001,26 @@ static Value *emit_array_nd_index( if (nidxs == 1) { // Linear indexing: Check against the entire linear span of the array Value *alen = emit_arraylen(ctx, ainfo); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(i, alen), endBB, failBB); + auto bc = ctx.builder.CreateICmpULT(i, alen); + setName(ctx.emission_context, bc, "inbounds"); + ctx.builder.CreateCondBr(bc, endBB, failBB); } else if (nidxs >= (size_t)nd){ // No dimensions were omitted; just check the last remaining index assert(nd >= 0); Value *last_index = ii; Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(last_index, last_dimension), endBB, failBB); + auto bc = ctx.builder.CreateICmpULT(last_index, last_dimension); + setName(ctx.emission_context, bc, "inbounds"); + ctx.builder.CreateCondBr(bc, endBB, failBB); } else { // There were fewer indices than dimensions; check the last remaining index BasicBlock *checktrailingdimsBB = BasicBlock::Create(ctx.builder.getContext(), "dimsib"); assert(nd >= 0); Value *last_index = ii; Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(last_index, last_dimension), checktrailingdimsBB, failBB); + auto bc = ctx.builder.CreateICmpULT(last_index, last_dimension); + setName(ctx.emission_context, bc, "inbounds"); + ctx.builder.CreateCondBr(bc, checktrailingdimsBB, failBB); ctx.f->getBasicBlockList().push_back(checktrailingdimsBB); ctx.builder.SetInsertPoint(checktrailingdimsBB); // And then also make sure that all dimensions that weren't explicitly @@ -2945,18 +3028,23 @@ static Value *emit_array_nd_index( for (size_t k = nidxs+1; k < (size_t)nd; k++) { BasicBlock *dimsokBB = BasicBlock::Create(ctx.builder.getContext(), "dimsok"); Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, k, nd); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)), dimsokBB, failBB); + auto bc = ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, bc, "inbounds"); + ctx.builder.CreateCondBr(bc, dimsokBB, failBB); ctx.f->getBasicBlockList().push_back(dimsokBB); ctx.builder.SetInsertPoint(dimsokBB); } Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nd, nd); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)), endBB, failBB); + auto bc2 = ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, bc2, "inbounds"); + ctx.builder.CreateCondBr(bc2, endBB, failBB); } ctx.f->getBasicBlockList().push_back(failBB); ctx.builder.SetInsertPoint(failBB); // CreateAlloca is OK here since we are on an error branch Value *tmp = ctx.builder.CreateAlloca(ctx.types().T_size, ConstantInt::get(ctx.types().T_size, nidxs)); + setName(ctx.emission_context, tmp, "errorbox"); for (size_t k = 0; k < nidxs; k++) { ctx.builder.CreateAlignedStore(idxs[k], ctx.builder.CreateInBoundsGEP(ctx.types().T_size, tmp, ConstantInt::get(ctx.types().T_size, k)), ctx.types().alignof_ptr); } @@ -3174,6 +3262,7 @@ static Value *compute_box_tindex(jl_codectx_t &ctx, Value *datatype_tag, jl_valu }, ut, counter); + setName(ctx.emission_context, tindex, "tindex"); return tindex; } @@ -3224,6 +3313,7 @@ static AllocaInst *try_emit_union_alloca(jl_codectx_t &ctx, jl_uniontype_t *ut, // try to pick an Integer type size such that SROA will emit reasonable code Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * min_align), (nbytes + min_align - 1) / min_align); AllocaInst *lv = emit_static_alloca(ctx, AT); + setName(ctx.emission_context, lv, "unionalloca"); if (align > 1) lv->setAlignment(Align(align)); return lv; @@ -3280,6 +3370,7 @@ static Value *box_union(jl_codectx_t &ctx, const jl_cgval_t &vinfo, const SmallB box = _boxed_special(ctx, vinfo_r, t); if (!box) { box = emit_allocobj(ctx, jt); + setName(ctx.emission_context, box, "unionbox"); init_bits_cgval(ctx, box, vinfo_r, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } } @@ -3408,6 +3499,7 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab box = emit_allocobj(ctx, (jl_datatype_t*)jt); Value *decayed = decay_derived(ctx, box); AllocaInst *originalAlloca = cast(vinfo.V); + box->takeName(originalAlloca); decayed = maybe_bitcast(ctx, decayed, PointerType::getWithSamePointeeType(originalAlloca->getType(), AddressSpace::Derived)); // Warning: Very illegal IR here temporarily originalAlloca->mutateType(decayed->getType()); @@ -3418,6 +3510,7 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab ctx.builder.restoreIP(IP); } else { box = emit_allocobj(ctx, (jl_datatype_t*)jt); + setName(ctx.emission_context, box, "box"); init_bits_cgval(ctx, box, vinfo, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } } @@ -3528,6 +3621,7 @@ static void emit_cpointercheck(jl_codectx_t &ctx, const jl_cgval_t &x, const std Value *istype = ctx.builder.CreateICmpEQ(emit_datatype_name(ctx, t), literal_pointer_val(ctx, (jl_value_t*)jl_pointer_typename)); + setName(ctx.emission_context, istype, "istype"); BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); ctx.builder.CreateCondBr(istype, passBB, failBB); @@ -3769,6 +3863,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg } else { strct = emit_static_alloca(ctx, lt); + setName(ctx.emission_context, strct, "newstruct"); if (tracked.count) undef_derived_strct(ctx, strct, sty, ctx.tbaa().tbaa_stack); } @@ -3847,6 +3942,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg Type *ET = IntegerType::get(ctx.builder.getContext(), 8 * al); assert(lt->getStructElementType(llvm_idx) == ET); AllocaInst *lv = emit_static_alloca(ctx, ET); + setName(ctx.emission_context, lv, "unioninit"); lv->setOperand(0, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), (fsz + al - 1) / al)); emit_unionmove(ctx, lv, ctx.tbaa().tbaa_stack, fval_info, nullptr); // emit all of the align-sized words @@ -3937,6 +4033,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg } } Value *strct = emit_allocobj(ctx, sty); + setName(ctx.emission_context, strct, "newstruct"); jl_cgval_t strctinfo = mark_julia_type(ctx, strct, true, ty); strct = decay_derived(ctx, strct); undef_derived_strct(ctx, strct, sty, strctinfo.tbaa); diff --git a/src/codegen.cpp b/src/codegen.cpp index 37281ed3038ec..137d3d78814af 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -167,6 +167,13 @@ typedef Instruction TerminatorInst; #undef DEBUG_TYPE //LLVM occasionally likes to set DEBUG_TYPE in a header... #define DEBUG_TYPE "julia_irgen_codegen" +void setName(jl_codegen_params_t ¶ms, Value *V, const Twine &Name) +{ + if (params.debug_level) { + V->setName(Name); + } +} + STATISTIC(EmittedAllocas, "Number of allocas emitted"); STATISTIC(EmittedIntToPtrs, "Number of inttoptrs emitted"); STATISTIC(ModulesCreated, "Number of LLVM Modules created"); @@ -1604,7 +1611,6 @@ class jl_codectx_t { Value *pgcstack = NULL; Instruction *topalloca = NULL; - bool debug_enabled = false; bool use_cache = false; bool external_linkage = false; const jl_cgparams_t *params = NULL; @@ -2024,6 +2030,7 @@ static void alloc_def_flag(jl_codectx_t &ctx, jl_varinfo_t& vi) assert((!vi.boxroot || vi.pTIndex) && "undef check is null pointer for boxed things"); if (vi.usedUndef) { vi.defFlag = emit_static_alloca(ctx, getInt1Ty(ctx.builder.getContext())); + setName(ctx.emission_context, vi.defFlag, "isdefined"); store_def_flag(ctx, vi, false); } } @@ -2122,6 +2129,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & v.typ, counter); } + setName(ctx.emission_context, new_tindex, "tindex"); // some of the values are still unboxed if (!isa(new_tindex)) { @@ -2136,6 +2144,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & wasboxed = ctx.builder.CreateAnd(v.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); new_tindex = ctx.builder.CreateOr(wasboxed, new_tindex); wasboxed = ctx.builder.CreateICmpNE(wasboxed, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); + setName(ctx.emission_context, wasboxed, "wasboxed"); BasicBlock *currBB = ctx.builder.GetInsertBlock(); @@ -2172,6 +2181,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & }, typ, counter); + setName(ctx.emission_context, union_box_tindex, "union_box_tindex"); if (union_box_dt) { BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_union_isa", ctx.f); ctx.builder.CreateBr(postBB); @@ -2183,6 +2193,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & tindex_phi->addIncoming(new_tindex, currBB); tindex_phi->addIncoming(union_box_tindex, post_union_isaBB); new_tindex = tindex_phi; + setName(ctx.emission_context, new_tindex, "tindex"); } } if (!skip_box.all()) { @@ -2341,6 +2352,21 @@ std::unique_ptr jl_create_llvm_module(StringRef name, LLVMContext &conte return m; } +static void jl_name_jlfunc_args(jl_codegen_params_t ¶ms, Function *F) { + assert(F->arg_size() == 3); + setName(params, F->getArg(0), "function"); + setName(params, F->getArg(1), "args"); + setName(params, F->getArg(2), "nargs"); +} + +static void jl_name_jlfuncparams_args(jl_codegen_params_t ¶ms, Function *F) { + assert(F->arg_size() == 4); + setName(params, F->getArg(0), "function"); + setName(params, F->getArg(1), "args"); + setName(params, F->getArg(2), "nargs"); + setName(params, F->getArg(3), "sparams"); +} + static void jl_init_function(Function *F, const Triple &TT) { // set any attributes that *must* be set on all functions @@ -2822,6 +2848,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * if (bnd->constp) return mark_julia_const(ctx, v); LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); + setName(ctx.emission_context, v, jl_symbol_name(name)); v->setOrdering(order); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); ai.decorateInst(v); @@ -2896,6 +2923,7 @@ static Value *emit_bitsunion_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, Value *tindex2 = arg2.TIndex; tindex2 = ctx.builder.CreateAnd(tindex2, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); Value *typeeq = ctx.builder.CreateICmpEQ(tindex, tindex2); + setName(ctx.emission_context, typeeq, "typematch"); tindex = ctx.builder.CreateSelect(typeeq, tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x00)); BasicBlock *defaultBB = BasicBlock::Create(ctx.builder.getContext(), "unionbits_is_boxed", ctx.f); SwitchInst *switchInst = ctx.builder.CreateSwitch(tindex, defaultBB); @@ -2927,6 +2955,7 @@ static Value *emit_bitsunion_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, ctx.builder.CreateCall(trap_func); ctx.builder.CreateUnreachable(); ctx.builder.SetInsertPoint(postBB); + setName(ctx.emission_context, phi, "unionbits_is"); return phi; } @@ -3410,14 +3439,16 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else { Value *idx_dyn = emit_unbox(ctx, ctx.types().T_size, idx, (jl_value_t*)jl_long_type); - error_unless(ctx, ctx.builder.CreateICmpSGT(idx_dyn, Constant::getNullValue(ctx.types().T_size)), - "arraysize: dimension out of range"); + auto positive = ctx.builder.CreateICmpSGT(idx_dyn, Constant::getNullValue(ctx.types().T_size)); + setName(ctx.emission_context, positive, "ispositive"); + error_unless(ctx, positive, "arraysize: dimension out of range"); BasicBlock *outBB = BasicBlock::Create(ctx.builder.getContext(), "outofrange", ctx.f); BasicBlock *inBB = BasicBlock::Create(ctx.builder.getContext(), "inrange"); BasicBlock *ansBB = BasicBlock::Create(ctx.builder.getContext(), "arraysize"); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpSLE(idx_dyn, - ConstantInt::get(ctx.types().T_size, ndims)), - inBB, outBB); + auto oor = ctx.builder.CreateICmpSLE(idx_dyn, + ConstantInt::get(ctx.types().T_size, ndims)); + setName(ctx.emission_context, oor, "sizeddim"); + ctx.builder.CreateCondBr(oor, inBB, outBB); ctx.builder.SetInsertPoint(outBB); Value *v_one = ConstantInt::get(ctx.types().T_size, 1); ctx.builder.CreateBr(ansBB); @@ -3431,6 +3462,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, PHINode *result = ctx.builder.CreatePHI(ctx.types().T_size, 2); result->addIncoming(v_one, outBB); result->addIncoming(v_sz, inBB); + setName(ctx.emission_context, result, "arraysize"); *ret = mark_julia_type(ctx, result, false, jl_long_type); return true; } @@ -3478,17 +3510,25 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, data = emit_bitcast(ctx, data, AT->getPointerTo()); // isbits union selector bytes are stored after a->maxsize Value *ndims = (nd == -1 ? emit_arrayndims(ctx, ary) : ConstantInt::get(getInt16Ty(ctx.builder.getContext()), nd)); + setName(ctx.emission_context, ndims, "ndims"); Value *is_vector = ctx.builder.CreateICmpEQ(ndims, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 1)); + setName(ctx.emission_context, is_vector, "isvec"); Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); + setName(ctx.emission_context, selidx_v, "selidx_v"); Value *selidx_m = emit_arraylen(ctx, ary); Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); + setName(ctx.emission_context, selidx, "selidx"); ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); + setName(ctx.emission_context, ptindex, "ptindex"); data = ctx.builder.CreateInBoundsGEP(AT, data, idx); + setName(ctx.emission_context, data, "data"); } ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, offset); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx); *ret = emit_unionload(ctx, data, ptindex, ety, elsz, al, ctx.tbaa().tbaa_arraybuf, true, union_max, ctx.tbaa().tbaa_arrayselbyte); + if (ret->V) + setName(ctx.emission_context, ret->V, "arrayref"); } else { MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.noalias().aliasscope.current : nullptr; @@ -3499,6 +3539,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, aliasscope, isboxed, AtomicOrdering::NotAtomic); + if (ret->V) + setName(ctx.emission_context, ret->V, "arrayref"); } return true; } @@ -3545,6 +3587,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // the owner of the data is ary itself except if ary->how == 3 flags = ctx.builder.CreateAnd(flags, 3); Value *is_owned = ctx.builder.CreateICmpEQ(flags, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 3)); + setName(ctx.emission_context, is_owned, "has_owner"); BasicBlock *curBB = ctx.builder.GetInsertBlock(); BasicBlock *ownedBB = BasicBlock::Create(ctx.builder.getContext(), "array_owned", ctx.f); BasicBlock *mergeBB = BasicBlock::Create(ctx.builder.getContext(), "merge_own", ctx.f); @@ -3558,6 +3601,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_bitcast(ctx, decay_derived(ctx, aryv), ctx.types().T_pprjlvalue), jl_array_data_owner_offset(nd) / sizeof(jl_value_t*)), Align(sizeof(void*))); + setName(ctx.emission_context, own_ptr, "external_owner"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); ai.decorateInst(maybe_mark_load_dereferenceable(own_ptr, false, (jl_value_t*)jl_array_any_type)); } @@ -3571,6 +3615,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, data_owner = ctx.builder.CreatePHI(ctx.types().T_prjlvalue, 2); data_owner->addIncoming(aryv, curBB); data_owner->addIncoming(own_ptr, ownedBB); + setName(ctx.emission_context, data_owner, "data_owner"); } if (!isboxed && jl_is_uniontype(ety)) { Type *AT = ArrayType::get(IntegerType::get(ctx.builder.getContext(), 8 * al), (elsz + al - 1) / al); @@ -3587,15 +3632,21 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { Value *ndims = (nd == -1 ? emit_arrayndims(ctx, ary) : ConstantInt::get(getInt16Ty(ctx.builder.getContext()), nd)); Value *is_vector = ctx.builder.CreateICmpEQ(ndims, ConstantInt::get(getInt16Ty(ctx.builder.getContext()), 1)); + setName(ctx.emission_context, is_vector, "is_vector"); Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); + setName(ctx.emission_context, selidx_v, "selidx_v"); Value *selidx_m = emit_arraylen(ctx, ary); Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); + setName(ctx.emission_context, selidx, "selidx"); ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); + setName(ctx.emission_context, ptindex, "ptindex"); data = ctx.builder.CreateInBoundsGEP(AT, data, idx); + setName(ctx.emission_context, data, "data"); } ptindex = emit_bitcast(ctx, ptindex, getInt8PtrTy(ctx.builder.getContext())); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, offset); ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), ptindex, idx); + setName(ctx.emission_context, ptindex, "ptindex"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayselbyte); ai.decorateInst(ctx.builder.CreateStore(tindex, ptindex)); if (elsz > 0 && (!jl_is_datatype(val.typ) || jl_datatype_size(val.typ) > 0)) { @@ -3694,6 +3745,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, idx = emit_bounds_check(ctx, va_ary, NULL, idx, valen, boundscheck); idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, ctx.nReqArgs)); Instruction *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, idx), Align(sizeof(void*))); + setName(ctx.emission_context, v, "getfield"); // if we know the result type of this load, we will mark that information here too jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_value); ai.decorateInst(maybe_mark_load_dereferenceable(v, false, rt)); @@ -3862,6 +3914,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *fieldtyp_p = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, decay_derived(ctx, emit_bitcast(ctx, types_svec, ctx.types().T_pprjlvalue)), idx); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *fieldtyp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); + setName(ctx.emission_context, fieldtyp, "fieldtype"); *ret = mark_julia_type(ctx, fieldtyp, true, (jl_value_t*)jl_type_type); return true; } @@ -3900,6 +3953,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, INTPTR_MAX)); cast(len)->setMetadata(LLVMContext::MD_range, rng); } + setName(ctx.emission_context, len, "sizeof"); *ret = mark_julia_type(ctx, len, false, jl_long_type); return true; } @@ -3914,6 +3968,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, elsize = ctx.builder.CreateZExt(emit_arrayelsize(ctx, obj), ctx.types().T_size); } *ret = mark_julia_type(ctx, ctx.builder.CreateMul(len, elsize), false, jl_long_type); + if (ret->V) + setName(ctx.emission_context, ret->V, "sizeof"); return true; } } @@ -4030,6 +4086,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } } Value *isdef = ctx.builder.CreateIsNotNull(fldv); + setName(ctx.emission_context, isdef, "isdefined"); *ret = mark_julia_type(ctx, isdef, false, jl_bool_type); } else { @@ -4131,6 +4188,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos break; case jl_returninfo_t::Union: result = emit_static_alloca(ctx, ArrayType::get(getInt8Ty(ctx.builder.getContext()), returninfo.union_bytes)); + setName(ctx.emission_context, result, "sret_box"); if (returninfo.union_align > 1) result->setAlignment(Align(returninfo.union_align)); argvals[idx] = result; @@ -4207,6 +4265,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos } jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); TheCallee = ai.decorateInst(ctx.builder.CreateAlignedLoad(TheCallee->getType(), GV, Align(sizeof(void*)))); + setName(ctx.emission_context, TheCallee, namep); } CallInst *call = ctx.builder.CreateCall(cft, TheCallee, argvals); call->setAttributes(returninfo.attrs); @@ -4276,6 +4335,7 @@ static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty } jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); theFptr = ai.decorateInst(ctx.builder.CreateAlignedLoad(pfunc, GV, Align(sizeof(void*)))); + setName(ctx.emission_context, theFptr, namep); } else { theFptr = jl_Module->getOrInsertFunction(specFunctionObject, ctx.types().T_jlfunc).getCallee(); @@ -4574,16 +4634,20 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, false, GlobalVariable::PrivateLinkage, initnul); LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); + setName(ctx.emission_context, cachedval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".cached"); cachedval->setOrdering(AtomicOrdering::Unordered); BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); BasicBlock *currentbb = ctx.builder.GetInsertBlock(); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpNE(cachedval, initnul), have_val, not_found); + auto iscached = ctx.builder.CreateICmpNE(cachedval, initnul); + setName(ctx.emission_context, iscached, "iscached"); + ctx.builder.CreateCondBr(iscached, have_val, not_found); ctx.f->getBasicBlockList().push_back(not_found); ctx.builder.SetInsertPoint(not_found); Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func), { literal_pointer_val(ctx, (jl_value_t*)m), literal_pointer_val(ctx, (jl_value_t*)s) }); + setName(ctx.emission_context, bval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".found"); ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); ctx.builder.CreateBr(have_val); ctx.f->getBasicBlockList().push_back(have_val); @@ -4591,6 +4655,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); p->addIncoming(cachedval, currentbb); p->addIncoming(bval, not_found); + setName(ctx.emission_context, p, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s)); return p; } if (assign) { @@ -4614,6 +4679,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, bool isvol, MDNode *tbaa) { LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); + setName(ctx.emission_context, v, jl_symbol_name(name) + StringRef(".checked")); if (isvol) v->setVolatile(true); v->setOrdering(AtomicOrdering::Unordered); @@ -4640,6 +4706,7 @@ static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i) i + sizeof(jl_svec_t) / sizeof(jl_value_t*)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *sp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); + setName(ctx.emission_context, sp, "sparam"); Value *isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp, false, true), emit_tagfrom(ctx, jl_tvar_type)); jl_unionall_t *sparam = (jl_unionall_t*)ctx.linfo->def.method->sig; for (size_t j = 0; j < i; j++) { @@ -4745,9 +4812,11 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va else { // copy value to a non-mutable (non-volatile SSA) location AllocaInst *varslot = cast(vi.value.V); + setName(ctx.emission_context, varslot, jl_symbol_name(varname)); Type *T = varslot->getAllocatedType(); assert(!varslot->isArrayAllocation() && "variables not expected to be VLA"); AllocaInst *ssaslot = cast(varslot->clone()); + setName(ctx.emission_context, ssaslot, jl_symbol_name(varname) + StringRef(".ssa")); ssaslot->insertAfter(varslot); if (vi.isVolatile) { Value *unbox = ctx.builder.CreateAlignedLoad(ssaslot->getAllocatedType(), varslot, @@ -4800,8 +4869,10 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va isnull = box_isnull; } } - if (isnull) + if (isnull) { + setName(ctx.emission_context, isnull, jl_symbol_name(varname) + StringRef("_is_null")); undef_var_error_ifnot(ctx, isnull, varname); + } return v; } @@ -5270,6 +5341,7 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met Function::ExternalLinkage, fname, jl_Module); jl_init_function(F, ctx.emission_context.TargetTriple); + jl_name_jlfunc_args(ctx.emission_context, F); F->setAttributes(AttributeList::get(ctx.builder.getContext(), {get_func_attrs(ctx.builder.getContext()), F->getAttributes()})); } Function *specF = NULL; @@ -5742,6 +5814,7 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod GlobalVariable::InternalLinkage, name, M); jl_init_function(f, params.TargetTriple); + jl_name_jlfunc_args(params, f); //f->setAlwaysInline(); ctx.f = f; // for jl_Module BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", f); @@ -6230,6 +6303,7 @@ static Function* gen_cfun_wrapper( theFptr = Function::Create(ctx.types().T_jlfunc, GlobalVariable::ExternalLinkage, fname, jl_Module); jl_init_function(theFptr, ctx.emission_context.TargetTriple); + jl_name_jlfunc_args(ctx.emission_context, theFptr); addRetAttr(theFptr, Attribute::NonNull); } else { @@ -6284,9 +6358,11 @@ static Function* gen_cfun_wrapper( else { if (jlfunc_sret) { result = emit_static_alloca(ctx, getAttributeAtIndex(returninfo.attrs, 1, Attribute::StructRet).getValueAsType()); + setName(ctx.emission_context, result, "sret"); assert(cast(result->getType())->hasSameElementTypeAs(cast(cft->getParamType(0)))); } else { result = emit_static_alloca(ctx, get_unionbytes_type(ctx.builder.getContext(), returninfo.union_bytes)); + setName(ctx.emission_context, result, "result_union"); assert(cast(result->getType())->hasSameElementTypeAs(cast(cft->getParamType(0)))); } } @@ -6294,6 +6370,7 @@ static Function* gen_cfun_wrapper( } if (returninfo.return_roots) { AllocaInst *return_roots = emit_static_alloca(ctx, get_returnroots_type(ctx, returninfo.return_roots)); + setName(ctx.emission_context, return_roots, "return_roots"); args.push_back(return_roots); } if (gcstack_arg) @@ -6433,6 +6510,8 @@ static Function* gen_cfun_wrapper( GlobalVariable::ExternalLinkage, funcName, M); jl_init_function(cw_make, ctx.emission_context.TargetTriple); + cw_make->getArg(0)->setName("wrapper"); + cw_make->getArg(1)->setName("newval"); BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", cw_make); IRBuilder<> cwbuilder(b0); Function::arg_iterator AI = cw_make->arg_begin(); @@ -6596,6 +6675,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con if (outboxed) { assert(jl_datatype_size(output_type) == sizeof(void*) * 4); Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type); + setName(ctx.emission_context, strct, "cfun_result"); Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), ctx.types().T_size->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), output_type); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); @@ -6674,6 +6754,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret ++GeneratedInvokeWrappers; Function *w = Function::Create(get_func_sig(M->getContext()), GlobalVariable::ExternalLinkage, funcName, M); jl_init_function(w, params.TargetTriple); + jl_name_jlfunc_args(params, w); w->setAttributes(AttributeList::get(M->getContext(), {get_func_attrs(M->getContext()), w->getAttributes()})); w->addFnAttr(Attribute::OptimizeNone); w->addFnAttr(Attribute::NoInline); @@ -6710,6 +6791,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret case jl_returninfo_t::SRet: assert(cast(ftype->getParamType(0))->isOpaqueOrPointeeTypeMatches(getAttributeAtIndex(f.attrs, 1, Attribute::StructRet).getValueAsType())); result = ctx.builder.CreateAlloca(getAttributeAtIndex(f.attrs, 1, Attribute::StructRet).getValueAsType()); + setName(ctx.emission_context, result, "sret"); args[idx] = result; idx++; break; @@ -6719,10 +6801,12 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret result->setAlignment(Align(f.union_align)); args[idx] = result; idx++; + setName(ctx.emission_context, result, "result_union"); break; } if (f.return_roots) { AllocaInst *return_roots = emit_static_alloca(ctx, ArrayType::get(ctx.types().T_prjlvalue, f.return_roots)); + setName(ctx.emission_context, return_roots, "return_roots"); args[idx] = return_roots; idx++; } @@ -7108,11 +7192,9 @@ static jl_llvm_functions_t // jl_printf(JL_STDERR, "\n*** compiling %s at %s:%d\n\n", // jl_symbol_name(ctx.name), ctx.file.str().c_str(), toplineno); - ctx.debug_enabled = true; + bool debug_enabled = ctx.emission_context.debug_level != 0; if (dbgFuncName.empty()) // Should never happen anymore? - ctx.debug_enabled = 0; - if (jl_options.debug_level == 0) - ctx.debug_enabled = 0; + debug_enabled = false; // step 2. process var-info lists to see what vars need boxing int n_ssavalues = jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_len(src->ssavaluetypes); @@ -7236,6 +7318,11 @@ static jl_llvm_functions_t GlobalVariable::ExternalLinkage, declarations.specFunctionObject, M); jl_init_function(f, ctx.emission_context.TargetTriple); + if (needsparams) { + jl_name_jlfuncparams_args(ctx.emission_context, f); + } else { + jl_name_jlfunc_args(ctx.emission_context, f); + } f->setAttributes(AttributeList::get(ctx.builder.getContext(), {get_func_attrs(ctx.builder.getContext()), f->getAttributes()})); returninfo.decl = f; declarations.functionObject = needsparams ? "jl_fptr_sparam" : "jl_fptr_args"; @@ -7281,14 +7368,14 @@ static jl_llvm_functions_t tableKind = DICompileUnit::DebugNameTableKind::GNU; else tableKind = DICompileUnit::DebugNameTableKind::None; - DIBuilder dbuilder(*M, true, ctx.debug_enabled ? getOrCreateJuliaCU(*M, emissionKind, tableKind) : NULL); + DIBuilder dbuilder(*M, true, debug_enabled ? getOrCreateJuliaCU(*M, emissionKind, tableKind) : NULL); DIFile *topfile = NULL; DISubprogram *SP = NULL; DebugLoc noDbg, topdebugloc; - if (ctx.debug_enabled) { + if (debug_enabled) { topfile = dbuilder.createFile(ctx.file, "."); DISubroutineType *subrty; - if (jl_options.debug_level <= 1) + if (ctx.emission_context.debug_level <= 1) subrty = debuginfo.jl_di_func_null_sig; else if (!specsig) subrty = debuginfo.jl_di_func_sig; @@ -7309,7 +7396,7 @@ static jl_llvm_functions_t ); topdebugloc = DILocation::get(ctx.builder.getContext(), toplineno, 0, SP, NULL); f->setSubprogram(SP); - if (jl_options.debug_level >= 2) { + if (ctx.emission_context.debug_level >= 2) { const bool AlwaysPreserve = true; // Go over all arguments and local variables and initialize their debug information for (i = 0; i < nreq; i++) { @@ -7371,6 +7458,7 @@ static jl_llvm_functions_t fArg = &*AI++; argArray = &*AI++; pargArray = ctx.builder.CreateAlloca(argArray->getType()); + setName(ctx.emission_context, pargArray, "stackargs"); ctx.builder.CreateStore(argArray, pargArray, true/*volatile store to prevent removal of this alloca*/); argCount = &*AI++; ctx.argArray = argArray; @@ -7412,6 +7500,7 @@ static jl_llvm_functions_t lv->setName(jl_symbol_name(s)); varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); varinfo.pTIndex = emit_static_alloca(ctx, getInt8Ty(ctx.builder.getContext())); + setName(ctx.emission_context, varinfo.pTIndex, "tindex"); } else if (allunbox) { // all ghost values just need a selector allocated @@ -7438,7 +7527,7 @@ static jl_llvm_functions_t } varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); alloc_def_flag(ctx, varinfo); - if (ctx.debug_enabled && varinfo.dinfo) { + if (debug_enabled && varinfo.dinfo) { assert((Metadata*)varinfo.dinfo->getType() != debuginfo.jl_pvalue_dillvmt); dbuilder.insertDeclare(lv, varinfo.dinfo, dbuilder.createExpression(), topdebugloc, @@ -7455,7 +7544,7 @@ static jl_llvm_functions_t StoreInst *SI = new StoreInst(Constant::getNullValue(ctx.types().T_prjlvalue), av, false, Align(sizeof(void*))); SI->insertAfter(ctx.topalloca); varinfo.boxroot = av; - if (ctx.debug_enabled && varinfo.dinfo) { + if (debug_enabled && varinfo.dinfo) { DIExpression *expr; if ((Metadata*)varinfo.dinfo->getType() == debuginfo.jl_pvalue_dillvmt) { expr = dbuilder.createExpression(); @@ -7656,7 +7745,7 @@ static jl_llvm_functions_t ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), false, vi.value.typ)); theArg = mark_julia_type(ctx, load, true, vi.value.typ); - if (ctx.debug_enabled && vi.dinfo && !vi.boxroot && !vi.value.V) { + if (debug_enabled && vi.dinfo && !vi.boxroot && !vi.value.V) { SmallVector addr; addr.push_back(llvm::dwarf::DW_OP_deref); addr.push_back(llvm::dwarf::DW_OP_plus_uconst); @@ -7675,7 +7764,7 @@ static jl_llvm_functions_t assert(vi.value.V == NULL && "unexpected variable slot created for argument"); // keep track of original (possibly boxed) value to avoid re-boxing or moving vi.value = theArg; - if (specsig && theArg.V && ctx.debug_enabled && vi.dinfo) { + if (specsig && theArg.V && debug_enabled && vi.dinfo) { SmallVector addr; Value *parg; if (theArg.ispointer()) { @@ -7801,7 +7890,7 @@ static jl_llvm_functions_t else info.is_user_code = in_user_mod(module); info.is_tracked = in_tracked_path(info.file); - if (ctx.debug_enabled) { + if (debug_enabled) { StringRef fname; if (jl_is_method_instance(method)) method = ((jl_method_instance_t*)method)->def.value; @@ -8042,7 +8131,7 @@ static jl_llvm_functions_t while (cursor != -1) { int32_t debuginfoloc = ((int32_t*)jl_array_data(src->codelocs))[cursor]; if (debuginfoloc > 0) { - if (ctx.debug_enabled) + if (debug_enabled) ctx.builder.SetCurrentDebugLocation(linetable.at(debuginfoloc).loc); coverageVisitStmt(debuginfoloc); } @@ -8444,7 +8533,7 @@ static jl_llvm_functions_t for (auto &I : BB) { CallBase *call = dyn_cast(&I); if (call) { - if (ctx.debug_enabled && !I.getDebugLoc()) { + if (debug_enabled && !I.getDebugLoc()) { // LLVM Verifier: inlinable function call in a function with debug info must have a !dbg location // make sure that anything we attempt to call has some inlining info, just in case optimization messed up // (except if we know that it is an intrinsic used in our prologue, which should never have its own debug subprogram) @@ -8469,7 +8558,7 @@ static jl_llvm_functions_t in_prologue = false; } } - if (ctx.debug_enabled) + if (debug_enabled) dbuilder.finalize(); if (ctx.vaSlot > 0) { @@ -8665,6 +8754,7 @@ jl_llvm_functions_t jl_emit_codeinst( if (// keep code when keeping everything !(JL_DELETE_NON_INLINEABLE) || // aggressively keep code when debugging level >= 2 + // note that this uses the global jl_options.debug_level, not the local emission_ctx.debug_level jl_options.debug_level > 1) { // update the stored code if (inferred != (jl_value_t*)src) { diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index ee1ded5a51e44..09e04eb683af1 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -345,12 +345,14 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) else if (!ty->isIntOrPtrTy() && !ty->isFloatingPointTy()) { assert(DL.getTypeSizeInBits(ty) == DL.getTypeSizeInBits(to)); AllocaInst *cast = ctx.builder.CreateAlloca(ty); + setName(ctx.emission_context, cast, "coercion"); ctx.builder.CreateStore(unboxed, cast); unboxed = ctx.builder.CreateLoad(to, ctx.builder.CreateBitCast(cast, to->getPointerTo())); } else if (frompointer) { Type *INTT_to = INTT(to, DL); unboxed = ctx.builder.CreatePtrToInt(unboxed, INTT_to); + setName(ctx.emission_context, unboxed, "coercion"); if (INTT_to != to) unboxed = ctx.builder.CreateBitCast(unboxed, to); } @@ -359,6 +361,7 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) if (to != INTT_to) unboxed = ctx.builder.CreateBitCast(unboxed, INTT_to); unboxed = emit_inttoptr(ctx, unboxed, to); + setName(ctx.emission_context, unboxed, "coercion"); } else { unboxed = ctx.builder.CreateBitCast(unboxed, to); @@ -394,6 +397,7 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va if (jt == (jl_value_t*)jl_bool_type || to->isIntegerTy(1)) { jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); Instruction *unbox_load = ai.decorateInst(ctx.builder.CreateLoad(getInt8Ty(ctx.builder.getContext()), maybe_bitcast(ctx, p, getInt8PtrTy(ctx.builder.getContext())))); + setName(ctx.emission_context, unbox_load, "unbox"); if (jt == (jl_value_t*)jl_bool_type) unbox_load->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), @@ -421,12 +425,14 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va (to->isFloatingPointTy() || to->isIntegerTy() || to->isPointerTy()) && DL.getTypeSizeInBits(AllocType) == DL.getTypeSizeInBits(to)) { Instruction *load = ctx.builder.CreateAlignedLoad(AllocType, p, Align(alignment)); + setName(ctx.emission_context, load, "unbox"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); return emit_unboxed_coercion(ctx, to, ai.decorateInst(load)); } } p = maybe_bitcast(ctx, p, ptype); Instruction *load = ctx.builder.CreateAlignedLoad(to, p, Align(alignment)); + setName(ctx.emission_context, load, "unbox"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); return ai.decorateInst(load); } @@ -531,8 +537,10 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) } else { Value *size = emit_datatype_size(ctx, typ); + auto sizecheck = ctx.builder.CreateICmpEQ(size, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nb)); + setName(ctx.emission_context, sizecheck, "sizecheck"); error_unless(ctx, - ctx.builder.CreateICmpEQ(size, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nb)), + sizecheck, "bitcast: argument size does not match size of target type"); } } @@ -555,20 +563,25 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) storage_type, emit_bitcast(ctx, data_pointer(ctx, v), storage_type->getPointerTo()))); + setName(ctx.emission_context, vx, "bitcast"); } vxt = vx->getType(); if (vxt != llvmt) { - if (llvmt->isIntegerTy(1)) + if (llvmt->isIntegerTy(1)) { vx = ctx.builder.CreateTrunc(vx, llvmt); - else if (vxt->isIntegerTy(1) && llvmt->isIntegerTy(8)) + } else if (vxt->isIntegerTy(1) && llvmt->isIntegerTy(8)) { vx = ctx.builder.CreateZExt(vx, llvmt); - else if (vxt->isPointerTy() && !llvmt->isPointerTy()) + } else if (vxt->isPointerTy() && !llvmt->isPointerTy()) { vx = ctx.builder.CreatePtrToInt(vx, llvmt); - else if (!vxt->isPointerTy() && llvmt->isPointerTy()) + setName(ctx.emission_context, vx, "bitcast_coercion"); + } else if (!vxt->isPointerTy() && llvmt->isPointerTy()) { vx = emit_inttoptr(ctx, vx, llvmt); - else + setName(ctx.emission_context, vx, "bitcast_coercion"); + } else { vx = emit_bitcast(ctx, vx, llvmt); + setName(ctx.emission_context, vx, "bitcast_coercion"); + } } if (jl_is_concrete_type((jl_value_t*)bt)) { @@ -576,6 +589,7 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) } else { Value *box = emit_allocobj(ctx, nb, bt_value_rt); + setName(ctx.emission_context, box, "bitcast_box"); init_bits_value(ctx, box, vx, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, bt->name->wrapper); } @@ -618,8 +632,10 @@ static jl_cgval_t generic_cast( // but if we start looking at more bits we need to actually do the // rounding first instead of carrying around incorrect low bits. Value *jlfloattemp_var = emit_static_alloca(ctx, from->getType()); + setName(ctx.emission_context, jlfloattemp_var, "rounding_slot"); ctx.builder.CreateStore(from, jlfloattemp_var); from = ctx.builder.CreateLoad(from->getType(), jlfloattemp_var, /*force this to load from the stack*/true); + setName(ctx.emission_context, from, "rounded"); } } Value *ans = ctx.builder.CreateCast(Op, from, to); @@ -632,6 +648,7 @@ static jl_cgval_t generic_cast( Value *targ_rt = boxed(ctx, targ); emit_concretecheck(ctx, targ_rt, std::string(jl_intrinsic_name(f)) + ": target type not a leaf primitive type"); Value *box = emit_allocobj(ctx, nb, targ_rt); + setName(ctx.emission_context, box, "cast_box"); init_bits_value(ctx, box, ans, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, jlto->name->wrapper); } @@ -667,10 +684,13 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) Value *idx = emit_unbox(ctx, ctx.types().T_size, i, (jl_value_t*)jl_long_type); Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, im1, "pointerref_idx"); if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); + setName(ctx.emission_context, thePtr, "unbox_any_ptr"); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, thePtr, im1), Align(align_nb)); + setName(ctx.emission_context, load, "any_unbox"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); ai.decorateInst(load); return mark_julia_type(ctx, load, true, ety); @@ -679,10 +699,13 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) assert(jl_is_datatype(ety)); uint64_t size = jl_datatype_size(ety); Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + setName(ctx.emission_context, strct, "pointerref_box"); im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); + setName(ctx.emission_context, im1, "pointerref_offset"); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); thePtr = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, thePtr, getInt8PtrTy(ctx.builder.getContext())), im1); + setName(ctx.emission_context, thePtr, "pointerref_src"); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); emit_memcpy(ctx, strct, jl_aliasinfo_t::fromTBAA(ctx, tbaa), thePtr, jl_aliasinfo_t::fromTBAA(ctx, nullptr), size, 1); return mark_julia_type(ctx, strct, true, ety); @@ -693,7 +716,9 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { Value *thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - return typed_load(ctx, thePtr, im1, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, AtomicOrdering::NotAtomic, true, align_nb); + auto load = typed_load(ctx, thePtr, im1, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, AtomicOrdering::NotAtomic, true, align_nb); + setName(ctx.emission_context, load.V, "pointerref"); + return load; } else { return ghostValue(ctx, ety); @@ -736,14 +761,17 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) Value *idx = emit_unbox(ctx, ctx.types().T_size, i, (jl_value_t*)jl_long_type); Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(ctx.types().T_size, 1)); + setName(ctx.emission_context, im1, "pointerset_idx"); Value *thePtr; if (ety == (jl_value_t*)jl_any_type) { // unsafe_store to Ptr{Any} is allowed to implicitly drop GC roots. thePtr = emit_unbox(ctx, ctx.types().T_size->getPointerTo(), e, e.typ); - Instruction *store = ctx.builder.CreateAlignedStore( - ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, boxed(ctx, x)), ctx.types().T_size), - ctx.builder.CreateInBoundsGEP(ctx.types().T_size, thePtr, im1), Align(align_nb)); + auto gep = ctx.builder.CreateInBoundsGEP(ctx.types().T_size, thePtr, im1); + setName(ctx.emission_context, gep, "pointerset_ptr"); + auto val = ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, boxed(ctx, x)), ctx.types().T_size); + setName(ctx.emission_context, val, "pointerset_val"); + Instruction *store = ctx.builder.CreateAlignedStore(val, gep, Align(align_nb)); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); ai.decorateInst(store); } @@ -752,7 +780,10 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) uint64_t size = jl_datatype_size(ety); im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); - emit_memcpy(ctx, ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1), jl_aliasinfo_t::fromTBAA(ctx, nullptr), x, size, align_nb); + setName(ctx.emission_context, im1, "pointerset_offset"); + auto gep = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1); + setName(ctx.emission_context, gep, "pointerset_ptr"); + emit_memcpy(ctx, gep, jl_aliasinfo_t::fromTBAA(ctx, nullptr), x, size, align_nb); } else { bool isboxed; @@ -803,6 +834,7 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, thePtr, Align(sizeof(jl_value_t*))); + setName(ctx.emission_context, load, "atomic_pointerref"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); ai.decorateInst(load); load->setOrdering(llvm_order); @@ -823,11 +855,13 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (!jl_isbits(ety)) { assert(jl_is_datatype(ety)); Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + setName(ctx.emission_context, strct, "atomic_pointerref_box"); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); Type *loadT = Type::getIntNTy(ctx.builder.getContext(), nb * 8); thePtr = emit_bitcast(ctx, thePtr, loadT->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); LoadInst *load = ctx.builder.CreateAlignedLoad(loadT, thePtr, Align(nb)); + setName(ctx.emission_context, load, "atomic_pointerref"); jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.decorateInst(load); load->setOrdering(llvm_order); @@ -842,7 +876,9 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) assert(!isboxed); if (!type_is_ghost(ptrty)) { Value *thePtr = emit_unbox(ctx, ptrty->getPointerTo(), e, e.typ); - return typed_load(ctx, thePtr, nullptr, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, llvm_order, true, nb); + auto load = typed_load(ctx, thePtr, nullptr, ety, ctx.tbaa().tbaa_data, nullptr, isboxed, llvm_order, true, nb); + setName(ctx.emission_context, load.V, "atomic_pointerref"); + return load; } else { if (order > jl_memory_order_monotonic) @@ -942,15 +978,18 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl static Value *emit_checked_srem_int(jl_codectx_t &ctx, Value *x, Value *den) { Type *t = den->getType(); + auto ndivby0 = ctx.builder.CreateICmpNE(den, ConstantInt::get(t, 0)); + setName(ctx.emission_context, ndivby0, "ndivby0"); raise_exception_unless(ctx, - ctx.builder.CreateICmpNE(den, ConstantInt::get(t, 0)), + ndivby0, literal_pointer_val(ctx, jl_diverror_exception)); BasicBlock *m1BB = BasicBlock::Create(ctx.builder.getContext(), "minus1", ctx.f); BasicBlock *okBB = BasicBlock::Create(ctx.builder.getContext(), "oksrem", ctx.f); BasicBlock *cont = BasicBlock::Create(ctx.builder.getContext(), "after_srem", ctx.f); PHINode *ret = PHINode::Create(t, 2); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(den ,ConstantInt::get(t, -1, true)), - m1BB, okBB); + auto divbym1 = ctx.builder.CreateICmpEQ(den, ConstantInt::get(t, -1, true)); + setName(ctx.emission_context, divbym1, "divbym1"); + ctx.builder.CreateCondBr(divbym1, m1BB, okBB); ctx.builder.SetInsertPoint(m1BB); ctx.builder.CreateBr(cont); ctx.builder.SetInsertPoint(okBB); @@ -960,6 +999,7 @@ static Value *emit_checked_srem_int(jl_codectx_t &ctx, Value *x, Value *den) ret->addIncoming(// rem(typemin, -1) is undefined ConstantInt::get(t, 0), m1BB); ret->addIncoming(sremval, okBB); + setName(ctx.emission_context, ret, "checked_srem"); ctx.builder.Insert(ret); return ret; } @@ -995,6 +1035,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_cgval_t y, jl_value_t *rt_hint) { Value *isfalse = emit_condition(ctx, c, "ifelse"); + setName(ctx.emission_context, isfalse, "ifelse_cond"); jl_value_t *t1 = x.typ; jl_value_t *t2 = y.typ; // handle cases where the condition is irrelevant based on type info @@ -1067,6 +1108,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ if (x_ptr->getType() != y_ptr->getType()) y_ptr = ctx.builder.CreateBitCast(y_ptr, x_ptr->getType()); ifelse_result = ctx.builder.CreateSelect(isfalse, y_ptr, x_ptr); + setName(ctx.emission_context, ifelse_result, "ifelse_result"); ifelse_tbaa = MDNode::getMostGenericTBAA(x.tbaa, y.tbaa); if (ifelse_tbaa == NULL) { // LLVM won't return a TBAA result for the root, but mark_julia_struct requires it: make it now @@ -1111,6 +1153,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ ctx.builder.SetInsertPoint(post); ctx.builder.Insert(ret); tindex = ret; + setName(ctx.emission_context, tindex, "ifelse_tindex"); } jl_cgval_t ret = mark_julia_slot(ifelse_result, rt_hint, tindex, ifelse_tbaa); if (x_vboxed || y_vboxed) { @@ -1119,6 +1162,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ if (!y_vboxed) y_vboxed = ConstantPointerNull::get(cast(x_vboxed->getType())); ret.Vboxed = ctx.builder.CreateSelect(isfalse, y_vboxed, x_vboxed); + setName(ctx.emission_context, ret.Vboxed, "ifelse_vboxed"); assert(ret.Vboxed->getType() == ctx.types().T_prjlvalue); } return ret; @@ -1126,6 +1170,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ ifelse_result = ctx.builder.CreateSelect(isfalse, boxed(ctx, y), boxed(ctx, x)); + setName(ctx.emission_context, ifelse_result, "ifelse_result"); } jl_value_t *jt = (t1 == t2 ? t1 : rt_hint); return mark_julia_type(ctx, ifelse_result, isboxed, jt); @@ -1411,7 +1456,9 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg FunctionCallee intr = Intrinsic::getDeclaration(jl_Module, intr_id, makeArrayRef(t)); Value *res = ctx.builder.CreateCall(intr, {x, y}); Value *val = ctx.builder.CreateExtractValue(res, ArrayRef(0)); + setName(ctx.emission_context, val, "checked"); Value *obit = ctx.builder.CreateExtractValue(res, ArrayRef(1)); + setName(ctx.emission_context, obit, "overflow"); Value *obyte = ctx.builder.CreateZExt(obit, getInt8Ty(ctx.builder.getContext())); jl_value_t *params[2]; @@ -1429,30 +1476,31 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg case checked_sdiv_int: { Value *typemin = ctx.builder.CreateShl(ConstantInt::get(t, 1), t->getPrimitiveSizeInBits() - 1); - raise_exception_unless(ctx, - ctx.builder.CreateAnd( - ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)), - ctx.builder.CreateOr( - ctx.builder.CreateICmpNE(y, ConstantInt::get(t, -1, true)), - ctx.builder.CreateICmpNE(x, typemin))), - literal_pointer_val(ctx, jl_diverror_exception)); + auto cond = ctx.builder.CreateAnd( + ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)), + ctx.builder.CreateOr( + ctx.builder.CreateICmpNE(y, ConstantInt::get(t, -1, true)), + ctx.builder.CreateICmpNE(x, typemin))); + setName(ctx.emission_context, cond, "divisor_valid"); + raise_exception_unless(ctx, cond, literal_pointer_val(ctx, jl_diverror_exception)); return ctx.builder.CreateSDiv(x, y); } - case checked_udiv_int: - raise_exception_unless(ctx, - ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)), - literal_pointer_val(ctx, jl_diverror_exception)); + case checked_udiv_int: { + auto cond = ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)); + setName(ctx.emission_context, cond, "ndivby0"); + raise_exception_unless(ctx, cond, literal_pointer_val(ctx, jl_diverror_exception)); return ctx.builder.CreateUDiv(x, y); - + } case checked_srem_int: return emit_checked_srem_int(ctx, x, y); - case checked_urem_int: - raise_exception_unless(ctx, - ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)), - literal_pointer_val(ctx, jl_diverror_exception)); + case checked_urem_int: { + auto cond = ctx.builder.CreateICmpNE(y, ConstantInt::get(t, 0)); + setName(ctx.emission_context, cond, "ndivby0"); + raise_exception_unless(ctx, cond, literal_pointer_val(ctx, jl_diverror_exception)); return ctx.builder.CreateURem(x, y); + } case eq_int: *newtyp = jl_bool_type; return ctx.builder.CreateICmpEQ(x, y); case ne_int: *newtyp = jl_bool_type; return ctx.builder.CreateICmpNE(x, y); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 1468b4f55f5a7..88e4b0f97927f 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -195,6 +195,8 @@ static jl_callptr_t _jl_compile_codeinst( jl_codegen_params_t params(std::move(context), jl_ExecutionEngine->getDataLayout(), jl_ExecutionEngine->getTargetTriple()); // Locks the context params.cache = true; params.world = world; + params.imaging = imaging_default(); + params.debug_level = jl_options.debug_level; jl_workqueue_t emitted; { orc::ThreadSafeModule result_m = @@ -358,6 +360,8 @@ int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void * return std::make_pair(M.getDataLayout(), Triple(M.getTargetTriple())); }); jl_codegen_params_t params(into->getContext(), std::move(target_info.first), std::move(target_info.second)); + params.imaging = imaging_default(); + params.debug_level = jl_options.debug_level; if (pparams == NULL) pparams = ¶ms; assert(pparams->tsctx.getContext() == into->getContext().getContext()); diff --git a/src/jitlayers.h b/src/jitlayers.h index 4e53bbed6e766..3aa3998d3ac23 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -237,6 +237,7 @@ typedef struct _jl_codegen_params_t { bool cache = false; bool external_linkage = false; bool imaging; + int debug_level; _jl_codegen_params_t(orc::ThreadSafeContext ctx, DataLayout DL, Triple triple) : tsctx(std::move(ctx)), tsctx_lock(tsctx.getLock()), DL(std::move(DL)), TargetTriple(std::move(triple)), imaging(imaging_default()) {} diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 13a68be2927de..21567468ffe9e 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -519,29 +519,34 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # -g @test readchomp(`$exename -E "Base.JLOptions().debug_level" -g`) == "2" - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g0`) - @test code[2] - code = code[1] - @test occursin("llvm.module.flags", code) - @test !occursin("llvm.dbg.cu", code) - @test !occursin("int.jl", code) - @test !occursin("Int64", code) - end - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g1`) - @test code[2] - code = code[1] - @test occursin("llvm.module.flags", code) - @test occursin("llvm.dbg.cu", code) - @test occursin("int.jl", code) - @test !occursin("Int64", code) - end - let code = writereadpipeline("code_llvm(stdout, +, (Int64, Int64), raw=true, dump_module=true)", `$exename -g2`) - @test code[2] - code = code[1] - @test occursin("llvm.module.flags", code) - @test occursin("llvm.dbg.cu", code) - @test occursin("int.jl", code) - @test occursin("\"Int64\"", code) + # --print-before/--print-after with pass names is broken on Windows due to no-gnu-unique issues + if !Sys.iswindows() + withenv("JULIA_LLVM_ARGS" => "--print-before=FinalLowerGC") do + let code = readchomperrors(`$exename -g0 -E "@eval Int64(1)+Int64(1)"`) + @test code[1] + code = code[3] + @test occursin("llvm.module.flags", code) + @test !occursin("llvm.dbg.cu", code) + @test !occursin("int.jl", code) + @test !occursin("\"Int64\"", code) + end + let code = readchomperrors(`$exename -g1 -E "@eval Int64(1)+Int64(1)"`) + @test code[1] + code = code[3] + @test occursin("llvm.module.flags", code) + @test occursin("llvm.dbg.cu", code) + @test occursin("int.jl", code) + @test !occursin("\"Int64\"", code) + end + let code = readchomperrors(`$exename -g2 -E "@eval Int64(1)+Int64(1)"`) + @test code[1] + code = code[3] + @test occursin("llvm.module.flags", code) + @test occursin("llvm.dbg.cu", code) + @test occursin("int.jl", code) + @test occursin("\"Int64\"", code) + end + end end # --check-bounds diff --git a/test/llvmpasses/llvmcall.jl b/test/llvmpasses/llvmcall.jl index a55201c3e3bc3..d39f64d5f839c 100644 --- a/test/llvmpasses/llvmcall.jl +++ b/test/llvmpasses/llvmcall.jl @@ -13,20 +13,109 @@ end @generated foo(x)=:(ccall("extern foo", llvmcall, $x, ($x,), x)) bar(x) = ntuple(i -> VecElement{Float16}(x[i]), 2) -# CHECK: call half @foo(half %{{[0-9]+}}) +# CHECK: define +# CHECK-SAME: half @julia_foo +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: [[FOO_RET:%.*]] = call half @foo(half [[FOO_ARG:%.*]]) +# CHECK-NOT: define +# CHECK: ret half +# CHECK-NOT: define +# CHECK: } emit(foo, Float16) -# CHECK: call [2 x half] @foo([2 x half] %{{[0-9]+}}) +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: { + +# CHECK: define +# CHECK-SAME: [2 x half] @julia_foo +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: [[FOO_RET:%.*]] = call [2 x half] @foo([2 x half] [[FOO_ARG:%.*]]) +# CHECK-NOT: define +# CHECK: ret [2 x half] +# CHECK-NOT: define +# CHECK: } emit(foo, NTuple{2, Float16}) -# CHECK: call <2 x half> @foo(<2 x half> %{{[0-9]+}}) +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: { + +# CHECK: define +# CHECK-SAME: <2 x half> @julia_foo +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: [[FOO_RET:%.*]] call <2 x half> @foo(<2 x half> [[FOO_ARG:%.*]]) +# CHECK-NOT: define +# CHECK: ret <2 x half> +# CHECK-NOT: define +# CHECK: } emit(foo, NTuple{2, VecElement{Float16}}) -# CHECK: call i8 addrspace(3)* @foo(i8 addrspace(3)* %{{[0-9]+}}) +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: { + +# CHECK: define +# CHECK-SAME: i8 addrspace(3)* @julia_foo +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: [[FOO_RET:%.*]] call i8 addrspace(3)* @foo(i8 addrspace(3)* [[FOO_ARG:%.*]]) +# CHECK-NOT: define +# CHECK: ret i8 addrspace(3)* +# CHECK-NOT: define +# CHECK: } emit(foo, Core.LLVMPtr{Float32, 3}) -# CHECK: call { i32, i32 } @foo({ i32, i32 } %{{[0-9]+}}) +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: { + +# CHECK: define +# CHECK-SAME: [2 x i32] @julia_foo +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: [[FOO_RET:%.*]] call { i32, i32 } @foo({ i32, i32 } [[FOO_ARG:%.*]]) +# CHECK-NOT: define +# CHECK: ret [2 x i32] +# CHECK-NOT: define +# CHECK: } emit(foo, Foo) -# CHECK: define {{(swiftcc )?}}<2 x half> @julia_bar_{{[0-9]+}}( +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: { + +# CHECK: define +# CHECK-SAME: <2 x half> @julia_bar +# CHECK-SAME: [2 x half] +# CHECK-SAME: { +# CHECK-NOT: define +# CHECK: ret <2 x half> +# CHECK-NOT: define +# CHECK: } emit(bar, NTuple{2, Float16}) + +# COM: Make sure that we don't miss a function by accident (helps localize errors) +# CHECK-NOT: { +# CHECK-NOT: } +# CHECK: define +# CHECK-SAME: nonnull {} addrspace(10)* @jfptr +# CHECK-SAME: {