diff --git a/NEWS.md b/NEWS.md index 01733579fe5bf1..29c9878db3af7e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,10 @@ New language features * Support for Unicode 13.0.0 (via utf8proc 2.5) ([#35282]). +* The compiler optimization level can now be set per-module using the experimental macro + `Base.Experimental.@optlevel n`. For code that is not performance-critical, setting + this to 0 or 1 can provide significant latency improvements ([#34896]). + Language changes ---------------- diff --git a/base/experimental.jl b/base/experimental.jl index a4b3da0831b301..910deb3f6fcac3 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -100,5 +100,20 @@ macro sync(block) end end +""" + Experimental.@optlevel n::Int + +Set the optimization level (equivalent to the `-O` command line argument) +for code in the current module. Submodules inherit the setting of their +parent module. + +Supported values are 0, 1, 2, and 3. + +The effective optimization level is the minimum of that specified on the +command line and in per-module settings. +""" +macro optlevel(n::Int) + return Expr(:meta, :optlevel, n) +end end diff --git a/src/ast.c b/src/ast.c index 982c96f614bc93..e9cbb4373c2256 100644 --- a/src/ast.c +++ b/src/ast.c @@ -62,6 +62,7 @@ jl_sym_t *throw_undef_if_not_sym; jl_sym_t *getfield_undefref_sym; jl_sym_t *gc_preserve_begin_sym; jl_sym_t *gc_preserve_end_sym; jl_sym_t *coverageeffect_sym; jl_sym_t *escape_sym; jl_sym_t *aliasscope_sym; jl_sym_t *popaliasscope_sym; +jl_sym_t *optlevel_sym; static uint8_t flisp_system_image[] = { #include @@ -380,6 +381,7 @@ void jl_init_frontend(void) isdefined_sym = jl_symbol("isdefined"); nospecialize_sym = jl_symbol("nospecialize"); specialize_sym = jl_symbol("specialize"); + optlevel_sym = jl_symbol("optlevel"); macrocall_sym = jl_symbol("macrocall"); escape_sym = jl_symbol("escape"); hygienicscope_sym = jl_symbol("hygienic-scope"); diff --git a/src/codegen.cpp b/src/codegen.cpp index 047ef93e0f701e..62f42a56ad863f 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5242,6 +5242,14 @@ static std::pair, jl_llvm_functions_t> #ifdef JL_DEBUG_BUILD f->addFnAttr(Attribute::StackProtectStrong); #endif + + // add the optimization level specified for this module, if any + int optlevel = jl_get_module_optlevel(ctx.module); + if (optlevel >= 0 && optlevel <= 3) { + static const char* const optLevelStrings[] = { "0", "1", "2", "3" }; + f->addFnAttr("julia-optimization-level", optLevelStrings[optlevel]); + } + ctx.f = f; // Step 4b. determine debug info signature and other type info for locals @@ -7458,14 +7466,7 @@ extern "C" void jl_init_llvm(void) #else None; #endif - auto optlevel = -#ifdef DISABLE_OPT - CodeGenOpt::None; -#else - (jl_options.opt_level < 2 ? CodeGenOpt::None : - jl_options.opt_level == 2 ? CodeGenOpt::Default : - CodeGenOpt::Aggressive); -#endif + auto optlevel = CodeGenOptLevelFor(jl_options.opt_level); jl_TargetMachine = TheTarget->createTargetMachine( TheTriple.getTriple(), TheCPU, FeaturesStr, options, diff --git a/src/dump.c b/src/dump.c index e42b996a2e2b40..822083888c34f9 100644 --- a/src/dump.c +++ b/src/dump.c @@ -479,6 +479,7 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) write_uint64(s->s, m->build_id); write_int32(s->s, m->counter); write_int32(s->s, m->nospecialize); + write_int32(s->s, m->optlevel); } static int is_ir_node(jl_value_t *v) @@ -1915,6 +1916,7 @@ static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DIS m->build_id = read_uint64(s->s); m->counter = read_int32(s->s); m->nospecialize = read_int32(s->s); + m->optlevel = read_int32(s->s); m->primary_world = jl_world_counter; return (jl_value_t*)m; } diff --git a/src/interpreter.c b/src/interpreter.c index 016e2304691142..0e7f00cf4a245a 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -798,6 +798,12 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, if (jl_expr_nargs(stmt) == 1 && jl_exprarg(stmt, 0) == (jl_value_t*)specialize_sym) { jl_set_module_nospecialize(s->module, 0); } + if (jl_expr_nargs(stmt) == 2 && jl_exprarg(stmt, 0) == (jl_value_t*)optlevel_sym) { + if (jl_is_long(jl_exprarg(stmt, 1))) { + int n = jl_unbox_long(jl_exprarg(stmt, 1)); + jl_set_module_optlevel(s->module, n); + } + } } else { eval_stmt_value(stmt, s); diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 603bb5b2e4f642..7340a9805a9e9f 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -436,11 +436,61 @@ void JuliaOJIT::DebugObjectRegistrar::operator()(RTDyldObjHandleT H, static_cast(&LOS)); } +CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) +{ +#ifdef DISABLE_OPT + return CodeGenOpt::None; +#else + return optlevel < 2 ? CodeGenOpt::None : + optlevel == 2 ? CodeGenOpt::Default : + CodeGenOpt::Aggressive; +#endif +} + +static void addPassesForOptLevel(legacy::PassManager &PM, TargetMachine &TM, raw_svector_ostream &ObjStream, MCContext *Ctx, int optlevel) +{ + auto oldlevel = TM.getOptLevel(); + TM.setOptLevel(CodeGenOptLevelFor(optlevel)); + addTargetPasses(&PM, &TM); + addOptimizationPasses(&PM, optlevel); + if (TM.addPassesToEmitMC(PM, Ctx, ObjStream)) + llvm_unreachable("Target does not support MC emission."); + TM.setOptLevel(oldlevel); +} CompilerResultT JuliaOJIT::CompilerT::operator()(Module &M) { JL_TIMING(LLVM_OPT); - jit.PM.run(M); + int optlevel; + if (jl_generating_output()) { + optlevel = 0; + } + else { + optlevel = jl_options.opt_level; + for (auto &F : M.functions()) { + if (!F.getBasicBlockList().empty()) { + Attribute attr = F.getFnAttribute("julia-optimization-level"); + StringRef val = attr.getValueAsString(); + if (val != "") { + int ol = (int)val[0] - '0'; + if (ol >= 0 && ol < optlevel) + optlevel = ol; + } + } + } + } + auto oldlevel = jit.TM.getOptLevel(); + jit.TM.setOptLevel(CodeGenOptLevelFor(optlevel)); + if (optlevel == 0) + jit.PM0.run(M); + else if (optlevel == 1) + jit.PM1.run(M); + else if (optlevel == 2) + jit.PM2.run(M); + else if (optlevel >= 3) + jit.PM3.run(M); + jit.TM.setOptLevel(oldlevel); + std::unique_ptr ObjBuffer( new SmallVectorMemoryBuffer(std::move(jit.ObjBufferSV))); auto Obj = object::ObjectFile::createObjectFile(ObjBuffer->getMemBufferRef()); @@ -490,10 +540,10 @@ JuliaOJIT::JuliaOJIT(TargetMachine &TM) CompilerT(this) ) { - addTargetPasses(&PM, &TM); - addOptimizationPasses(&PM, jl_generating_output() ? 0 : jl_options.opt_level); - if (TM.addPassesToEmitMC(PM, Ctx, ObjStream)) - llvm_unreachable("Target does not support MC emission."); + addPassesForOptLevel(PM0, TM, ObjStream, Ctx, 0); + addPassesForOptLevel(PM1, TM, ObjStream, Ctx, 1); + addPassesForOptLevel(PM2, TM, ObjStream, Ctx, 2); + addPassesForOptLevel(PM3, TM, ObjStream, Ctx, 3); // Make sure SectionMemoryManager::getSymbolAddressInProcess can resolve // symbols in the program as well. The nullptr argument to the function diff --git a/src/jitlayers.h b/src/jitlayers.h index 773afbe60ec9ef..e789e9d1394ba0 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -212,7 +212,10 @@ class JuliaOJIT { // object fits in its entirety SmallVector ObjBufferSV; raw_svector_ostream ObjStream; - legacy::PassManager PM; + legacy::PassManager PM0; // per-optlevel pass managers + legacy::PassManager PM1; + legacy::PassManager PM2; + legacy::PassManager PM3; MCContext *Ctx; std::shared_ptr MemMgr; DebugObjectRegistrar registrar; @@ -243,3 +246,5 @@ static inline bool isIntrinsicFunction(Function *F) { return F->isIntrinsic() || F->getName().startswith("julia."); } + +CodeGenOpt::Level CodeGenOptLevelFor(int optlevel); diff --git a/src/julia.h b/src/julia.h index 71aababc44ec1d..ce08c71feaaffd 100644 --- a/src/julia.h +++ b/src/julia.h @@ -495,6 +495,7 @@ typedef struct _jl_module_t { size_t primary_world; uint32_t counter; int32_t nospecialize; // global bit flags: initialization for new methods + int32_t optlevel; uint8_t istopmod; jl_mutex_t lock; } jl_module_t; @@ -1454,6 +1455,8 @@ extern JL_DLLEXPORT jl_module_t *jl_base_module JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_top_module JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name); JL_DLLEXPORT void jl_set_module_nospecialize(jl_module_t *self, int on); +JL_DLLEXPORT void jl_set_module_optlevel(jl_module_t *self, int lvl); +JL_DLLEXPORT int jl_get_module_optlevel(jl_module_t *m); // get binding for reading JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); diff --git a/src/julia_internal.h b/src/julia_internal.h index 02149a49d85865..0e95c0c8ef76e4 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1161,6 +1161,7 @@ extern jl_sym_t *throw_undef_if_not_sym; extern jl_sym_t *getfield_undefref_sym; extern jl_sym_t *gc_preserve_begin_sym; extern jl_sym_t *gc_preserve_end_sym; extern jl_sym_t *failed_sym; extern jl_sym_t *done_sym; extern jl_sym_t *runnable_sym; extern jl_sym_t *coverageeffect_sym; extern jl_sym_t *escape_sym; +extern jl_sym_t *optlevel_sym; struct _jl_sysimg_fptrs_t; diff --git a/src/module.c b/src/module.c index 33e7afedc16967..2a6ea42f9ec3c0 100644 --- a/src/module.c +++ b/src/module.c @@ -35,6 +35,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name) m->primary_world = 0; m->counter = 1; m->nospecialize = 0; + m->optlevel = -1; JL_MUTEX_INIT(&m->lock); htable_new(&m->bindings, 0); arraylist_new(&m->usings, 0); @@ -72,6 +73,21 @@ JL_DLLEXPORT void jl_set_module_nospecialize(jl_module_t *self, int on) self->nospecialize = (on ? -1 : 0); } +JL_DLLEXPORT void jl_set_module_optlevel(jl_module_t *self, int lvl) +{ + self->optlevel = lvl; +} + +JL_DLLEXPORT int jl_get_module_optlevel(jl_module_t *m) +{ + int lvl = m->optlevel; + while (lvl == -1 && m->parent != m && m != jl_base_module) { + m = m->parent; + lvl = m->optlevel; + } + return lvl; +} + JL_DLLEXPORT void jl_set_istopmod(jl_module_t *self, uint8_t isprimary) { self->istopmod = 1; diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 367889d029fe24..9556b9c9a5d305 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -2,6 +2,8 @@ module InteractiveUtils +Base.Experimental.@optlevel 1 + export apropos, edit, less, code_warntype, code_llvm, code_native, methodswith, varinfo, versioninfo, subtypes, supertypes, @which, @edit, @less, @functionloc, @code_warntype, @code_typed, @code_lowered, @code_llvm, @code_native, clipboard diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 765ce011f6083a..657b2ca4c9e338 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -13,6 +13,8 @@ Run Evaluate Print Loop (REPL) """ module REPL +Base.Experimental.@optlevel 1 + using Base.Meta, Sockets import InteractiveUtils