From 3b46ba954a27219534991a3da292349b23d27d55 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 10 Mar 2026 11:01:00 +1100 Subject: [PATCH 01/11] feat: add 0020-target-annotation --- .../codegen/target_feature_annotation_spec.cr | 94 +++++++++++++++++++ src/compiler/crystal/codegen/fun.cr | 15 ++- src/compiler/crystal/program.cr | 3 +- src/compiler/crystal/semantic/ast.cr | 8 ++ .../crystal/semantic/top_level_visitor.cr | 18 ++++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 spec/compiler/codegen/target_feature_annotation_spec.cr diff --git a/spec/compiler/codegen/target_feature_annotation_spec.cr b/spec/compiler/codegen/target_feature_annotation_spec.cr new file mode 100644 index 000000000000..e5ab370aadc4 --- /dev/null +++ b/spec/compiler/codegen/target_feature_annotation_spec.cr @@ -0,0 +1,94 @@ +require "../spec_helper" + +describe "Code gen: TargetFeature annotation" do + it "errors if invalid argument provided" do + assert_error <<-CRYSTAL, "no argument named 'invalid', expected 'cpu'" + @[TargetFeature(invalid: "lorem ipsum")] + def foo + end + CRYSTAL + end + + it "errors if invalid cpu argument type provided" do + assert_error <<-CRYSTAL, "expected argument 'cpu' to be String" + @[TargetFeature(cpu: 3)] + def foo + end + CRYSTAL + end + + it "errors if invalid feature argument type provided" do + assert_error <<-CRYSTAL, "expected argument #1 to 'TargetFeature' to be String" + @[TargetFeature(3)] + def foo + end + CRYSTAL + end + + it "errors wrong number of arguments provided" do + assert_error <<-CRYSTAL, "wrong number of arguments for TargetFeature (given 2, expected 0..1)" + @[TargetFeature("+sve", "+sve2")] + def foo + end + CRYSTAL + end + + it "can target optional CPU features" do + {% if flag?(:aarch64) %} + run(<<-CRYSTAL).to_b.should be_true + require "prelude" + + # SVE2-only instruction (will fail unless +sve,+sve2 enabled) + @[TargetFeature("+sve,+sve2")] + def sve2_smoke_test : Nil + asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") + nil + end + + enum Supported + NEON + SVE + SVE2 + end + + supported = Supported::NEON + + # Don’t call it; we only care that it compiles. + case supported + when .sve2? + sve2_smoke_test + end + + true + CRYSTAL + {% else %} + pending! "no strictly optional features on this architecture" + {% end %} + end + + it "can optimize code for a specific CPU" do + {% if flag?(:aarch64) %} + run(<<-CRYSTAL).to_i.should be > 0 + require "prelude" + + @[TargetFeature(cpu: "apple-m1")] + def foo + [1, 2, 3].sample + end + foo + CRYSTAL + {% elsif flag?(:x86_64) %} + run(<<-CRYSTAL).to_i.should be > 0 + require "prelude" + + @[TargetFeature(cpu: "znver1")] + def foo + [1, 2, 3].sample + end + foo + CRYSTAL + {% else %} + pending! "no CPU specified for this architecture" + {% end %} + end +end diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 6262dba44925..2cc9f213a75e 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -447,16 +447,25 @@ class Crystal::CodeGenVisitor context.fun = typed_fun.func context.fun_type = typed_fun.type - if @debug.variables? + optimize_none = @debug.variables? + features = target_def.target_features + cpu = target_def.target_cpu + + if optimize_none || features || cpu + # Make it a hard boundary, disabling inlining context.fun.add_attribute LLVM::Attribute::NoInline - context.fun.add_attribute LLVM::Attribute::OptimizeNone + + context.fun.add_attribute LLVM::Attribute::OptimizeNone if optimize_none + context.fun.add_target_dependent_attribute("target-features", features) if features + context.fun.add_target_dependent_attribute("target-cpu", cpu) if cpu else context.fun.add_attribute LLVM::Attribute::AlwaysInline if target_def.always_inline? + context.fun.add_attribute LLVM::Attribute::NoInline if target_def.no_inline? end + context.fun.add_attribute LLVM::Attribute::ReturnsTwice if target_def.returns_twice? context.fun.add_attribute LLVM::Attribute::Naked if target_def.naked? context.fun.add_attribute LLVM::Attribute::NoReturn if target_def.no_returns? - context.fun.add_attribute LLVM::Attribute::NoInline if target_def.no_inline? end def setup_closure_vars(target_def, closure_vars, context, closure_type, closure_ptr) diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 3e4e2ab6fb09..f1afb2fc6ebf 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -279,6 +279,7 @@ module Crystal types["ThreadLocal"] = @thread_local_annotation = AnnotationType.new self, self, "ThreadLocal" types["Deprecated"] = @deprecated_annotation = AnnotationType.new self, self, "Deprecated" types["Experimental"] = @experimental_annotation = AnnotationType.new self, self, "Experimental" + types["TargetFeature"] = @target_feature_annotation = AnnotationType.new self, self, "TargetFeature" define_crystal_constants @@ -562,7 +563,7 @@ module Crystal {% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128 uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable array static_array exception tuple named_tuple proc union enum range slice regex crystal - packed_annotation thread_local_annotation no_inline_annotation + packed_annotation thread_local_annotation no_inline_annotation target_feature_annotation always_inline_annotation naked_annotation returns_twice_annotation raises_annotation primitive_annotation call_convention_annotation flags_annotation link_annotation extern_annotation deprecated_annotation experimental_annotation) %} diff --git a/src/compiler/crystal/semantic/ast.cr b/src/compiler/crystal/semantic/ast.cr index ca5905ef223e..fedeae5f3fd5 100644 --- a/src/compiler/crystal/semantic/ast.cr +++ b/src/compiler/crystal/semantic/ast.cr @@ -159,6 +159,12 @@ module Crystal # `true` if this def has the `@[ReturnsTwice]` annotation property? returns_twice = false + # Set to the value of the `@[TargetFeature("+sve,+sve2")]` annotation + property target_features : String? = nil + + # Set to the value of the `@[TargetFeature(cpu: "apple-m4")]` annotation + property target_cpu : String? = nil + # `true` if this def has the `@[Naked]` annotation property? naked = false @@ -222,6 +228,8 @@ module Crystal a_def.no_inline = no_inline? a_def.always_inline = always_inline? a_def.returns_twice = returns_twice? + a_def.target_features = target_features + a_def.target_cpu = target_cpu a_def.naked = naked? a_def.annotations = annotations a_def.new = new? diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 17a29ee2ba93..3ca2a004fac7 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1258,6 +1258,24 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor node.returns_twice = true when @program.raises_annotation node.raises = true + when @program.target_feature_annotation + ann.named_args.try &.each do |arg| + case arg.name + when "cpu" + cpu_value = arg.value.to_s + ann.raise "expected argument 'cpu' to be String" unless cpu_value.starts_with?('"') && cpu_value.ends_with?('"') + node.target_cpu = cpu_value[1..-2] + else + ann.raise "no argument named '#{arg.name}', expected 'cpu'" + end + end + + if ann.args.size > 0 + ann.raise "wrong number of arguments for TargetFeature (given #{ann.args.size}, expected 0..1)" if ann.args.size > 1 + features_value = ann.args[0].to_s + ann.raise "expected argument #1 to 'TargetFeature' to be String" unless features_value.starts_with?('"') && features_value.ends_with?('"') + node.target_features = features_value + end else yield annotation_type, ann end From 6569fb3efde73792ff703956e2d63a998a92c430 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Tue, 10 Mar 2026 12:07:01 +1100 Subject: [PATCH 02/11] fix: target features extraction --- src/compiler/crystal/semantic/top_level_visitor.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 3ca2a004fac7..43140dfd7270 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1274,7 +1274,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor ann.raise "wrong number of arguments for TargetFeature (given #{ann.args.size}, expected 0..1)" if ann.args.size > 1 features_value = ann.args[0].to_s ann.raise "expected argument #1 to 'TargetFeature' to be String" unless features_value.starts_with?('"') && features_value.ends_with?('"') - node.target_features = features_value + node.target_features = features_value[1..-2] end else yield annotation_type, ann From ebea9129cbcc2337d982ce9e5af9962ee2ab1978 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Mar 2026 15:15:21 +1100 Subject: [PATCH 03/11] feat: update specs --- .../codegen/target_annotation_spec.cr | 52 ++++++++++ .../codegen/target_feature_annotation_spec.cr | 94 ------------------- .../semantic/target_annotation_spec.cr | 63 +++++++++++++ 3 files changed, 115 insertions(+), 94 deletions(-) create mode 100644 spec/compiler/codegen/target_annotation_spec.cr delete mode 100644 spec/compiler/codegen/target_feature_annotation_spec.cr create mode 100644 spec/compiler/semantic/target_annotation_spec.cr diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr new file mode 100644 index 000000000000..4cccb5f08d79 --- /dev/null +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -0,0 +1,52 @@ +require "../spec_helper" + +describe "Code gen: TargetFeature annotation" do + it "can target optional CPU features" do + run(<<-CRYSTAL).to_b.should be_true + require "prelude" + + # This feature is available on all platforms + @[TargetFeature("+soft-float")] + def no_hardware_floating_points(input : Float64) : Float64 + input / 2.0 + end + + output = no_hardware_floating_points(4.0) + output == 2.0 + CRYSTAL + end + + it "can optimize code for a specific CPU" do + run(<<-CRYSTAL).to_i.should be > 0 + require "prelude" + + {% if flag?(:aarch64) %} + @[TargetFeature(cpu: "apple-m1")] + {% elsif flag?(:x86_64) %} + @[TargetFeature(cpu: "x86-64-v4")] + {% end %} + def foo + [1, 2, 3].sample + end + foo + CRYSTAL + end + + it "can target optional CPU features and optimize code for a specific CPU" do + run(<<-CRYSTAL).to_b.should be_true + require "prelude" + + {% if flag?(:aarch64) %} + @[TargetFeature("+soft-float", cpu: "apple-m1")] + {% elsif flag?(:x86_64) %} + @[TargetFeature("+soft-float", cpu: "x86-64-v4")] + {% end %} + def no_hardware_floating_points(input : Float64) : Float64 + input / 2.0 + end + + output = no_hardware_floating_points(4.0) + output == 2.0 + CRYSTAL + end +end diff --git a/spec/compiler/codegen/target_feature_annotation_spec.cr b/spec/compiler/codegen/target_feature_annotation_spec.cr deleted file mode 100644 index e5ab370aadc4..000000000000 --- a/spec/compiler/codegen/target_feature_annotation_spec.cr +++ /dev/null @@ -1,94 +0,0 @@ -require "../spec_helper" - -describe "Code gen: TargetFeature annotation" do - it "errors if invalid argument provided" do - assert_error <<-CRYSTAL, "no argument named 'invalid', expected 'cpu'" - @[TargetFeature(invalid: "lorem ipsum")] - def foo - end - CRYSTAL - end - - it "errors if invalid cpu argument type provided" do - assert_error <<-CRYSTAL, "expected argument 'cpu' to be String" - @[TargetFeature(cpu: 3)] - def foo - end - CRYSTAL - end - - it "errors if invalid feature argument type provided" do - assert_error <<-CRYSTAL, "expected argument #1 to 'TargetFeature' to be String" - @[TargetFeature(3)] - def foo - end - CRYSTAL - end - - it "errors wrong number of arguments provided" do - assert_error <<-CRYSTAL, "wrong number of arguments for TargetFeature (given 2, expected 0..1)" - @[TargetFeature("+sve", "+sve2")] - def foo - end - CRYSTAL - end - - it "can target optional CPU features" do - {% if flag?(:aarch64) %} - run(<<-CRYSTAL).to_b.should be_true - require "prelude" - - # SVE2-only instruction (will fail unless +sve,+sve2 enabled) - @[TargetFeature("+sve,+sve2")] - def sve2_smoke_test : Nil - asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") - nil - end - - enum Supported - NEON - SVE - SVE2 - end - - supported = Supported::NEON - - # Don’t call it; we only care that it compiles. - case supported - when .sve2? - sve2_smoke_test - end - - true - CRYSTAL - {% else %} - pending! "no strictly optional features on this architecture" - {% end %} - end - - it "can optimize code for a specific CPU" do - {% if flag?(:aarch64) %} - run(<<-CRYSTAL).to_i.should be > 0 - require "prelude" - - @[TargetFeature(cpu: "apple-m1")] - def foo - [1, 2, 3].sample - end - foo - CRYSTAL - {% elsif flag?(:x86_64) %} - run(<<-CRYSTAL).to_i.should be > 0 - require "prelude" - - @[TargetFeature(cpu: "znver1")] - def foo - [1, 2, 3].sample - end - foo - CRYSTAL - {% else %} - pending! "no CPU specified for this architecture" - {% end %} - end -end diff --git a/spec/compiler/semantic/target_annotation_spec.cr b/spec/compiler/semantic/target_annotation_spec.cr new file mode 100644 index 000000000000..04ae5323c7ed --- /dev/null +++ b/spec/compiler/semantic/target_annotation_spec.cr @@ -0,0 +1,63 @@ +require "../spec_helper" + +describe "Semantic: TargetFeature annotation" do + it "errors if invalid argument provided" do + assert_error <<-CRYSTAL, "no argument named 'invalid', expected 'cpu'" + @[TargetFeature(invalid: "lorem ipsum")] + def foo + end + CRYSTAL + end + + it "errors if invalid cpu argument type provided" do + assert_error <<-CRYSTAL, "expected argument 'cpu' to be String" + @[TargetFeature(cpu: 3)] + def foo + end + CRYSTAL + end + + it "errors if invalid cpu argument type provided and feature provided" do + assert_error <<-CRYSTAL, "expected argument 'cpu' to be String" + @[TargetFeature("+sve", cpu: 4)] + def foo + end + CRYSTAL + end + + it "errors if invalid feature argument type provided" do + assert_error <<-CRYSTAL, "expected argument #1 to 'TargetFeature' to be String" + @[TargetFeature(3)] + def foo + end + CRYSTAL + end + + it "errors if invalid feature argument type provided and cpu provided" do + assert_error <<-CRYSTAL, "expected argument #1 to 'TargetFeature' to be String" + @[TargetFeature(3, cpu: "apple-m1")] + def foo + end + CRYSTAL + end + + it "errors if wrong number of arguments provided" do + assert_error <<-CRYSTAL, "wrong number of arguments for TargetFeature (given 2, expected 0..1)" + @[TargetFeature("+sve", "+sve2")] + def foo + end + CRYSTAL + end + + it "can target a specific LLVM supported feature" do + assert_type(<<-CRYSTAL) { float64 } + # This feature is available on all platforms + @[TargetFeature("+soft-float")] + def no_hardware_floating_points(input : Float64) : Float64 + input / 2.0 + end + + no_hardware_floating_points(4.0) + CRYSTAL + end +end From a49dfe4a850c240e153b113f7f54821bec10cf3e Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Mar 2026 15:58:44 +1100 Subject: [PATCH 04/11] check for StringLiteral --- .../crystal/semantic/top_level_visitor.cr | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 43140dfd7270..15faf08fb118 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1259,22 +1259,22 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor when @program.raises_annotation node.raises = true when @program.target_feature_annotation - ann.named_args.try &.each do |arg| - case arg.name + ann.named_args.try &.each do |named_arg| + case named_arg.name when "cpu" - cpu_value = arg.value.to_s - ann.raise "expected argument 'cpu' to be String" unless cpu_value.starts_with?('"') && cpu_value.ends_with?('"') - node.target_cpu = cpu_value[1..-2] + cpu_value = named_arg.value + named_arg.raise "expected argument 'cpu' to be String" unless cpu_value.is_a?(StringLiteral) + node.target_cpu = cpu_value.to_s[1..-2] else - ann.raise "no argument named '#{arg.name}', expected 'cpu'" + named_arg.raise "no argument named '#{named_arg.name}', expected 'cpu'" end end if ann.args.size > 0 ann.raise "wrong number of arguments for TargetFeature (given #{ann.args.size}, expected 0..1)" if ann.args.size > 1 - features_value = ann.args[0].to_s - ann.raise "expected argument #1 to 'TargetFeature' to be String" unless features_value.starts_with?('"') && features_value.ends_with?('"') - node.target_features = features_value[1..-2] + features_value = ann.args[0] + ann.raise "expected argument #1 to 'TargetFeature' to be String" unless features_value.is_a?(StringLiteral) + node.target_features = features_value.to_s[1..-2] end else yield annotation_type, ann From e7bc9fe10927fe8d869bcabebbdb9366594b7bf2 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Mar 2026 16:03:43 +1100 Subject: [PATCH 05/11] fix spec --- .../codegen/target_annotation_spec.cr | 22 +++++++++---------- .../semantic/target_annotation_spec.cr | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr index 4cccb5f08d79..5aa66b91fd79 100644 --- a/spec/compiler/codegen/target_annotation_spec.cr +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -6,13 +6,13 @@ describe "Code gen: TargetFeature annotation" do require "prelude" # This feature is available on all platforms - @[TargetFeature("+soft-float")] - def no_hardware_floating_points(input : Float64) : Float64 - input / 2.0 + @[TargetFeature("+strict-align")] + def strict_align(input : Int32) : Int32 + input * 2 end - output = no_hardware_floating_points(4.0) - output == 2.0 + output = strict_align(1) + output == 2 CRYSTAL end @@ -37,16 +37,16 @@ describe "Code gen: TargetFeature annotation" do require "prelude" {% if flag?(:aarch64) %} - @[TargetFeature("+soft-float", cpu: "apple-m1")] + @[TargetFeature("+strict-align", cpu: "apple-m1")] {% elsif flag?(:x86_64) %} - @[TargetFeature("+soft-float", cpu: "x86-64-v4")] + @[TargetFeature("+strict-align", cpu: "x86-64-v4")] {% end %} - def no_hardware_floating_points(input : Float64) : Float64 - input / 2.0 + def strict_align(input : Int32) : Int32 + input * 2 end - output = no_hardware_floating_points(4.0) - output == 2.0 + output = strict_align(1) + output == 2 CRYSTAL end end diff --git a/spec/compiler/semantic/target_annotation_spec.cr b/spec/compiler/semantic/target_annotation_spec.cr index 04ae5323c7ed..5b2872f45d3f 100644 --- a/spec/compiler/semantic/target_annotation_spec.cr +++ b/spec/compiler/semantic/target_annotation_spec.cr @@ -50,14 +50,14 @@ describe "Semantic: TargetFeature annotation" do end it "can target a specific LLVM supported feature" do - assert_type(<<-CRYSTAL) { float64 } + assert_type(<<-CRYSTAL) { int32 } # This feature is available on all platforms - @[TargetFeature("+soft-float")] - def no_hardware_floating_points(input : Float64) : Float64 - input / 2.0 + @[TargetFeature("+strict-align")] + def strict_align(input : Int32) : Int32 + input * 2 end - no_hardware_floating_points(4.0) + strict_align(1) CRYSTAL end end From 9d95d0c0666f7ce12a49627c5400ac2885c95ba4 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Wed, 11 Mar 2026 20:02:38 +1100 Subject: [PATCH 06/11] fix spec --- spec/compiler/semantic/target_annotation_spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compiler/semantic/target_annotation_spec.cr b/spec/compiler/semantic/target_annotation_spec.cr index 5b2872f45d3f..7581a356bf22 100644 --- a/spec/compiler/semantic/target_annotation_spec.cr +++ b/spec/compiler/semantic/target_annotation_spec.cr @@ -54,7 +54,7 @@ describe "Semantic: TargetFeature annotation" do # This feature is available on all platforms @[TargetFeature("+strict-align")] def strict_align(input : Int32) : Int32 - input * 2 + input end strict_align(1) From 40b3aacffb8ecca8e414555c16dfbdfe77a31a41 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Thu, 12 Mar 2026 06:21:39 +1100 Subject: [PATCH 07/11] use .value to obtain the string literal value --- src/compiler/crystal/semantic/top_level_visitor.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 15faf08fb118..814242ac8b70 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -1264,7 +1264,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor when "cpu" cpu_value = named_arg.value named_arg.raise "expected argument 'cpu' to be String" unless cpu_value.is_a?(StringLiteral) - node.target_cpu = cpu_value.to_s[1..-2] + node.target_cpu = cpu_value.value else named_arg.raise "no argument named '#{named_arg.name}', expected 'cpu'" end @@ -1274,7 +1274,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor ann.raise "wrong number of arguments for TargetFeature (given #{ann.args.size}, expected 0..1)" if ann.args.size > 1 features_value = ann.args[0] ann.raise "expected argument #1 to 'TargetFeature' to be String" unless features_value.is_a?(StringLiteral) - node.target_features = features_value.to_s[1..-2] + node.target_features = features_value.value end else yield annotation_type, ann From 6b4e3277f37b8821df27fd53ac64665181010fc3 Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 23 Mar 2026 22:11:18 +1100 Subject: [PATCH 08/11] chore: fix spec --- .../codegen/target_annotation_spec.cr | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr index 5aa66b91fd79..4a7c612949e8 100644 --- a/spec/compiler/codegen/target_annotation_spec.cr +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -2,17 +2,14 @@ require "../spec_helper" describe "Code gen: TargetFeature annotation" do it "can target optional CPU features" do - run(<<-CRYSTAL).to_b.should be_true + compile(<<-CRYSTAL) require "prelude" - # This feature is available on all platforms - @[TargetFeature("+strict-align")] - def strict_align(input : Int32) : Int32 - input * 2 + @[TargetFeature("+sve,+sve2")] + def sve2_smoke_test : Nil + asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") + nil end - - output = strict_align(1) - output == 2 CRYSTAL end @@ -33,20 +30,14 @@ describe "Code gen: TargetFeature annotation" do end it "can target optional CPU features and optimize code for a specific CPU" do - run(<<-CRYSTAL).to_b.should be_true + compile(<<-CRYSTAL) require "prelude" - {% if flag?(:aarch64) %} - @[TargetFeature("+strict-align", cpu: "apple-m1")] - {% elsif flag?(:x86_64) %} - @[TargetFeature("+strict-align", cpu: "x86-64-v4")] - {% end %} - def strict_align(input : Int32) : Int32 - input * 2 + @[TargetFeature("+sve,+sve2", cpu: "apple-m1")] + def sve2_smoke_test : Nil + asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") + nil end - - output = strict_align(1) - output == 2 CRYSTAL end end From 2038f7f8bea35b33f2f97a210fcb248a87c7f81a Mon Sep 17 00:00:00 2001 From: Stephen von Takach Date: Mon, 23 Mar 2026 23:10:02 +1100 Subject: [PATCH 09/11] chore: fix spec --- spec/compiler/codegen/target_annotation_spec.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr index 4a7c612949e8..a7b86a25b72c 100644 --- a/spec/compiler/codegen/target_annotation_spec.cr +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -14,18 +14,17 @@ describe "Code gen: TargetFeature annotation" do end it "can optimize code for a specific CPU" do - run(<<-CRYSTAL).to_i.should be > 0 + compile(<<-CRYSTAL) require "prelude" {% if flag?(:aarch64) %} @[TargetFeature(cpu: "apple-m1")] {% elsif flag?(:x86_64) %} - @[TargetFeature(cpu: "x86-64-v4")] + @[TargetFeature(cpu: "x86-64-v3")] {% end %} def foo [1, 2, 3].sample end - foo CRYSTAL end From 76bb31409608e8984512c8f14bfa2895987598d4 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 23 Mar 2026 14:56:20 +0100 Subject: [PATCH 10/11] Fix: function must be called We must call the function for it to be compiled, otherwise it's dead code and will be skipped and always succeed. We must also specify the compilation target otherwise specs are failing once we call the function with a LLVM error. --- .../codegen/target_annotation_spec.cr | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr index a7b86a25b72c..f5006b8272b0 100644 --- a/spec/compiler/codegen/target_annotation_spec.cr +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -2,41 +2,43 @@ require "../spec_helper" describe "Code gen: TargetFeature annotation" do it "can target optional CPU features" do - compile(<<-CRYSTAL) - require "prelude" + {% if compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} pending! "requires LLVM 13+" {% end %} + compile(<<-CRYSTAL, target: "aarch64-darwin") @[TargetFeature("+sve,+sve2")] def sve2_smoke_test : Nil asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") nil end + + sve2_smoke_test CRYSTAL end it "can optimize code for a specific CPU" do - compile(<<-CRYSTAL) - require "prelude" - - {% if flag?(:aarch64) %} - @[TargetFeature(cpu: "apple-m1")] - {% elsif flag?(:x86_64) %} - @[TargetFeature(cpu: "x86-64-v3")] - {% end %} + {% if compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} pending! "requires LLVM 13+" {% end %} + + compile(<<-CRYSTAL, prelude: "prelude", target: "x86_64-linux-gnu") + @[TargetFeature(cpu: "x86-64-v3")] def foo [1, 2, 3].sample end + + foo CRYSTAL end it "can target optional CPU features and optimize code for a specific CPU" do - compile(<<-CRYSTAL) - require "prelude" + {% if compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} pending! "requires LLVM 13+" {% end %} + compile(<<-CRYSTAL, target: "aarch64-darwin") @[TargetFeature("+sve,+sve2", cpu: "apple-m1")] def sve2_smoke_test : Nil asm("ext z0.b, { z1.b, z2.b }, #0" :::: "volatile") nil end + + sve2_smoke_test CRYSTAL end end From e1342a595b6440e50180370908d820da0ad49553 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 24 Mar 2026 10:56:18 +0100 Subject: [PATCH 11/11] simplify cpu x86-64-v3 example --- spec/compiler/codegen/target_annotation_spec.cr | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/compiler/codegen/target_annotation_spec.cr b/spec/compiler/codegen/target_annotation_spec.cr index f5006b8272b0..6376b412aca1 100644 --- a/spec/compiler/codegen/target_annotation_spec.cr +++ b/spec/compiler/codegen/target_annotation_spec.cr @@ -18,13 +18,19 @@ describe "Code gen: TargetFeature annotation" do it "can optimize code for a specific CPU" do {% if compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %} pending! "requires LLVM 13+" {% end %} - compile(<<-CRYSTAL, prelude: "prelude", target: "x86_64-linux-gnu") + # unlike the ARM backend, the X86 backend doesn't validate assembly + # instructions, but the LLVM intrinsics are validated so we use one + compile(<<-CRYSTAL, target: "x86_64-linux-gnu") + lib LibIntrinsics + fun x86_avx_vzeroall = "llvm.x86.avx.vzeroall" + end + @[TargetFeature(cpu: "x86-64-v3")] - def foo - [1, 2, 3].sample + def x86_vzeroall : Nil + LibIntrinsics.x86_avx_vzeroall end - foo + x86_vzeroall CRYSTAL end