Skip to content

Commit ce9d263

Browse files
committed
codegen: propagate at-pure macro to llvm
1 parent 9bd5c18 commit ce9d263

File tree

5 files changed

+83
-36
lines changed

5 files changed

+83
-36
lines changed

doc/src/devdocs/ast.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,8 @@ A (usually temporary) container for holding lowered source code.
621621
* 0 = inbounds
622622
* 1,2 = <reserved> inlinehint,always-inline,noinline
623623
* 3 = <reserved> strict-ieee (strictfp)
624-
* 4-6 = <unused>
624+
* 4 = <reserved> inferred-pure
625+
* 5-6 = <unused>
625626
* 7 = <reserved> has out-of-band info
626627

627628
* `linetable`

src/codegen.cpp

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,13 @@ static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const s
548548
static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0);
549549
static void CreateTrap(IRBuilder<> &irbuilder);
550550
static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF,
551-
jl_cgval_t *args, size_t nargs, CallingConv::ID cc);
551+
jl_cgval_t *args, size_t nargs,
552+
AttributeList Attrs, CallingConv::ID cc);
553+
static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF,
554+
jl_cgval_t *args, size_t nargs) {
555+
AttributeList Attrs;
556+
return emit_jlcall(ctx, theFptr, theF, args, nargs, Attrs, JLCALL_F_CC);
557+
}
552558

553559
static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p);
554560
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
21822188
strct,
21832189
mark_julia_const((jl_value_t*)name)
21842190
};
2185-
Value *result = emit_jlcall(ctx, jlgetfield_func, maybe_decay_untracked(V_null), myargs_array, 2, JLCALL_F_CC);
2191+
Value *result = emit_jlcall(ctx, jlgetfield_func, maybe_decay_untracked(V_null), myargs_array, 2);
21862192
return mark_julia_type(ctx, result, true, jl_any_type);
21872193
}
21882194

@@ -3022,7 +3028,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
30223028
}
30233029

30243030
static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF,
3025-
jl_cgval_t *argv, size_t nargs, CallingConv::ID cc)
3031+
jl_cgval_t *argv, size_t nargs,
3032+
AttributeList Attrs, CallingConv::ID cc)
30263033
{
30273034
// emit arguments
30283035
SmallVector<Value*, 3> theArgs;
@@ -3040,7 +3047,8 @@ static CallInst *emit_jlcall(jl_codectx_t &ctx, Value *theFptr, Value *theF,
30403047
CallInst *result = ctx.builder.CreateCall(FTy,
30413048
ctx.builder.CreateBitCast(prepare_call(theFptr), FTy->getPointerTo()),
30423049
theArgs);
3043-
add_return_attr(result, Attribute::NonNull);
3050+
Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::ReturnIndex, Attribute::NonNull);
3051+
result->setAttributes(Attrs);
30443052
result->setCallingConv(cc);
30453053
return result;
30463054
}
@@ -3052,7 +3060,14 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_code_instance_t
30523060
// emit specialized call site
30533061
jl_value_t *jlretty = codeinst->rettype;
30543062
jl_returninfo_t returninfo = get_specsig_function(jl_Module, specFunctionObject, codeinst->def->specTypes, jlretty);
3055-
FunctionType *cft = returninfo.decl->getFunctionType();
3063+
Function *f = returninfo.decl;
3064+
FunctionType *cft = f->getFunctionType();
3065+
if (codeinst->def->def.method->pure) {
3066+
// pure marked functions don't have side-effects, nor observe them
3067+
f->addFnAttr(Thunk);
3068+
f->addFnAttr(Attribute::ReadNone);
3069+
f->addFnAttr(Attribute::NoUnwind);
3070+
}
30563071

30573072
size_t nfargs = cft->getNumParams();
30583073
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
31163131
idx++;
31173132
}
31183133
assert(idx == nfargs);
3119-
CallInst *call = ctx.builder.CreateCall(returninfo.decl, ArrayRef<Value*>(&argvals[0], nfargs));
3120-
call->setAttributes(returninfo.decl->getAttributes());
3134+
CallInst *call = ctx.builder.CreateCall(f, ArrayRef<Value*>(&argvals[0], nfargs));
3135+
call->setAttributes(f->getAttributes());
31213136

31223137
jl_cgval_t retval;
31233138
switch (returninfo.cc) {
@@ -3157,7 +3172,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_code_instance_t
31573172
return retval;
31583173
}
31593174

