Skip to content

[Xtensa] Add basic support for inline asm constraints. #108986

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

Merged
merged 2 commits into from
Sep 25, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions llvm/lib/Target/Xtensa/XtensaAsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//

#include "XtensaAsmPrinter.h"
#include "MCTargetDesc/XtensaInstPrinter.h"
#include "MCTargetDesc/XtensaMCExpr.h"
#include "MCTargetDesc/XtensaTargetStreamer.h"
#include "TargetInfo/XtensaTargetInfo.h"
Expand Down Expand Up @@ -157,6 +158,57 @@ void XtensaAsmPrinter::emitConstantPool() {
OutStreamer->popSection();
}

void XtensaAsmPrinter::printOperand(const MachineInstr *MI, int OpNo,
raw_ostream &O) {
const MachineOperand &MO = MI->getOperand(OpNo);

switch (MO.getType()) {
case MachineOperand::MO_Register:
case MachineOperand::MO_Immediate: {
MCOperand MC = lowerOperand(MI->getOperand(OpNo));
XtensaInstPrinter::printOperand(MC, O);
break;
}
default:
llvm_unreachable("unknown operand type");
}
}

bool XtensaAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
const char *ExtraCode, raw_ostream &O) {
// Print the operand if there is no operand modifier.
if (!ExtraCode || !ExtraCode[0]) {
printOperand(MI, OpNo, O);
return false;
}

// Fallback to the default implementation.
return AsmPrinter::PrintAsmOperand(MI, OpNo, ExtraCode, O);
}

bool XtensaAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI,
unsigned OpNo,
const char *ExtraCode,
raw_ostream &OS) {
if (ExtraCode && ExtraCode[0])
return true; // Unknown modifier.

assert(OpNo + 1 < MI->getNumOperands() && "Insufficient operands");

const MachineOperand &Base = MI->getOperand(OpNo);
const MachineOperand &Offset = MI->getOperand(OpNo + 1);

assert(Base.isReg() &&
"Unexpected base pointer for inline asm memory operand.");
assert(Offset.isImm() && "Unexpected offset for inline asm memory operand.");

OS << XtensaInstPrinter::getRegisterName(Base.getReg());
OS << ", ";
OS << Offset.getImm();

return false;
}

MCSymbol *
XtensaAsmPrinter::GetConstantPoolIndexSymbol(const MachineOperand &MO) const {
// Create a symbol for the name.
Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/Target/Xtensa/XtensaAsmPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ class LLVM_LIBRARY_VISIBILITY XtensaAsmPrinter : public AsmPrinter {

void emitMachineConstantPoolValue(MachineConstantPoolValue *MCPV) override;

void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);

bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
const char *ExtraCode, raw_ostream &O) override;

bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNo,
const char *ExtraCode, raw_ostream &OS) override;

MCSymbol *GetConstantPoolIndexSymbol(const MachineOperand &MO) const;

