Skip to content

Commit

Permalink
Add compiler flags -Os and -Oz to optimize binary size (#14463)
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Müller <[email protected]>
Co-authored-by: Sijawusz Pur Rahnama <[email protected]>
  • Loading branch information
3 people authored Apr 24, 2024
1 parent 2eed385 commit dcccbc5
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 36 deletions.
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 @@ -204,10 +204,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

Expand Down

0 comments on commit dcccbc5

Please sign in to comment.