Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compiler flags -Os and -Oz to optimize binary size #14463

Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions man/crystal.1
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,10 @@ Low optimization
Middle optimization
.It Fl O3
High optimization
.It Fl \!Os
Middle optimization with focus on file size
.It Fl Oz
Middle optimization aggressively focused on file size
.
.Sh ENVIRONMENT\ VARIABLES
.Bl -tag -width "12345678" -compact
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/crystal/codegen/target.cr
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ class Crystal::Codegen::Target
end

opt_level = case optimization_mode
in .o3? then LLVM::CodeGenOptLevel::Aggressive
in .o2? then LLVM::CodeGenOptLevel::Default
in .o1? then LLVM::CodeGenOptLevel::Less
in .o0? then LLVM::CodeGenOptLevel::None
in .o3? then LLVM::CodeGenOptLevel::Aggressive
in .o2?, .os?, .oz? then LLVM::CodeGenOptLevel::Default
in .o1? then LLVM::CodeGenOptLevel::Less
in .o0? then LLVM::CodeGenOptLevel::None
end

target = LLVM::Target.from_triple(self.to_s)
Expand Down
17 changes: 12 additions & 5 deletions src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,12 @@ class Crystal::Command
opts.on("--release", "Compile in release mode (-O3 --single-module)") do
compiler.release!
end
opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level|
optimization_mode = level.to_i?.try { |v| Compiler::OptimizationMode.from_value?(v) }
compiler.optimization_mode = optimization_mode || raise Error.new("Invalid optimization mode: #{level}")
opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3, s, z") do |level|
if mode = Compiler::OptimizationMode.from_level?(level)
compiler.optimization_mode = mode
else
raise Error.new("Unknown optimization mode #{level}")
end
end
end

Expand Down Expand Up @@ -661,8 +664,12 @@ class Crystal::Command
opts.on("--release", "Compile in release mode (-O3 --single-module)") do
compiler.release!
end
opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3") do |level|
compiler.optimization_mode = Compiler::OptimizationMode.from_value?(level.to_i) || raise Error.new("Unknown optimization mode #{level}")
opts.on("-O LEVEL", "Optimization mode: 0 (default), 1, 2, 3, s, z") do |level|
if mode = Compiler::OptimizationMode.from_level?(level)
compiler.optimization_mode = mode
else
raise Error.new("Unknown optimization mode #{level}")
end
end
opts.on("--single-module", "Generate a single LLVM module") do
compiler.single_module = true
Expand Down
61 changes: 46 additions & 15 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,27 @@ module Crystal
# enables with --release flag
O3 = 3

# optimize for size, enables most O2 optimizations but aims for smaller
# code size
Os

# optimize aggressively for size rather than speed
Oz

def suffix
".#{to_s.downcase}"
end

def self.from_level?(level : String) : self?
case level
when "0" then O0
when "1" then O1
when "2" then O2
when "3" then O3
when "s" then Os
when "z" then Oz
end
end
end

# Sets the Optimization mode.
Expand Down Expand Up @@ -674,8 +692,8 @@ module Crystal
exit 1
end

{% if LibLLVM::IS_LT_130 %}
protected def optimize(llvm_mod)
{% if LibLLVM::IS_LT_170 %}
private def optimize_with_pass_manager(llvm_mod)
fun_pass_manager = llvm_mod.new_function_pass_manager
pass_manager_builder.populate fun_pass_manager
fun_pass_manager.run llvm_mod
Expand All @@ -700,6 +718,8 @@ module Crystal
registry.initialize_all

builder = LLVM::PassManagerBuilder.new
builder.size_level = 0

case optimization_mode
in .o3?
builder.opt_level = 3
Expand All @@ -712,26 +732,37 @@ module Crystal
builder.use_inliner_with_threshold = 150
in .o0?
# default behaviour, no optimizations
in .os?
builder.opt_level = 2
builder.size_level = 1
builder.use_inliner_with_threshold = 50
in .oz?
builder.opt_level = 2
builder.size_level = 2
builder.use_inliner_with_threshold = 5
end

builder.size_level = 0

builder
end
end
{% else %}
protected def optimize(llvm_mod)
{% end %}

protected def optimize(llvm_mod)
{% if LibLLVM::IS_LT_130 %}
optimize_with_pass_manager(llvm_mod)
{% else %}
{% if LibLLVM::IS_LT_170 %}
# PassBuilder doesn't support Os and Oz before LLVM 17
if @optimization_mode.os? || @optimization_mode.oz?
return optimize_with_pass_manager(llvm_mod)
end
{% end %}

LLVM::PassBuilderOptions.new do |options|
mode = case @optimization_mode
in .o3? then "default<O3>"
in .o2? then "default<O2>"
in .o1? then "default<O1>"
in .o0? then "default<O0>"
end
LLVM.run_passes(llvm_mod, mode, target_machine, options)
LLVM.run_passes(llvm_mod, "default<#{@optimization_mode}>", target_machine, options)
end
end
{% end %}
{% end %}
end

private def run_linker(linker_name, command, args)
print_command(command, args) if verbose?
Expand Down
8 changes: 2 additions & 6 deletions src/llvm/function_pass_manager.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
{% skip_file unless LibLLVM::IS_LT_170 %}

class LLVM::FunctionPassManager
def initialize(@unwrap : LibLLVM::PassManagerRef)
end
Expand Down Expand Up @@ -34,9 +33,6 @@ class LLVM::FunctionPassManager
LibLLVM.dispose_pass_manager(@unwrap)
end

