From 3bce3eaabe609b7979fcc5c339946f6df267294e Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 7 Dec 2013 00:18:13 -0800 Subject: [PATCH 1/4] Add back llvmcall --- base/boot.jl | 2 +- base/inference.jl | 4 ++ src/ccall.cpp | 132 +++++++++++++++++++++++++++++++++++++++++++++ src/intrinsics.cpp | 4 +- 4 files changed, 140 insertions(+), 2 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 1fcf1b149a7e5..72d74827a1129 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -147,7 +147,7 @@ export JULIA_HOME, nothing, Main, # intrinsics module Intrinsics - #ccall, cglobal, abs_float, add_float, add_int, and_int, ashr_int, + #ccall, cglobal, llvmcall, abs_float, add_float, add_int, and_int, ashr_int, #box, bswap_int, checked_fptosi, checked_fptoui, checked_sadd, #checked_smul, checked_ssub, checked_uadd, checked_umul, checked_usub, #checked_trunc_sint, checked_trunc_uint, diff --git a/base/inference.jl b/base/inference.jl index fa9b58a446cb4..43b5fbc42ed2c 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -115,6 +115,10 @@ t_func[nan_dom_err] = (2, 2, (a, b)->a) t_func[eval(Core.Intrinsics,:ccall)] = (3, Inf, (fptr, rt, at, a...)->(is(rt,Type{Void}) ? Nothing : isType(rt) ? rt.parameters[1] : Any)) +t_func[eval(Core.Intrinsics,:llvmcall)] = + (3, Inf, (fptr, rt, at, a...)->(is(rt,Type{Void}) ? Nothing : + isType(rt) ? rt.parameters[1] : + isa(rt,Tuple) ? map(x->x.parameters[1],rt) : Any)) t_func[eval(Core.Intrinsics,:cglobal)] = (1, 2, (fptr, t...)->(isempty(t) ? Ptr{Void} : isType(t[1]) ? Ptr{t[1].parameters[1]} : Ptr)) diff --git a/src/ccall.cpp b/src/ccall.cpp index 992399b77922a..2769a616377b2 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -568,6 +568,138 @@ static Value *emit_cglobal(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) return mark_julia_type(res, rt); } +// llvmcall(ir, (rettypes...), (argtypes...), args...) +static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) +{ + JL_NARGSV(llvmcall, 3) + jl_value_t *rt = NULL, *at = NULL; + JL_GC_PUSH2(&rt, &at); + { + JL_TRY { + at = jl_interpret_toplevel_expr_in(ctx->module, args[3], + &jl_tupleref(ctx->sp,0), + jl_tuple_len(ctx->sp)/2); + } + JL_CATCH { + jl_rethrow_with_add("error interpreting llvmcall return type"); + } + } + { + JL_TRY { + rt = jl_interpret_toplevel_expr_in(ctx->module, args[2], + &jl_tupleref(ctx->sp,0), + jl_tuple_len(ctx->sp)/2); + } + JL_CATCH { + jl_rethrow_with_add("error interpreting ccall argument tuple"); + } + } + JL_TYPECHK(llvmcall, type, rt); + JL_TYPECHK(llvmcall, tuple, at); + JL_TYPECHK(llvmcall, type, at); + int i = 1; + // Make sure to find a unique name + std::string ir_name; + while(true) { + std::stringstream name; + name << (ctx->f->getName().str()) << i++; + ir_name = name.str(); + if(jl_Module->getFunction(ir_name) == NULL) + break; + } + jl_value_t *ir = static_eval(args[1], ctx, true); + if (!jl_is_byte_string(ir)) + { + jl_error("First argument to llvmcall must be a string"); + } + + std::stringstream ir_stream; + + // Generate arguments + std::string arguments; + llvm::raw_string_ostream argstream(arguments); + jl_tuple_t *tt = (jl_tuple_t*)at; + size_t nargt = jl_tuple_len(tt); + Value *argvals[nargt]; + /* + * Semantics for arguments are as follows: + * If the argument type is immutable (including bitstype), we pass the loaded llvm value + * type. Otherwise we pass a pointer to a jl_value_t. + */ + for (size_t i = 0; i < nargt; ++i) + { + jl_value_t *tti = jl_tupleref(tt,i); + Type *t = julia_type_to_llvm(tti); + t->print(argstream); + argstream << " "; + jl_value_t *argi = args[4+i]; + Value *arg; + bool needroot = false; + if (t == jl_pvalue_llvmt || !jl_isbits(tti)) { + arg = emit_expr(argi, ctx, true); + if (t == jl_pvalue_llvmt && arg->getType() != jl_pvalue_llvmt) { + arg = boxed(arg, ctx); + needroot = true; + } + } + else { + arg = emit_unboxed(argi, ctx); + if (jl_is_bitstype(expr_type(argi, ctx))) { + arg = emit_unbox(t, arg, tti); + } + } + +#ifdef JL_GC_MARKSWEEP + // make sure args are rooted + if (t == jl_pvalue_llvmt && (needroot || might_need_root(argi))) { + make_gcroot(arg, ctx); + } +#endif + bool mightNeedTempSpace = false; + argvals[i] = julia_to_native(t,tti,arg,argi,false,i,ctx,&mightNeedTempSpace,&mightNeedTempSpace); + if ((i+1) != nargt) + argstream << ","; + } + + Type *rettype; + std::string rstring; + llvm::raw_string_ostream rtypename(rstring); + // Construct return type + rettype = julia_type_to_llvm(rt); + rettype->print(rtypename); + + ir_stream << "; Number of arguments: " << nargt << "\n" + << "define "<getFunction(ir_name); + /* + * It might be tempting to just try to set the Always inline attribute on the function + * and hope for the best. However, this doesn't work since that would require an inlining + * pass (which is a Call Graph pass and cannot be managed by a FunctionPassManager). Instead + * We are sneaky and call the inliner directly. This however doesn't work until we've actually + * generated the entire function, so we need to store it in the context until the end of the + * function. This also has the benefit of looking exactly like we cut/pasted it in in `code_llvm`. + */ + f->setLinkage(GlobalValue::LinkOnceODRLinkage); + + // the actual call + CallInst *inst = builder.CreateCall(f,ArrayRef(&argvals[0],nargt)); + ctx->to_inline.push_back(inst); + + JL_GC_POP(); + + return mark_julia_type(inst,rt); +} + // --- code generator for ccall itself --- // ccall(pointer, rettype, (argtypes...), args...) diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index cd0fb72a4830e..26ed20b72c53d 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -39,7 +39,7 @@ namespace JL_I { // pointer access pointerref, pointerset, pointertoref, // c interface - ccall, cglobal, jl_alloca + ccall, cglobal, jl_alloca, llvmcall }; }; @@ -820,6 +820,7 @@ static Value *emit_intrinsic(intrinsic f, jl_value_t **args, size_t nargs, switch (f) { case ccall: return emit_ccall(args, nargs, ctx); case cglobal: return emit_cglobal(args, nargs, ctx); + case llvmcall: return emit_llvmcall(args, nargs, ctx); HANDLE(box,2) return generic_box(args[1], args[2], ctx); HANDLE(unbox,2) return generic_unbox(args[1], args[2], ctx); @@ -1438,4 +1439,5 @@ extern "C" void jl_init_intrinsic_functions(void) ADD_I(nan_dom_err); ADD_I(ccall); ADD_I(cglobal); ADD_I(jl_alloca); + ADD_I(llvmcall); } From b286ff0798ce41ff29a510479d7ba2dc2b94afc9 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 18 Jul 2014 11:31:55 -0700 Subject: [PATCH 2/4] Import update llvmcall from cxx branch --- src/ccall.cpp | 169 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 120 insertions(+), 49 deletions(-) diff --git a/src/ccall.cpp b/src/ccall.cpp index 2769a616377b2..5b1a5ea319e33 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -572,8 +572,8 @@ static Value *emit_cglobal(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) { JL_NARGSV(llvmcall, 3) - jl_value_t *rt = NULL, *at = NULL; - JL_GC_PUSH2(&rt, &at); + jl_value_t *rt = NULL, *at = NULL, *ir = NULL; + JL_GC_PUSH3(&ir, &rt, &at); { JL_TRY { at = jl_interpret_toplevel_expr_in(ctx->module, args[3], @@ -591,36 +591,52 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) jl_tuple_len(ctx->sp)/2); } JL_CATCH { - jl_rethrow_with_add("error interpreting ccall argument tuple"); + jl_rethrow_with_add("error interpreting llvmcall argument tuple"); + } + } + { + JL_TRY { + ir = jl_interpret_toplevel_expr_in(ctx->module, args[1], + &jl_tupleref(ctx->sp,0), + jl_tuple_len(ctx->sp)/2); + } + JL_CATCH { + jl_rethrow_with_add("error interpreting IR argument"); } } - JL_TYPECHK(llvmcall, type, rt); - JL_TYPECHK(llvmcall, tuple, at); - JL_TYPECHK(llvmcall, type, at); int i = 1; - // Make sure to find a unique name - std::string ir_name; - while(true) { - std::stringstream name; - name << (ctx->f->getName().str()) << i++; - ir_name = name.str(); - if(jl_Module->getFunction(ir_name) == NULL) - break; + if (ir == NULL) { + jl_error("Cannot statically evaluate first argument to llvmcall"); } - jl_value_t *ir = static_eval(args[1], ctx, true); - if (!jl_is_byte_string(ir)) + bool isString = jl_is_byte_string(ir); + bool isPtr = jl_is_cpointer(ir); + if (!isString && !isPtr) { - jl_error("First argument to llvmcall must be a string"); + jl_error("First argument to llvmcall must be a string or pointer to an LLVM Function"); } + JL_TYPECHK(llvmcall, type, rt); + JL_TYPECHK(llvmcall, tuple, at); + JL_TYPECHK(llvmcall, type, at); + std::stringstream ir_stream; + jl_tuple_t *stt = jl_alloc_tuple(nargs - 3); + + for (size_t i = 0; i < nargs-3; ++i) + { + jl_tupleset(stt,i,expr_type(args[4+i],ctx)); + } + // Generate arguments std::string arguments; llvm::raw_string_ostream argstream(arguments); jl_tuple_t *tt = (jl_tuple_t*)at; + jl_value_t *rtt = rt; + size_t nargt = jl_tuple_len(tt); Value *argvals[nargt]; + std::vector argtypes; /* * Semantics for arguments are as follows: * If the argument type is immutable (including bitstype), we pass the loaded llvm value @@ -630,8 +646,11 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) { jl_value_t *tti = jl_tupleref(tt,i); Type *t = julia_type_to_llvm(tti); - t->print(argstream); - argstream << " "; + argtypes.push_back(t); + if (4+i > nargs) + { + jl_error("Missing arguments to llvmcall!"); + } jl_value_t *argi = args[4+i]; Value *arg; bool needroot = false; @@ -657,47 +676,99 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) #endif bool mightNeedTempSpace = false; argvals[i] = julia_to_native(t,tti,arg,argi,false,i,ctx,&mightNeedTempSpace,&mightNeedTempSpace); - if ((i+1) != nargt) - argstream << ","; - } - - Type *rettype; - std::string rstring; - llvm::raw_string_ostream rtypename(rstring); - // Construct return type - rettype = julia_type_to_llvm(rt); - rettype->print(rtypename); - - ir_stream << "; Number of arguments: " << nargt << "\n" - << "define "<getFunction(ir_name); + } + + Function *f; + Type *rettype = julia_type_to_llvm(rtt); + if (isString) { + // Make sure to find a unique name + std::string ir_name; + while(true) { + std::stringstream name; + name << (ctx->f->getName().str()) << i++; + ir_name = name.str(); + if(jl_Module->getFunction(ir_name) == NULL) + break; + } + + bool first = true; + for (std::vector::iterator it = argtypes.begin(); it != argtypes.end(); ++it) { + if(!first) + argstream << ","; + else + first = false; + (*it)->print(argstream); + argstream << " "; + } + + std::string rstring; + llvm::raw_string_ostream rtypename(rstring); + rettype->print(rtypename); + + ir_stream << "; Number of arguments: " << nargt << "\n" + << "define "<getFunction(ir_name); + } else { + assert(isPtr); + // Create Function sceleton + f = (llvm::Function*)jl_unbox_voidpointer(ir); + assert(f->getReturnType() == rettype); + int i = 0; + for (std::vector::iterator it = argtypes.begin(); + it != argtypes.end(); ++it, ++i) + assert(*it == f->getFunctionType()->getParamType(i)); + + if (f->getParent() != jl_Module) + { + FunctionMover mover(jl_Module,f->getParent()); + f = (llvm::Function*)MapValue(f,mover.VMap,RF_None,NULL,&mover); + } + + //f->dump(); + #ifndef LLVM35 + if (verifyFunction(*f,PrintMessageAction)) { + #else + llvm::raw_fd_ostream out(1,false); + if (verifyFunction(*f,&out)) + { + #endif + f->dump(); + jl_error("Malformed LLVM Function"); + } + } + /* * It might be tempting to just try to set the Always inline attribute on the function * and hope for the best. However, this doesn't work since that would require an inlining * pass (which is a Call Graph pass and cannot be managed by a FunctionPassManager). Instead - * We are sneaky and call the inliner directly. This however doesn't work until we've actually - * generated the entire function, so we need to store it in the context until the end of the - * function. This also has the benefit of looking exactly like we cut/pasted it in in `code_llvm`. + * We are sneaky and call the inliner directly. This however doesn't work until we've actually + * generated the entire function, so we need to store it in the context until the end of the + * function. This also has the benefit of looking exactly like we cut/pasted it in in `code_llvm`. */ f->setLinkage(GlobalValue::LinkOnceODRLinkage); - + // the actual call - CallInst *inst = builder.CreateCall(f,ArrayRef(&argvals[0],nargt)); + CallInst *inst = builder.CreateCall(prepare_call(f),ArrayRef(&argvals[0],nargt)); ctx->to_inline.push_back(inst); JL_GC_POP(); - return mark_julia_type(inst,rt); + if(inst->getType() != rettype) + { + jl_error("Return type of llvmcall'ed function does not match declared return type"); + } + + return mark_julia_type(inst,rtt); } // --- code generator for ccall itself --- From fd47e6da58f07d7f367a79d103be1db6bcb10fde Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 18 Jul 2014 12:43:11 -0700 Subject: [PATCH 3/4] Fix build for non-MCJIT versions of Julia & Add tests --- src/ccall.cpp | 5 ++++- src/codegen.cpp | 1 + test/Makefile | 2 +- test/llvmcall.jl | 33 +++++++++++++++++++++++++++++++++ test/runtests.jl | 2 +- 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 test/llvmcall.jl diff --git a/src/ccall.cpp b/src/ccall.cpp index 5b1a5ea319e33..9c4dbf0d2a88f 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -571,6 +571,7 @@ static Value *emit_cglobal(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) // llvmcall(ir, (rettypes...), (argtypes...), args...) static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) { + JL_NARGSV(llvmcall, 3) jl_value_t *rt = NULL, *at = NULL, *ir = NULL; JL_GC_PUSH3(&ir, &rt, &at); @@ -706,7 +707,7 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) rettype->print(rtypename); ir_stream << "; Number of arguments: " << nargt << "\n" - << "define "<getFunctionType()->getParamType(i)); +#ifdef USE_MCJIT if (f->getParent() != jl_Module) { FunctionMover mover(jl_Module,f->getParent()); f = (llvm::Function*)MapValue(f,mover.VMap,RF_None,NULL,&mover); } +#endif //f->dump(); #ifndef LLVM35 diff --git a/src/codegen.cpp b/src/codegen.cpp index 44d6f19fbe720..f31113823e8da 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -45,6 +45,7 @@ #include "llvm/Target/TargetMachine.h" #else #include "llvm/Analysis/Verifier.h" +#include "llvm/Assembly/Parser.h" #endif #include "llvm/DebugInfo/DIContext.h" #if defined(LLVM_VERSION_MAJOR) && LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR >= 4 diff --git a/test/Makefile b/test/Makefile index 1355d46d9b03a..d10bed65ec9b8 100644 --- a/test/Makefile +++ b/test/Makefile @@ -8,7 +8,7 @@ TESTS = all core keywordargs numbers strings unicode collections hashing \ git pkg resolve suitesparse complex version pollfd mpfr broadcast \ socket floatapprox priorityqueue readdlm regex float16 combinatorics \ sysinfo rounding ranges mod2pi euler show lineedit \ - replcompletions backtrace repl test goto + replcompletions backtrace repl test goto llvmcall default: all diff --git a/test/llvmcall.jl b/test/llvmcall.jl new file mode 100644 index 0000000000000..6c7790e4b7b73 --- /dev/null +++ b/test/llvmcall.jl @@ -0,0 +1,33 @@ +using Base.llvmcall + +function add1234(x::(Int32,Int32,Int32,Int32)) + llvmcall("""%3 = add <4 x i32> %1, %0 + ret <4 x i32> %3""",(Int32,Int32,Int32,Int32), + ((Int32,Int32,Int32,Int32),(Int32,Int32,Int32,Int32)), + (int32(1),int32(2),int32(3),int32(4)), + x) +end + +function add1234(x::NTuple{4,Int64}) + llvmcall("""%3 = add <4 x i64> %1, %0 + ret <4 x i64> %3""",NTuple{4,Int64}, + (NTuple{4,Int64},NTuple{4,Int64}), + (int64(1),int64(2),int64(3),int64(4)), + x) +end + +@test add1234(map(int32,(2,3,4,5))) === map(int32,(3,5,7,9)) +@test add1234(map(int64,(2,3,4,5))) === map(int64,(3,5,7,9)) + +# Test whether llvmcall escapes the function name correctly +baremodule PlusTest + using Base.llvmcall + using Base.Test + using Base + + function +(x::Int32, y::Int32) + llvmcall("""%3 = add i32 %1, %0 + ret i32 %3""", Int32, (Int32, Int32), x, y) + end + @test int32(1)+int32(2)==int32(3) +end diff --git a/test/runtests.jl b/test/runtests.jl index 8913b083a72cc..61ae742a98ceb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ testnames = [ "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", "floatapprox", "readdlm", "regex", "float16", "combinatorics", "sysinfo", "rounding", "ranges", "mod2pi", "euler", "show", - "lineedit", "replcompletions", "repl", "test", "examples", "goto" + "lineedit", "replcompletions", "repl", "test", "examples", "goto", "llvmcall" ] @unix_only push!(testnames, "unicode") From 38bb525e297684e2a55f599403f7d825bdb3c744 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 12 Aug 2014 00:49:36 -0400 Subject: [PATCH 4/4] Typo and whitespace fix --- src/ccall.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ccall.cpp b/src/ccall.cpp index 9c4dbf0d2a88f..72f41bb05a0d2 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -638,7 +638,7 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) size_t nargt = jl_tuple_len(tt); Value *argvals[nargt]; std::vector argtypes; - /* + /* * Semantics for arguments are as follows: * If the argument type is immutable (including bitstype), we pass the loaded llvm value * type. Otherwise we pass a pointer to a jl_value_t. @@ -688,7 +688,7 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) std::stringstream name; name << (ctx->f->getName().str()) << i++; ir_name = name.str(); - if(jl_Module->getFunction(ir_name) == NULL) + if(jl_Module->getFunction(ir_name) == NULL) break; } @@ -721,7 +721,7 @@ static Value *emit_llvmcall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) f = m->getFunction(ir_name); } else { assert(isPtr); - // Create Function sceleton + // Create Function skeleton f = (llvm::Function*)jl_unbox_voidpointer(ir); assert(f->getReturnType() == rettype); int i = 0;