MCSymbol *GetJumpTableSymbol(const MachineOperand &MO) const;
Expand Down
23 changes: 23 additions & 0 deletions llvm/lib/Target/Xtensa/XtensaISelDAGToDAG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class XtensaDAGToDAGISel : public SelectionDAGISel {

void Select(SDNode *Node) override;

bool SelectInlineAsmMemoryOperand(const SDValue &Op,
InlineAsm::ConstraintCode ConstraintID,
std::vector<SDValue> &OutOps) override;

// For load/store instructions generate (base+offset) pair from
// memory address. The offset must be a multiple of scale argument.
bool selectMemRegAddr(SDValue Addr, SDValue &Base, SDValue &Offset,
Expand Down Expand Up @@ -212,3 +216,22 @@ void XtensaDAGToDAGISel::Select(SDNode *Node) {

SelectCode(Node);
}

bool XtensaDAGToDAGISel::SelectInlineAsmMemoryOperand(
const SDValue &Op, InlineAsm::ConstraintCode ConstraintID,
std::vector<SDValue> &OutOps) {
switch (ConstraintID) {
default:
llvm_unreachable("Unexpected asm memory constraint");
case InlineAsm::ConstraintCode::m: {
SDValue Base, Offset;

selectMemRegAddr(Op, Base, Offset, 4);
OutOps.push_back(Base);
OutOps.push_back(Offset);

return false;
}
}
return false;
}
68 changes: 68 additions & 0 deletions llvm/lib/Target/Xtensa/XtensaISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,74 @@ bool XtensaTargetLowering::isOffsetFoldingLegal(
return false;
}

//===----------------------------------------------------------------------===//
// Inline asm support
//===----------------------------------------------------------------------===//
TargetLowering::ConstraintType
XtensaTargetLowering::getConstraintType(StringRef Constraint) const {
if (Constraint.size() == 1) {
switch (Constraint[0]) {
case 'r':
return C_RegisterClass;
default:
break;
}
}
return TargetLowering::getConstraintType(Constraint);
}

TargetLowering::ConstraintWeight
XtensaTargetLowering::getSingleConstraintMatchWeight(
AsmOperandInfo &Info, const char *Constraint) const {
ConstraintWeight Weight = CW_Invalid;
Value *CallOperandVal = Info.CallOperandVal;
// If we don't have a value, we can't do a match,
// but allow it at the lowest weight.
if (!CallOperandVal)
return CW_Default;

Type *Ty = CallOperandVal->getType();

// Look at the constraint type.
switch (*Constraint) {
default:
Weight = TargetLowering::getSingleConstraintMatchWeight(Info, Constraint);
break;
case 'r':
if (Ty->isIntegerTy())
Weight = CW_Register;
break;
}
return Weight;
}

std::pair<unsigned, const TargetRegisterClass *>
XtensaTargetLowering::getRegForInlineAsmConstraint(
const TargetRegisterInfo *TRI, StringRef Constraint, MVT VT) const {
if (Constraint.size() == 1) {
// GCC Constraint Letters
switch (Constraint[0]) {
default:
break;
case 'r': // General-purpose register
return std::make_pair(0U, &Xtensa::ARRegClass);
}
}
return TargetLowering::getRegForInlineAsmConstraint(TRI, Constraint, VT);
}

void XtensaTargetLowering::LowerAsmOperandForConstraint(
SDValue Op, StringRef Constraint, std::vector<SDValue> &Ops,
SelectionDAG &DAG) const {
SDLoc DL(Op);

// Only support length 1 constraints for now.
if (Constraint.size() > 1)
return;

TargetLowering::LowerAsmOperandForConstraint(Op, Constraint, Ops, DAG);
}

//===----------------------------------------------------------------------===//
// Calling conventions
//===----------------------------------------------------------------------===//
Expand Down
15 changes: 15 additions & 0 deletions llvm/lib/Target/Xtensa/XtensaISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ class XtensaTargetLowering : public TargetLowering {

const char *getTargetNodeName(unsigned Opcode) const override;

std::pair<unsigned, const TargetRegisterClass *>
getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI,
StringRef Constraint, MVT VT) const override;

TargetLowering::ConstraintType
getConstraintType(StringRef Constraint) const override;

TargetLowering::ConstraintWeight
getSingleConstraintMatchWeight(AsmOperandInfo &Info,
const char *Constraint) const override;

void LowerAsmOperandForConstraint(SDValue Op, StringRef Constraint,
std::vector<SDValue> &Ops,
SelectionDAG &DAG) const override;

SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;

SDValue LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv,
Expand Down
14 changes: 14 additions & 0 deletions llvm/test/CodeGen/Xtensa/inline-asm-invalid.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: not llc --mtriple=xtensa < %s 2>&1 | FileCheck %s

define void @constraint_f() nounwind {
; CHECK: error: unknown asm constraint 'f'
tail call void asm "addi a1, a1, $0", "f"(i32 1)
ret void
}

define i32 @register_a100(i32 %a) nounwind {
; CHECK: error: couldn't allocate input reg for constraint '{$a100}'
%1 = tail call i32 asm "addi $0, $1, 1", "=r,{$a100}"(i32 %a)
ret i32 %1
}
46 changes: 46 additions & 0 deletions llvm/test/CodeGen/Xtensa/inline-asm-mem-constraint.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc --mtriple=xtensa < %s | FileCheck %s --check-prefix=XTENSA

define i32 @m_offset_0(ptr %p) nounwind {
; XTENSA-LABEL: m_offset_0:
; XTENSA: #APP
; XTENSA-NEXT: l32i a2, a2, 0
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = call i32 asm "l32i $0, $1", "=r,*m"(ptr elementtype(i32) %p)
ret i32 %1
}

define i32 @m_offset_1020(ptr %p) nounwind {
; XTENSA-LABEL: m_offset_1020:
; XTENSA: #APP
; XTENSA-NEXT: l32i a2, a2, 1020
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = getelementptr inbounds i8, ptr %p, i32 1020
%2 = call i32 asm "l32i $0, $1", "=r,*m"(ptr elementtype(i32) %1)
ret i32 %2
}

define i8 @m_i8_offset_7(ptr %p) nounwind {
; XTENSA-LABEL: m_i8_offset_7:
; XTENSA: addi a8, a2, 7
; XTENSA-NEXT: #APP
; XTENSA-NEXT: l8ui a2, a8, 0
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = getelementptr inbounds i8, ptr %p, i32 7
%2 = call i8 asm "l8ui $0, $1", "=r,*m"(ptr elementtype(i8) %1)
ret i8 %2
}

define i16 @m_i16_offset_10(ptr %p) nounwind {
; XTENSA-LABEL: m_i16_offset_10:
; XTENSA: #APP
; XTENSA-NEXT: l16si a2, a2, 20
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = getelementptr inbounds i16, ptr %p, i32 10
%2 = call i16 asm "l16si $0, $1", "=r,*m"(ptr elementtype(i16) %1)
ret i16 %2
}
40 changes: 40 additions & 0 deletions llvm/test/CodeGen/Xtensa/inline-asm.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
; RUN: llc -mtriple=xtensa < %s \
; RUN: | FileCheck -check-prefix=XTENSA %s

@gi = external global i32

define i32 @constraint_r(i32 %a) {
; XTENSA-LABEL: constraint_r:
; XTENSA: l32r a8, .LCPI0_0
; XTENSA-NEXT: l32i a8, a8, 0
; XTENSA-NEXT: #APP
; XTENSA-NEXT: add a2, a2, a8
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = load i32, ptr @gi
%2 = tail call i32 asm "add $0, $1, $2", "=r,r,r"(i32 %a, i32 %1)
ret i32 %2
}

define i32 @constraint_i(i32 %a) {
; XTENSA-LABEL: constraint_i:
; XTENSA: #APP
; XTENSA-NEXT: addi a2, a2, 113
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = load i32, ptr @gi
%2 = tail call i32 asm "addi $0, $1, $2", "=r,r,i"(i32 %a, i32 113)
ret i32 %2
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test some cases with physical registers, and different types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for comments. I added more tests.


define i32 @explicit_register_a3(i32 %a) nounwind {
; XTENSA-LABEL: explicit_register_a3:
; XTENSA: or a3, a2, a2
; XTENSA-NEXT: #APP
; XTENSA-NEXT: addi a2, a3, 1
; XTENSA-NEXT: #NO_APP
; XTENSA-NEXT: ret
%1 = tail call i32 asm "addi $0, $1, 1", "=r,{a3}"(i32 %a)
ret i32 %1
}
Loading