{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
struct Runner
@fpm : FunctionPassManager

Expand Down
6 changes: 4 additions & 2 deletions src/llvm/lib_llvm/initialization.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ lib LibLLVM
fun initialize_core = LLVMInitializeCore(r : PassRegistryRef)
fun initialize_transform_utils = LLVMInitializeTransformUtils(r : PassRegistryRef)
fun initialize_scalar_opts = LLVMInitializeScalarOpts(r : PassRegistryRef)
fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef)
{% if LibLLVM::IS_LT_160 %}
fun initialize_obj_c_arc_opts = LLVMInitializeObjCARCOpts(r : PassRegistryRef)
fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef)
{% end %}
fun initialize_vectorization = LLVMInitializeVectorization(r : PassRegistryRef)
fun initialize_inst_combine = LLVMInitializeInstCombine(r : PassRegistryRef)
fun initialize_ipo = LLVMInitializeIPO(r : PassRegistryRef)
fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef)
fun initialize_analysis = LLVMInitializeAnalysis(r : PassRegistryRef)
fun initialize_ipa = LLVMInitializeIPA(r : PassRegistryRef)
fun initialize_code_gen = LLVMInitializeCodeGen(r : PassRegistryRef)
Expand Down
6 changes: 6 additions & 0 deletions src/llvm/lib_llvm/transforms/pass_builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ lib LibLLVM

fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef
fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef)
{% unless LibLLVM::IS_LT_170 %}
fun pass_builder_options_set_inliner_threshold = LLVMPassBuilderOptionsSetInlinerThreshold(PassBuilderOptionsRef, Int)
{% end %}
fun pass_builder_options_set_loop_unrolling = LLVMPassBuilderOptionsSetLoopUnrolling(PassBuilderOptionsRef, Bool)
fun pass_builder_options_set_loop_vectorization = LLVMPassBuilderOptionsSetLoopVectorization(PassBuilderOptionsRef, Bool)
fun pass_builder_options_set_slp_vectorization = LLVMPassBuilderOptionsSetSLPVectorization(PassBuilderOptionsRef, Bool)
end
9 changes: 4 additions & 5 deletions src/llvm/module.cr
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,11 @@ class LLVM::Module
self
end

{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% if LibLLVM::IS_LT_170 %}
def new_function_pass_manager
FunctionPassManager.new LibLLVM.create_function_pass_manager_for_module(self)
end
{% end %}
def new_function_pass_manager
FunctionPassManager.new LibLLVM.create_function_pass_manager_for_module(self)
end

def ==(other : self)
@unwrap == other.@unwrap
Expand Down
5 changes: 2 additions & 3 deletions src/llvm/module_pass_manager.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
{% skip_file unless LibLLVM::IS_LT_170 %}

class LLVM::ModulePassManager
def initialize
@unwrap = LibLLVM.pass_manager_create
Expand Down
18 changes: 18 additions & 0 deletions src/llvm/pass_builder_options.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,22 @@ class LLVM::PassBuilderOptions

LibLLVM.dispose_pass_builder_options(self)
end

{% unless LibLLVM::IS_LT_170 %}
def set_inliner_threshold(threshold : Int)
LibLLVM.pass_builder_options_set_inliner_threshold(self, threshold)
end
{% end %}

def set_loop_unrolling(enabled : Bool)
LibLLVM.pass_builder_options_set_loop_unrolling(self, enabled)
end

def set_loop_vectorization(enabled : Bool)
LibLLVM.pass_builder_options_set_loop_vectorization(self, enabled)
end

def set_slp_vectorization(enabled : Bool)
LibLLVM.pass_builder_options_set_slp_vectorization(self, enabled)
end
end
5 changes: 2 additions & 3 deletions src/llvm/pass_manager_builder.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
{% skip_file unless LibLLVM::IS_LT_170 %}

class LLVM::PassManagerBuilder
def initialize
@unwrap = LibLLVM.pass_manager_builder_create
Expand Down
28 changes: 21 additions & 7 deletions src/llvm/pass_registry.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% unless LibLLVM::IS_LT_130 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
{% skip_file unless LibLLVM::IS_LT_170 %}

struct LLVM::PassRegistry
def self.instance : self
new LibLLVM.get_global_pass_registry
Expand All @@ -9,17 +8,32 @@ struct LLVM::PassRegistry
def initialize(@unwrap : LibLLVM::PassRegistryRef)
end

Inits = %w(core transform_utils scalar_opts obj_c_arc_opts vectorization inst_combine ipo instrumentation analysis ipa code_gen target)
{% begin %}
Inits = [
"initialize_core",
"initialize_transform_utils",
"initialize_scalar_opts",
{% unless LibLLVM::IS_LT_160 %} "initialize_obj_c_arc_opts", {% end %}
"initialize_vectorization",
"initialize_inst_combine",
"initialize_ipo",
{% unless LibLLVM::IS_LT_160 %} "initialize_instrumentation", {% end %}
"initialize_analysis",
"initialize_ipa",
"initialize_code_gen",
"initialize_target",
]
{% end %}

{% for name in Inits %}
def initialize_{{name.id}}
LibLLVM.initialize_{{name.id}} self
def {{name.id}}
LibLLVM.{{name.id}} self
end
{% end %}

def initialize_all
{% for name in Inits %}
initialize_{{name.id}}
{{name.id}}
{% end %}
end
ysbaddaden marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
Loading