Skip to content
94 changes: 94 additions & 0 deletions spec/compiler/codegen/target_feature_annotation_spec.cr
Original file line number Diff line number Diff line change
@@ -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
15 changes: 12 additions & 3 deletions src/compiler/crystal/codegen/fun.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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) %}
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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?
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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[1..-2]
end
else
yield annotation_type, ann
end
Expand Down
Loading