3160-
static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, StringRef specFunctionObject,
3175+
static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_code_instance_t *codeinst, StringRef specFunctionObject,
31613176
jl_cgval_t *argv, size_t nargs, jl_value_t *inferred_retty)
31623177
{
31633178
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
31663181
#else
31673182
;
31683183
#endif
3169-
if (auto F = dyn_cast<Function>(theFptr->stripPointerCasts())) {
3170-
add_return_attr(F, Attribute::NonNull);
3171-
F->addFnAttr(Thunk);
3172-
}
3173-
Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, JLCALL_F_CC);
3184+
AttributeList Attrs;
3185+
auto F = dyn_cast<Function>(theFptr->stripPointerCasts());
3186+
if (F)
3187+
Attrs = F->getAttributes();
3188+
Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::ReturnIndex, Attribute::NonNull)
3189+
.addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Thunk);
3190+
if (codeinst->def->def.method->pure) {
3191+
// pure marked functions don't have side-effects, nor observe them
3192+
Attrs = Attrs.addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Attribute::ReadNone)
3193+
.addAttribute(jl_LLVMContext, AttributeList::FunctionIndex, Attribute::NoUnwind);
3194+
}
3195+
if (F)
3196+
F->setAttributes(Attrs);
3197+
Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, Attrs, JLCALL_F_CC);
31743198
return mark_julia_type(ctx, ret, true, inferred_retty);
31753199
}
31763200

@@ -3204,7 +3228,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt)
32043228
}
32053229
if (decls.functionObject) {
32063230
if (!strcmp(decls.functionObject, "jl_fptr_args")) {
3207-
result = emit_call_specfun_boxed(ctx, decls.specFunctionObject, argv, nargs, rt);
3231+
result = emit_call_specfun_boxed(ctx, codeinst, decls.specFunctionObject, argv, nargs, rt);
32083232
handled = true;
32093233
}
32103234
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)
32163240
}
32173241
}
32183242
if (!handled) {
3219-
Value *r = emit_jlcall(ctx, prepare_call(jlinvoke_func), boxed(ctx, lival), argv, nargs, JLCALL_F2_CC);
3243+
Value *r = emit_jlcall(ctx, prepare_call(jlinvoke_func), boxed(ctx, lival), argv, nargs, jlinvoke_func->getAttributes(), JLCALL_F2_CC);
32203244
result = mark_julia_type(ctx, r, true, rt);
32213245
}
32223246
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)
32573281
std::map<jl_fptr_args_t, Function*>::iterator it = builtin_func_map.find(jl_get_builtin_fptr(f.constant));
32583282
if (it != builtin_func_map.end()) {
32593283
Value *theFptr = it->second;
3260-
Value *ret = emit_jlcall(ctx, theFptr, maybe_decay_untracked(V_null), &argv[1], nargs - 1, JLCALL_F_CC);
3284+
Value *ret = emit_jlcall(ctx, theFptr, maybe_decay_untracked(V_null), &argv[1], nargs - 1);
32613285
return mark_julia_type(ctx, ret, true, rt);
32623286
}
32633287
}
32643288

32653289
// emit function and arguments
3266-
Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs, JLCALL_F_CC);
3290+
Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs);
32673291
return mark_julia_type(ctx, callval, true, rt);
32683292
}
32693293

@@ -4128,7 +4152,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
41284152
assert(nargs <= jl_datatype_nfields(jl_tparam0(ty)) + 1);
41294153
return emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, &argv[1]);
41304154
}
4131-
Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv, nargs, JLCALL_F_CC);
4155+
Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv, nargs);
41324156
// temporarily mark as `Any`, expecting `emit_ssaval_assign` to update
41334157
// it to the inferred type.
41344158
return mark_julia_type(ctx, val, true, (jl_value_t*)jl_any_type);
@@ -4325,7 +4349,7 @@ static void emit_cfunc_invalidate(
43254349
}
43264350
}
43274351
assert(AI == gf_thunk->arg_end());
4328-
Value *gf_ret = emit_jlcall(ctx, jlapplygeneric_func, nullptr, myargs, nargs, JLCALL_F_CC);
4352+
Value *gf_ret = emit_jlcall(ctx, jlapplygeneric_func, nullptr, myargs, nargs);
43294353
jl_cgval_t gf_retbox = mark_julia_type(ctx, gf_ret, true, jl_any_type);
43304354
jl_value_t *astrt = codeinst->rettype;
43314355
if (cc != jl_returninfo_t::Boxed) {
@@ -4697,11 +4721,11 @@ static Function* gen_cfun_wrapper(
46974721
// for jlcall, we need to pass the function object even if it is a ghost.
46984722
Value *theF = boxed(ctx, inputargs[0]);
46994723
assert(theF);
4700-
ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs, JLCALL_F_CC);
4724+
ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs);
47014725
ctx.builder.CreateBr(b_after);
47024726
ctx.builder.SetInsertPoint(b_generic);
47034727
}
4704-
Value *ret = emit_jlcall(ctx, prepare_call(jlapplygeneric_func), NULL, inputargs, nargs + 1, JLCALL_F_CC);
4728+
Value *ret = emit_jlcall(ctx, prepare_call(jlapplygeneric_func), NULL, inputargs, nargs + 1);
47054729
if (age_ok) {
47064730
ctx.builder.CreateBr(b_after);
47074731
ctx.builder.SetInsertPoint(b_after);
@@ -5998,7 +6022,7 @@ static std::unique_ptr<Module> emit_function(
59986022
emit_varinfo_assign(ctx, vi, tuple);
59996023
} else {
60006024
Value *vtpl = emit_jlcall(ctx, prepare_call(jltuple_func), maybe_decay_untracked(V_null),
6001-
vargs, ctx.nvargs, JLCALL_F_CC);
6025+
vargs, ctx.nvargs);
60026026
jl_cgval_t tuple = mark_julia_type(ctx, vtpl, true, vi.value.typ);
60036027
emit_varinfo_assign(ctx, vi, tuple);
60046028
}

