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 all 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("Invalid optimization mode: O#{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("Invalid optimization mode: O#{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
4 changes: 2 additions & 2 deletions src/llvm/function_pass_manager.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
class LLVM::FunctionPassManager
Expand Down Expand Up @@ -34,7 +34,7 @@ class LLVM::FunctionPassManager
LibLLVM.dispose_pass_manager(@unwrap)
end

{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
struct Runner
Expand Down
4 changes: 2 additions & 2 deletions src/llvm/lib_llvm/initialization.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ 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) {% 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)
{% if LibLLVM::IS_LT_160 %} fun initialize_instrumentation = LLVMInitializeInstrumentation(r : PassRegistryRef) {% end %}
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
2 changes: 1 addition & 1 deletion src/llvm/module.cr
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class LLVM::Module
self
end

{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
def new_function_pass_manager
Expand Down
2 changes: 1 addition & 1 deletion src/llvm/module_pass_manager.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
class LLVM::ModulePassManager
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
2 changes: 1 addition & 1 deletion src/llvm/pass_manager_builder.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
class LLVM::PassManagerBuilder
Expand Down
25 changes: 20 additions & 5 deletions src/llvm/pass_registry.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% unless LibLLVM::IS_LT_130 %}
{% unless LibLLVM::IS_LT_170 %}
@[Deprecated("The legacy pass manager was removed in LLVM 17. Use `LLVM::PassBuilderOptions` instead")]
{% end %}
struct LLVM::PassRegistry
Expand All @@ -9,17 +9,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 = %w[
initialize_core
initialize_transform_utils
initialize_scalar_opts
{% if LibLLVM::IS_LT_160 %} initialize_obj_c_arc_opts {% end %}
initialize_vectorization
initialize_inst_combine
initialize_ipo
{% if 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