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 macro methods for Asm and AsmOperand #14268

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 69 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
56 changes: 56 additions & 0 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down