diff --git a/base/bool.jl b/base/bool.jl index 988bf874b1ebe..44bd919a7b6c4 100644 --- a/base/bool.jl +++ b/base/bool.jl @@ -30,11 +30,7 @@ julia> .![true false true] 0 1 0 ``` """ -function !(x::Bool) - ## We need a better heuristic to detect this automatically - @_pure_meta - return not_int(x) -end +!(x::Bool) = not_int(x) (~)(x::Bool) = !x (&)(x::Bool, y::Bool) = and_int(x, y) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index a208156274496..d230f0917424b 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -171,23 +171,20 @@ function optimize(opt::OptimizationState, @nospecialize(result)) # compute inlining and other related optimizations if (isa(result, Const) || isconstType(result)) - proven_pure = false - # must be proven pure to use const_api; otherwise we might skip throwing errors - # (issue #20704) - # TODO: Improve this analysis; if a function is marked @pure we should really - # only care about certain errors (e.g. method errors and type errors). - if length(ir.stmts) < 10 + proven_pure = def isa Method && def.pure + # if it can be proven pure, we can use const_api; otherwise we might skip side-effects (like throwing errors) + if !proven_pure proven_pure = true - for i in 1:length(ir.stmts) - stmt = ir.stmts[i] - if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, ir.types[i], ir, ir.sptypes) + for fl in opt.src.slotflags + if (fl & SLOT_USEDUNDEF) != 0 proven_pure = false break end end if proven_pure - for fl in opt.src.slotflags - if (fl & SLOT_USEDUNDEF) != 0 + for i in 1:length(ir.stmts) + stmt = ir.stmts[i] + if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, ir.types[i], ir, ir.sptypes) proven_pure = false break end diff --git a/base/essentials.jl b/base/essentials.jl index 37c4361a47403..a21b309d8c36e 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -204,7 +204,7 @@ tail(::Tuple{}) = throw(ArgumentError("Cannot call tail on an empty tuple.")) tuple_type_head(T::Type) = (@_pure_meta; fieldtype(T::Type{<:Tuple}, 1)) function tuple_type_tail(T::Type) - @_pure_meta + @_pure_meta # TODO: this method is wrong (and not @pure) if isa(T, UnionAll) return UnionAll(T.var, tuple_type_tail(T.body)) elseif isa(T, Union) @@ -698,7 +698,7 @@ julia> f(Val(true)) struct Val{x} end -Val(x) = (@_pure_meta; Val{x}()) +Val(x) = Val{x}() """ invokelatest(f, args...; kwargs...) diff --git a/base/promotion.jl b/base/promotion.jl index 4eb80c1b68239..98950ff5944f8 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -9,8 +9,8 @@ Return the closest common ancestor of `T` and `S`, i.e. the narrowest type from which they both inherit. """ -typejoin() = (@_pure_meta; Bottom) -typejoin(@nospecialize(t)) = (@_pure_meta; t) +typejoin() = Bottom +typejoin(@nospecialize(t)) = t typejoin(@nospecialize(t), ts...) = (@_pure_meta; typejoin(t, typejoin(ts...))) function typejoin(@nospecialize(a), @nospecialize(b)) @_pure_meta diff --git a/base/reflection.jl b/base/reflection.jl index 77f67e68852b1..c4d834032a458 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -538,31 +538,6 @@ struct type with no fields. """ issingletontype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && isdefined(t, :instance)) -""" - Base.parameter_upper_bound(t::UnionAll, idx) - -Determine the upper bound of a type parameter in the underlying datatype. -This method should generally not be relied upon: -code instead should usually use static parameters in dispatch to extract these values. - -# Examples -```jldoctest -julia> struct Foo{T<:AbstractFloat, N} - x::Tuple{T, N} - end - -julia> Base.parameter_upper_bound(Foo, 1) -AbstractFloat - -julia> Base.parameter_upper_bound(Foo, 2) -Any -``` -""" -function parameter_upper_bound(t::UnionAll, idx) - @_pure_meta - return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) -end - """ typeintersect(T, S) @@ -727,13 +702,12 @@ julia> instances(Color) function instances end function to_tuple_type(@nospecialize(t)) - @_pure_meta - if isa(t,Tuple) || isa(t,AbstractArray) || isa(t,SimpleVector) + if isa(t, Tuple) || isa(t, AbstractArray) || isa(t, SimpleVector) t = Tuple{t...} end - if isa(t,Type) && t<:Tuple + if isa(t, Type) && t <: Tuple for p in unwrap_unionall(t).parameters - if !(isa(p,Type) || isa(p,TypeVar)) + if !(isa(p, Type) || isa(p, TypeVar)) error("argument tuple type must contain only types") end end diff --git a/base/tuple.jl b/base/tuple.jl index 7e8c6804bc496..6af1a5a87045c 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -94,6 +94,7 @@ function _compute_eltype(t::Type{<:Tuple}) @_pure_meta t isa Union && return promote_typejoin(eltype(t.a), eltype(t.b)) t´ = unwrap_unionall(t) + # TODO: handle Union/UnionAll correctly here r = Union{} for ti in t´.parameters r = promote_typejoin(r, rewrap_unionall(unwrapva(ti), t)) diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index e23488069d12b..55c6435921b22 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -621,7 +621,8 @@ A (usually temporary) container for holding lowered source code. * 0 = inbounds * 1,2 = inlinehint,always-inline,noinline * 3 = strict-ieee (strictfp) - * 4-6 = + * 4 = inferred-pure + * 5-6 = * 7 = has out-of-band info * `linetable` diff --git a/src/codegen.cpp b/src/codegen.cpp index 02ddbbdd37a53..6140d5f9e8df6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -548,7 +548,13 @@ static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const s static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0); static void CreateTrap(IRBuilder<> &irbuilder); static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF, - jl_cgval_t *args, size_t nargs, CallingConv::ID cc); + jl_cgval_t *args, size_t nargs, + AttributeList Attrs, CallingConv::ID cc); +static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF, + jl_cgval_t *args, size_t nargs) { + AttributeList Attrs; + return emit_jlcall(ctx, theFptr, theF, args, nargs, Attrs, JLCALL_F_CC); +} static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); @@ -2182,7 +2188,7 @@ static jl_cgval_t emit_getfield(jl_codectx_t &ctx, const jl_cgval_t &strct, jl_s strct, mark_julia_const((jl_value_t*)name) }; - Value *result = emit_jlcall(ctx, jlgetfield_func, maybe_decay_untracked(V_null), myargs_array, 2, JLCALL_F_CC); + Value *result = emit_jlcall(ctx, jlgetfield_func, maybe_decay_untracked(V_null), myargs_array, 2); return mark_julia_type(ctx, result, true, jl_any_type); } @@ -3022,7 +3028,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF, - jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) + jl_cgval_t *argv, size_t nargs, + AttributeList Attrs, CallingConv::ID cc) { // emit arguments SmallVector theArgs; @@ -3040,7 +3047,8 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF, CallInst *result = ctx.builder.CreateCall(FTy, ctx.builder.CreateBitCast(prepare_call(theFptr), FTy->getPointerTo()), theArgs); - add_return_attr(result, Attribute::NonNull); + Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::ReturnIndex, Attribute::NonNull); + result->setAttributes(Attrs); result->setCallingConv(cc); return result; } @@ -3052,7 +3060,14 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_code_instance_t // emit specialized call site jl_value_t *jlretty = codeinst->rettype; jl_returninfo_t returninfo = get_specsig_function(jl_Module, specFunctionObject, codeinst->def->specTypes, jlretty); - FunctionType *cft = returninfo.decl->getFunctionType(); + Function *f = returninfo.decl; + FunctionType *cft = f->getFunctionType(); + if (codeinst->def->def.method->pure) { + // pure marked functions don't have side-effects, nor observe them + f->addFnAttr(Thunk); + f->addFnAttr(Attribute::ReadNone); + f->addFnAttr(Attribute::NoUnwind); + } size_t nfargs = cft->getNumParams(); Value **argvals = (Value**)alloca(nfargs * sizeof(Value*)); @@ -3116,8 +3131,8 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_code_instance_t idx++; } assert(idx == nfargs); - CallInst *call = ctx.builder.CreateCall(returninfo.decl, ArrayRef(&argvals[0], nfargs)); - call->setAttributes(returninfo.decl->getAttributes()); + CallInst *call = ctx.builder.CreateCall(f, ArrayRef(&argvals[0], nfargs)); + call->setAttributes(f->getAttributes()); jl_cgval_t retval; switch (returninfo.cc) { @@ -3157,7 +3172,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_code_instance_t return retval; } -static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, StringRef specFunctionObject, +static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_code_instance_t *codeinst, StringRef specFunctionObject, jl_cgval_t *argv, size_t nargs, jl_value_t *inferred_retty) { auto theFptr = jl_Module->getOrInsertFunction(specFunctionObject, jl_func_sig) @@ -3166,11 +3181,20 @@ static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, StringRef specFunct #else ; #endif - if (auto F = dyn_cast(theFptr->stripPointerCasts())) { - add_return_attr(F, Attribute::NonNull); - F->addFnAttr(Thunk); - } - Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, JLCALL_F_CC); + AttributeList Attrs; + auto F = dyn_cast(theFptr->stripPointerCasts()); + if (F) + Attrs = F->getAttributes(); + Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::ReturnIndex, Attribute::NonNull) + .addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Thunk); + if (codeinst->def->def.method->pure) { + // pure marked functions don't have side-effects, nor observe them + Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Attribute::ReadNone) + .addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Attribute::NoUnwind); + } + if (F) + F->setAttributes(Attrs); + Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, Attrs, JLCALL_F_CC); return mark_julia_type(ctx, ret, true, inferred_retty); } @@ -3204,7 +3228,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) } if (decls.functionObject) { if (!strcmp(decls.functionObject, "jl_fptr_args")) { - result = emit_call_specfun_boxed(ctx, decls.specFunctionObject, argv, nargs, rt); + result = emit_call_specfun_boxed(ctx, codeinst, decls.specFunctionObject, argv, nargs, rt); handled = true; } else if (!!strcmp(decls.functionObject, "jl_fptr_sparam")) { @@ -3216,7 +3240,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) } } if (!handled) { - Value *r = emit_jlcall(ctx, prepare_call(jlinvoke_func), boxed(ctx, lival), argv, nargs, JLCALL_F2_CC); + Value *r = emit_jlcall(ctx, prepare_call(jlinvoke_func), boxed(ctx, lival), argv, nargs, jlinvoke_func->getAttributes(), JLCALL_F2_CC); result = mark_julia_type(ctx, r, true, rt); } if (result.typ == jl_bottom_type) @@ -3257,13 +3281,13 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) std::map::iterator it = builtin_func_map.find(jl_get_builtin_fptr(f.constant)); if (it != builtin_func_map.end()) { Value *theFptr = it->second; - Value *ret = emit_jlcall(ctx, theFptr, maybe_decay_untracked(V_null), &argv[1], nargs - 1, JLCALL_F_CC); + Value *ret = emit_jlcall(ctx, theFptr, maybe_decay_untracked(V_null), &argv[1], nargs - 1); return mark_julia_type(ctx, ret, true, rt); } } // emit function and arguments - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs, JLCALL_F_CC); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs); return mark_julia_type(ctx, callval, true, rt); } @@ -4128,7 +4152,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) assert(nargs <= jl_datatype_nfields(jl_tparam0(ty)) + 1); return emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, &argv[1]); } - Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv, nargs, JLCALL_F_CC); + Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv, nargs); // temporarily mark as `Any`, expecting `emit_ssaval_assign` to update // it to the inferred type. return mark_julia_type(ctx, val, true, (jl_value_t*)jl_any_type); @@ -4325,7 +4349,7 @@ static void emit_cfunc_invalidate( } } assert(AI == gf_thunk->arg_end()); - Value *gf_ret = emit_jlcall(ctx, jlapplygeneric_func, nullptr, myargs, nargs, JLCALL_F_CC); + Value *gf_ret = emit_jlcall(ctx, jlapplygeneric_func, nullptr, myargs, nargs); jl_cgval_t gf_retbox = mark_julia_type(ctx, gf_ret, true, jl_any_type); jl_value_t *astrt = codeinst->rettype; if (cc != jl_returninfo_t::Boxed) { @@ -4697,11 +4721,11 @@ static Function* gen_cfun_wrapper( // for jlcall, we need to pass the function object even if it is a ghost. Value *theF = boxed(ctx, inputargs[0]); assert(theF); - ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs, JLCALL_F_CC); + ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs); ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_generic); } - Value *ret = emit_jlcall(ctx, prepare_call(jlapplygeneric_func), NULL, inputargs, nargs + 1, JLCALL_F_CC); + Value *ret = emit_jlcall(ctx, prepare_call(jlapplygeneric_func), NULL, inputargs, nargs + 1); if (age_ok) { ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_after); @@ -5998,7 +6022,7 @@ static std::unique_ptr emit_function( emit_varinfo_assign(ctx, vi, tuple); } else { Value *vtpl = emit_jlcall(ctx, prepare_call(jltuple_func), maybe_decay_untracked(V_null), - vargs, ctx.nvargs, JLCALL_F_CC); + vargs, ctx.nvargs); jl_cgval_t tuple = mark_julia_type(ctx, vtpl, true, vi.value.typ); emit_varinfo_assign(ctx, vi, tuple); } diff --git a/src/julia.h b/src/julia.h index cb4a0dd3e877a..3fff9c7e47e64 100644 --- a/src/julia.h +++ b/src/julia.h @@ -243,8 +243,9 @@ typedef struct _jl_code_info_t { // 0 = inbounds // 1,2 = inlinehint,always-inline,noinline // 3 = strict-ieee (strictfp) - // 4-6 = - // 7 = has out-of-band info + // 4 = inferred-pure + // 5-6 = + // 7 = has out-of-band info // miscellaneous data: jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference jl_value_t *linetable; // Table of locations [TODO: make this volatile like slotnames] diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 185ceae9dd706..d02a88c37979f 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1160,16 +1160,20 @@ State LateLowerGCFrame::LocalScan(Function &F) { callee == write_barrier_func || callee->getName() == "memcmp") { continue; } - if (callee->hasFnAttribute(Attribute::ReadNone) || - callee->hasFnAttribute(Attribute::ReadOnly) || - callee->hasFnAttribute(Attribute::ArgMemOnly)) { - continue; + if (!callee->hasFnAttribute("thunk")) { + if (callee->hasFnAttribute(Attribute::ReadNone) || + callee->hasFnAttribute(Attribute::ReadOnly) || + callee->hasFnAttribute(Attribute::ArgMemOnly)) { + continue; + } } } - if (isa(CI) || CI->hasFnAttr(Attribute::ArgMemOnly) || - CI->hasFnAttr(Attribute::ReadNone) || CI->hasFnAttr(Attribute::ReadOnly)) { - // Intrinsics are never safepoints. - continue; + if (!CI->hasFnAttr("thunk")) { + if (isa(CI) || CI->hasFnAttr(Attribute::ArgMemOnly) || + CI->hasFnAttr(Attribute::ReadNone) || CI->hasFnAttr(Attribute::ReadOnly)) { + // Intrinsics are never safepoints. + continue; + } } int SafepointNumber = NoteSafepoint(S, BBS, CI); BBS.HasSafepoint = true; diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 5f1e0a7bb282e..e42780e3465d0 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -767,12 +767,6 @@ fUnionAll(::Type{T}) where {T} = Type{S} where S <: T @inferred fUnionAll(Real) == Type{T} where T <: Real @inferred fUnionAll(Rational{T} where T <: AbstractFloat) == Type{T} where T<:(Rational{S} where S <: AbstractFloat) -fComplicatedUnionAll(::Type{T}) where {T} = Type{Tuple{S,rand() >= 0.5 ? Int : Float64}} where S <: T -let pub = Base.parameter_upper_bound, x = fComplicatedUnionAll(Real) - @test pub(pub(x, 1), 1) == Real - @test pub(pub(x, 1), 2) == Int || pub(pub(x, 1), 2) == Float64 -end - # issue #20733 # run this test in a separate process to avoid interfering with `getindex` let def = "Base.getindex(t::NTuple{3,NTuple{2,Int}}, i::Int, j::Int, k::Int) = (t[1][i], t[2][j], t[3][k])" @@ -788,28 +782,6 @@ end f20267(x::T20267{T}, y::T) where (T) = f20267(Any[1][1], x.inds) @test Base.return_types(f20267, (Any, Any)) == Any[Union{}] -# issue #20704 -f20704(::Int) = 1 -Base.@pure b20704(@nospecialize(x)) = f20704(x) -@test b20704(42) === 1 -@test_throws MethodError b20704(42.0) - -bb20704() = b20704(Any[1.0][1]) -@test_throws MethodError bb20704() - -v20704() = Val{b20704(Any[1.0][1])} -@test_throws MethodError v20704() -@test Base.return_types(v20704, ()) == Any[Type{Val{1}}] - -Base.@pure g20704(::Int) = 1 -h20704(@nospecialize(x)) = g20704(x) -@test g20704(1) === 1 -@test_throws MethodError h20704(1.2) - -Base.@pure c20704() = (f20704(1.0); 1) -d20704() = c20704() -@test_throws MethodError d20704() - Base.@pure function a20704(x) rand() 42 diff --git a/test/llvmpasses/noinline.jl b/test/llvmpasses/noinline.jl index f542968b21979..318add0dc31c2 100644 --- a/test/llvmpasses/noinline.jl +++ b/test/llvmpasses/noinline.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# RUN: julia --startup-file=no %s %t && llvm-link -S %t/* -o %t/module.ll +# RUN: julia -g0 --startup-file=no %s %t && llvm-link -S %t/* -o %t/module.ll # RUN: cat %t/module.ll | FileCheck %s ## Notes: @@ -12,10 +12,27 @@ include(joinpath("..", "testhelpers", "llvmpasses.jl")) -# CHECK-LABEL: @julia_simple_noinline @noinline function simple_noinline(A, B) return A + B end -# CHECK: attributes #{{[0-9]+}} = {{{([a-z]+ )*}} noinline {{([a-z]+ )*}}} +@noinline Base.@pure function simple_pure_helper(A, B) + return A + B +end +function simple_pure(A, B) + return simple_pure_helper(A, B) +end + +# CHECK: define double @julia_simple_noinline_{{[0-9]+}}(double, double) #[[NOINLINE:[0-9]+]] { emit(simple_noinline, Float64, Float64) +# CHECK-LABEL: @julia_simple_pure +# CHECK: call double @julia_simple_pure_helper_{{[0-9]+}}(double %0, double %1) #[[PURE:[0-9]+]] +# CHECK: declare double @julia_simple_pure_helper_{{[0-9]+}}(double, double) #[[PURE]] +emit(simple_pure, Float64, Float64) +# CHECK-LABEL @japi1_simple_pure +# CHECK: call cc37 {{.+}} @japi1_simple_pure_helper_{{.+}} #[[PURE]] +# CHECK: declare nonnull %jl_value_t addrspace(10)* @japi1_simple_pure_helper_{{[0-9]+}}(%jl_value_t addrspace(10)*, %jl_value_t addrspace(10)**, i32) #[[PURE:[0-9]+]] +emit(simple_pure, BigFloat, BigFloat) + +# CHECK: attributes #[[NOINLINE]] = {{{([a-z]+ )*}} noinline {{([a-z]+ )*}}} +# CHECK: attributes #[[PURE]] = { nounwind readnone "thunk" } diff --git a/test/reflection.jl b/test/reflection.jl index 3b015d3a6c1b6..06514033fbfa5 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -568,10 +568,6 @@ end @test !isstructtype(Int) @test isstructtype(TLayout) -@test Base.parameter_upper_bound(ReflectionExample, 1) === AbstractFloat -@test Base.parameter_upper_bound(ReflectionExample, 2) === Any -@test Base.parameter_upper_bound(ReflectionExample{T, N} where T where N <: Real, 2) === Real - let wrapperT(T) = Base.typename(T).wrapper @test @inferred wrapperT(ReflectionExample{Float64, Int64}) == ReflectionExample