diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index a970aff3e876..bd774aec01e8 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -3414,6 +3414,75 @@ module Crystal end end + describe Asm do + asm1 = Asm.new("nop") + asm2 = Asm.new( + text: "foo", + outputs: [AsmOperand.new("=r", "x".var), AsmOperand.new("=r", "y".var)], + inputs: [AsmOperand.new("i", 1.int32), AsmOperand.new("r", 2.int32)], + clobbers: %w(rax memory), + volatile: true, + alignstack: true, + intel: true, + can_throw: true, + ) + + it "executes text" do + assert_macro %({{x.text}}), %("nop"), {x: asm1} + assert_macro %({{x.text}}), %("foo"), {x: asm2} + end + + it "executes outputs" do + assert_macro %({{x.outputs}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.outputs}}), %(["=r"(x), "=r"(y)]), {x: asm2} + end + + it "executes inputs" do + assert_macro %({{x.inputs}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.inputs}}), %(["i"(1), "r"(2)]), {x: asm2} + end + + it "executes clobbers" do + assert_macro %({{x.clobbers}}), %([] of ::NoReturn), {x: asm1} + assert_macro %({{x.clobbers}}), %(["rax", "memory"]), {x: asm2} + end + + it "executes volatile?" do + assert_macro %({{x.volatile?}}), %(false), {x: asm1} + assert_macro %({{x.volatile?}}), %(true), {x: asm2} + end + + it "executes alignstack?" do + assert_macro %({{x.alignstack?}}), %(false), {x: asm1} + assert_macro %({{x.alignstack?}}), %(true), {x: asm2} + end + + it "executes intel?" do + assert_macro %({{x.intel?}}), %(false), {x: asm1} + assert_macro %({{x.intel?}}), %(true), {x: asm2} + end + + it "executes can_throw?" do + assert_macro %({{x.can_throw?}}), %(false), {x: asm1} + assert_macro %({{x.can_throw?}}), %(true), {x: asm2} + end + end + + describe AsmOperand do + asm_operand1 = AsmOperand.new("=r", "x".var) + asm_operand2 = AsmOperand.new("i", 1.int32) + + it "executes constraint" do + assert_macro %({{x.constraint}}), %("=r"), {x: asm_operand1} + assert_macro %({{x.constraint}}), %("i"), {x: asm_operand2} + end + + it "executes exp" do + assert_macro %({{x.exp}}), %(x), {x: asm_operand1} + assert_macro %({{x.exp}}), %(1), {x: asm_operand2} + end + end + describe "env" do it "has key" do with_env("FOO": "foo") do diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 3b9ce399c7ef..c5954559f56c 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2363,6 +2363,75 @@ module Crystal::Macros class MagicConstant < ASTNode end + # An inline assembly expression. + # + # Every assembly `node` is equivalent to: + # + # ``` + # asm( + # {{ node.text }} : + # {{ node.outputs.splat }} : + # {{ node.inputs.splat }} : + # {{ node.clobbers.splat }} : + # {% if node.volatile? %} "volatile", {% end %} + # {% if node.alignstack? %} "alignstack", {% end %} + # {% if node.intel? %} "intel", {% end %} + # {% if node.can_throw? %} "unwind", {% end %} + # ) + # ``` + class Asm < ASTNode + # Returns the template string for this assembly expression. + def text : StringLiteral + end + + # Returns an array of output operands for this assembly expression. + def outputs : ArrayLiteral(AsmOperand) + end + + # Returns an array of input operands for this assembly expression. + def inputs : ArrayLiteral(AsmOperand) + end + + # Returns an array of clobbered register names for this assembly expression. + def clobbers : ArrayLiteral(StringLiteral) + end + + # Returns whether the assembly expression contains side effects that are + # not listed in `#outputs`, `#inputs`, and `#clobbers`. + def volatile? : BoolLiteral + end + + # Returns whether the assembly expression requires stack alignment code. + def alignstack? : BoolLiteral + end + + # Returns `true` if the template string uses the Intel syntax, `false` if it + # uses the AT&T syntax. + def intel? : BoolLiteral + end + + # Returns whether the assembly expression might unwind the stack. + def can_throw? : BoolLiteral + end + end + + # An output or input operand for an `Asm` node. + # + # Every operand `node` is equivalent to: + # + # ``` + # {{ node.constraint }}({{ node.exp }}) + # ``` + class AsmOperand < ASTNode + # Returns the constraint string of this operand. + def constraint : StringLiteral + end + + # Returns the associated output or input argument of this operand. + def exp : ASTNode + end + end + # A fictitious node representing an identifier like, `foo`, `Bar` or `something_else`. # # The parser doesn't create these nodes. Instead, you create them by invoking `id` diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index b98306db3fc8..65e8f84fca65 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1640,6 +1640,62 @@ module Crystal end end + class Asm + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "text" + interpret_check_args { StringLiteral.new(@text) } + when "outputs" + interpret_check_args do + if outputs = @outputs + ArrayLiteral.map(outputs, &.itself) + else + empty_no_return_array + end + end + when "inputs" + interpret_check_args do + if inputs = @inputs + ArrayLiteral.map(inputs, &.itself) + else + empty_no_return_array + end + end + when "clobbers" + interpret_check_args do + if clobbers = @clobbers + ArrayLiteral.map(clobbers) { |clobber| StringLiteral.new(clobber) } + else + empty_no_return_array + end + end + when "volatile?" + interpret_check_args { BoolLiteral.new(@volatile) } + when "alignstack?" + interpret_check_args { BoolLiteral.new(@alignstack) } + when "intel?" + interpret_check_args { BoolLiteral.new(@intel) } + when "can_throw?" + interpret_check_args { BoolLiteral.new(@can_throw) } + else + super + end + end + end + + class AsmOperand + def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) + case method + when "constraint" + interpret_check_args { StringLiteral.new(@constraint) } + when "exp" + interpret_check_args { @exp } + else + super + end + end + end + class MacroId def interpret(method : String, args : Array(ASTNode), named_args : Hash(String, ASTNode)?, block : Crystal::Block?, interpreter : Crystal::MacroInterpreter, name_loc : Location?) case method