src/julia.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ typedef struct _jl_code_info_t {
243243
// 0 = inbounds
244244
// 1,2 = <reserved> inlinehint,always-inline,noinline
245245
// 3 = <reserved> strict-ieee (strictfp)
246-
// 4-6 = <unused>
247-
// 7 = has out-of-band info
246+
// 4 = <reserved> inferred-pure
247+
// 5-6 = <unused>
248+
// 7 = <reserved> has out-of-band info
248249
// miscellaneous data:
249250
jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference
250251
jl_value_t *linetable; // Table of locations [TODO: make this volatile like slotnames]

src/llvm-late-gc-lowering.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,16 +1160,20 @@ State LateLowerGCFrame::LocalScan(Function &F) {
11601160
callee == write_barrier_func || callee->getName() == "memcmp") {
11611161
continue;
11621162
}
1163-
if (callee->hasFnAttribute(Attribute::ReadNone) ||
1164-
callee->hasFnAttribute(Attribute::ReadOnly) ||
1165-
callee->hasFnAttribute(Attribute::ArgMemOnly)) {
1166-
continue;
1163+
if (!callee->hasFnAttribute("thunk")) {
1164+
if (callee->hasFnAttribute(Attribute::ReadNone) ||
1165+
callee->hasFnAttribute(Attribute::ReadOnly) ||
1166+
callee->hasFnAttribute(Attribute::ArgMemOnly)) {
1167+
continue;
1168+
}
11671169
}
11681170
}
1169-
if (isa<IntrinsicInst>(CI) || CI->hasFnAttr(Attribute::ArgMemOnly) ||
1170-
CI->hasFnAttr(Attribute::ReadNone) || CI->hasFnAttr(Attribute::ReadOnly)) {
1171-
// Intrinsics are never safepoints.
1172-
continue;
1171+
if (!CI->hasFnAttr("thunk")) {
1172+
if (isa<IntrinsicInst>(CI) || CI->hasFnAttr(Attribute::ArgMemOnly) ||
1173+
CI->hasFnAttr(Attribute::ReadNone) || CI->hasFnAttr(Attribute::ReadOnly)) {
1174+
// Intrinsics are never safepoints.
1175+
continue;
1176+
}
11731177
}
11741178
int SafepointNumber = NoteSafepoint(S, BBS, CI);
11751179
BBS.HasSafepoint = true;

test/llvmpasses/noinline.jl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
# RUN: julia --startup-file=no %s %t && llvm-link -S %t/* -o %t/module.ll
3+
# RUN: julia -g0 --startup-file=no %s %t && llvm-link -S %t/* -o %t/module.ll
44
# RUN: cat %t/module.ll | FileCheck %s
55

66
## Notes:
@@ -12,10 +12,27 @@
1212

1313
include(joinpath("..", "testhelpers", "llvmpasses.jl"))
1414

15-
# CHECK-LABEL: @julia_simple_noinline
1615
@noinline function simple_noinline(A, B)
1716
return A + B
1817
end
1918

20-
# CHECK: attributes #{{[0-9]+}} = {{{([a-z]+ )*}} noinline {{([a-z]+ )*}}}
19+
@noinline Base.@pure function simple_pure_helper(A, B)
20+
return A + B
21+
end
22+
function simple_pure(A, B)
23+
return simple_pure_helper(A, B)
24+
end
25+
26+
# CHECK: define double @julia_simple_noinline_{{[0-9]+}}(double, double) #[[NOINLINE:[0-9]+]] {
2127
emit(simple_noinline, Float64, Float64)
28+
# CHECK-LABEL: @julia_simple_pure
29+
# CHECK: call double @julia_simple_pure_helper_{{[0-9]+}}(double %0, double %1) #[[PURE:[0-9]+]]
30+
# CHECK: declare double @julia_simple_pure_helper_{{[0-9]+}}(double, double) #[[PURE]]
31+
emit(simple_pure, Float64, Float64)
32+
# CHECK-LABEL @japi1_simple_pure
33+
# CHECK: call cc37 {{.+}} @japi1_simple_pure_helper_{{.+}} #[[PURE]]
34+
# 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]+]]
35+
emit(simple_pure, BigFloat, BigFloat)
36+
37+
# CHECK: attributes #[[NOINLINE]] = {{{([a-z]+ )*}} noinline {{([a-z]+ )*}}}
38+
# CHECK: attributes #[[PURE]] = { nounwind readnone "thunk" }

0 commit comments

Comments
 (0)