diff --git a/llvm/include/llvm/MC/MCInstrDesc.h b/llvm/include/llvm/MC/MCInstrDesc.h --- a/llvm/include/llvm/MC/MCInstrDesc.h +++ b/llvm/include/llvm/MC/MCInstrDesc.h @@ -32,8 +32,9 @@ /// corresponding high-order hex digit specifying the constraint value. /// This allows for a maximum of 3 constraints. enum OperandConstraint { - TIED_TO = 0, // Must be allocated the same register as specified value. - EARLY_CLOBBER // If present, operand is an early clobber register. + TIED_TO = 0, // Must be allocated the same register as specified value. + EARLY_CLOBBER, // If present, operand is an early clobber register. + NOT_ALL_SAME // Operands listed cannot all be assigned the same register. }; // Define a macro to produce each constraint value. @@ -43,6 +44,9 @@ #define MCOI_EARLY_CLOBBER \ (1 << MCOI::EARLY_CLOBBER) +#define MCOI_NOT_ALL_SAME(bitset) \ + ((bitset << (16 + 4*MCOI::NOT_ALL_SAME)) | (1 << MCOI::NOT_ALL_SAME)) + /// These are flags set on operands, but should be considered /// private, all access should go through the MCOperandInfo accessors. /// See the accessors for a description of what these are. diff --git a/llvm/test/TableGen/ConstraintChecking.inc b/llvm/test/TableGen/ConstraintChecking.inc --- a/llvm/test/TableGen/ConstraintChecking.inc +++ b/llvm/test/TableGen/ConstraintChecking.inc @@ -1,6 +1,10 @@ include "llvm/Target/Target.td" -def TestTarget : Target; +def TestInstrInfo : InstrInfo; + +def TestTarget : Target { + let InstructionSet = TestInstrInfo; +} class Encoding : Instruction { field bits<8> Inst; @@ -16,9 +20,10 @@ def Reg : RegisterClass<"TestTarget", [i32], 32, (sequence "R%d", 0, 1)>; class TestInstructionWithConstraints : Encoding { + let Namespace = "Test"; dag OutOperandList = (outs Reg:$dest1, Reg:$dest2); - dag InOperandList = (ins Reg:$src1, Reg:$src2); - string AsmString = "mnemonic $dest1, $dest2, $src1, $src2"; + dag InOperandList = (ins Reg:$src1, Reg:$src2, Reg:$src3); + string AsmString = "mnemonic $dest1, $dest2, $src1, $src2, $src3"; string AsmVariantName = ""; let Constraints = cstr; field bits<1> dest1; diff --git a/llvm/test/TableGen/NotAllSameConstraintChecking1.td b/llvm/test/TableGen/NotAllSameConstraintChecking1.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/NotAllSameConstraintChecking1.td @@ -0,0 +1,51 @@ +// RUN: llvm-tblgen -gen-asm-writer -I %p -I %p/../../include %s 2>&1 | FileCheck %s -DFILE=%s + +include "ConstraintChecking.inc" + +// CHECK-LABEL: #ifdef GET_INSTRINFO_ENUM + +// CHECK: Dest1A {{[[:space]]+}} = [[DEST1A:[0-9]+]] +// CHECK: Dest1B {{[[:space]]+}} = [[DEST1B:[0-9]+]] +// CHECK: Dest2A {{[[:space]]+}} = [[DEST2A:[0-9]+]] +// CHECK: Dest2B {{[[:space]]+}} = [[DEST2B:[0-9]+]] +// CHECK: Src1A {{[[:space]]+}} = [[SRC1A:[0-9]+]] +// CHECK: Src1B {{[[:space]]+}} = [[SRC1B:[0-9]+]] +// CHECK: Src2 {{[[:space]]+}} = [[SRC2:[0-9]+]] + +// CHECK-LABEL: #ifdef GET_INSTRINFO_MC_DESC + +// CHECK: static const MCOperandInfo [[OPINFODEST1:OperandInfo[0-9]+]][] = { { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0xF<< 16 + 4*MCOI::NOT_ALL_SAME| (1 << MCOI::NOT_ALL_SAME)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, (1 << MCOI::EARLY_CLOBBER) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, }; +// CHECK-NEXT: static const MCOperandInfo [[OPINFODEST2:OperandInfo[0-9]+]][] = { { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0xF<< 16 + 4*MCOI::NOT_ALL_SAME| (1 << MCOI::NOT_ALL_SAME)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0 << 16) | (1 << MCOI::TIED_TO)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, (1 << MCOI::EARLY_CLOBBER) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, }; +// CHECK-NEXT: static const MCOperandInfo [[OPINFOSRC1:OperandInfo[0-9]+]][] = { { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, (1 << MCOI::EARLY_CLOBBER) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, (1 << MCOI::EARLY_CLOBBER) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0xF<< 16 + 4*MCOI::NOT_ALL_SAME| (1 << MCOI::NOT_ALL_SAME)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0 << 16) | (1 << MCOI::TIED_TO)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, }; +// CHECK-NEXT: static const MCOperandInfo [[OPINFOSRC2:OperandInfo[0-9]+]][] = { { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, ((0xF<< 16 + 4*MCOI::NOT_ALL_SAME| (1 << MCOI::NOT_ALL_SAME)) }, { TestTarget::RegRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, }; + +// CHECK-LABEL: extern const MCInstrDesc TestTargetInsts[] = { + +// Constraint should go on $src2. +// CHECK-DAG: { [[SRC2]], {{.*}}, [[OPINFOSRC2]] }, // Inst #[[SRC2]] = Src2 +def Src2 : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2)">; + +// Constraint should go on $dest1. +// CHECK-DAG: { [[DEST1A]], {{.*}}, [[OPINFODEST1]] }, // Inst #[[DEST1A]] = Dest1A +def Dest1A : TestInstructionWithConstraints<"@earlyclobber $src2, not_all_same($dest1, $dest2, $src1, $src2)">; + +// Constraint should go on $dest1. +// CHECK-DAG: { [[DEST1B]], {{.*}}, [[OPINFODEST1]] }, // Inst #[[DEST1B]] = Dest1B +def Dest1B : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2), @earlyclobber $src2">; + +// Constraint should go on $dest2. +// CHECK-DAG: { [[DEST2A]], {{.*}}, [[OPINFODEST2]] }, // Inst #[[DEST2A]] = Dest2A +def Dest2A : TestInstructionWithConstraints<"@earlyclobber $src2, $dest1 = $src1, not_all_same($dest1, $dest2, $src1, $src2)">; + +// Constraint should go on $dest2. +// CHECK-DAG: { [[DEST2B]], {{.*}}, [[OPINFODEST2]] }, // Inst #[[DEST2B]] = Dest2B +def Dest2B : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2), @earlyclobber $src2, $dest1 = $src1">; + +// Constraint should go on $src1. +// CHECK-DAG: { [[SRC1A]], {{.*}}, [[OPINFOSRC1]] }, // Inst #[[SRC1A]] = Src1A +def Src1A : TestInstructionWithConstraints<"@earlyclobber $dest1, @earlyclobber $dest2, $dest1 = $src2, not_all_same($dest1, $dest2, $src1, $src2)">; + +// Constraint should go on $src1. +// CHECK-DAG: { [[SRC1B]], {{.*}}, [[OPINFOSRC1]] }, // Inst #[[SRC1B]] = Src1B +def Src1B : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2), @earlyclobber $dest1, @earlyclobber $dest2, $dest1 = $src2">; + diff --git a/llvm/test/TableGen/NotAllSameConstraintChecking2.td b/llvm/test/TableGen/NotAllSameConstraintChecking2.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/NotAllSameConstraintChecking2.td @@ -0,0 +1,6 @@ +// RUN: not llvm-tblgen -gen-instr-info -I %p -I %p/../../include %s 2>&1 | FileCheck %s -DFILE=%s + +include "ConstraintChecking.inc" + +// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Operand '$dest1' of 'Foo' cannot have multiple operands tied to it! +def Foo : TestInstructionWithConstraints<"@earlyclobber $dest1, @earlyclobber $dest2, $dest1 = $src1, $dest2 = $src2, not_all_same($dest1, $dest2, $src1, $src2)">; diff --git a/llvm/test/TableGen/NotAllSameConstraintChecking3.td b/llvm/test/TableGen/NotAllSameConstraintChecking3.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/NotAllSameConstraintChecking3.td @@ -0,0 +1,6 @@ +// RUN: not llvm-tblgen -gen-instr-info -I %p -I %p/../../include %s 2>&1 | FileCheck %s -DFILE=%s + +include "ConstraintChecking.inc" + +// CHECK: [[FILE]]:[[@LINE+1]]:1: error: Operand '$src2' of 'Foo' cannot have multiple constraints! +def Foo : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2), @earlyclobber $dest1, @earlyclobber $dest2, $dest1 = $src1, $dest2 = $src2">; diff --git a/llvm/test/TableGen/NotAllSameConstraintChecking4.td b/llvm/test/TableGen/NotAllSameConstraintChecking4.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/NotAllSameConstraintChecking4.td @@ -0,0 +1,6 @@ +// RUN: not llvm-tblgen -gen-asm-writer -I %p -I %p/../../include %s 2>&1 | FileCheck %s -DFILE=%s + +include "ConstraintChecking.inc" + +// CHECK: [[FILE]]:[[@LINE+1]]:1: error: not_all_same operand '$src3' [Num = 4] out of bounds in 'Foo': 'not_all_same($dest1, $dest2, $src1, $src2, $src3)' +def Foo : TestInstructionWithConstraints<"not_all_same($dest1, $dest2, $src1, $src2, $src3)">; diff --git a/llvm/utils/TableGen/CodeGenInstruction.h b/llvm/utils/TableGen/CodeGenInstruction.h --- a/llvm/utils/TableGen/CodeGenInstruction.h +++ b/llvm/utils/TableGen/CodeGenInstruction.h @@ -13,6 +13,7 @@ #ifndef LLVM_UTILS_TABLEGEN_CODEGENINSTRUCTION_H #define LLVM_UTILS_TABLEGEN_CODEGENINSTRUCTION_H +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MachineValueType.h" #include "llvm/Support/SMLoc.h" @@ -30,8 +31,13 @@ class CGIOperandList { public: class ConstraintInfo { - enum { None, EarlyClobber, Tied } Kind = None; - unsigned OtherTiedOperand = 0; + public: + using Operand = unsigned; + using OperandList = SmallVector; + + private: + enum { None, EarlyClobber, Tied, NotAllSame } Kind = None; + OperandList Operands; public: ConstraintInfo() = default; @@ -39,30 +45,54 @@ static ConstraintInfo getEarlyClobber() { ConstraintInfo I; I.Kind = EarlyClobber; - I.OtherTiedOperand = 0; return I; } static ConstraintInfo getTied(unsigned Op) { ConstraintInfo I; I.Kind = Tied; - I.OtherTiedOperand = Op; + I.Operands.push_back(Op); + return I; + } + + static ConstraintInfo getNotAllSame(const SmallVectorImpl &Ops) { + ConstraintInfo I; + I.Kind = NotAllSame; + I.Operands.assign(Ops.begin(), Ops.end()); + return I; + } + + static ConstraintInfo getNotAllSame(SmallVectorImpl &&Ops) { + ConstraintInfo I; + I.Kind = NotAllSame; + I.Operands = std::move(Ops); return I; } bool isNone() const { return Kind == None; } bool isEarlyClobber() const { return Kind == EarlyClobber; } bool isTied() const { return Kind == Tied; } + bool isNotAllSame() const { return Kind == NotAllSame; } + + OperandList::size_type getNumOperands() const { return Operands.size(); } + + using const_iterator = OperandList::const_iterator; + using const_range = iterator_range; + + const_iterator begin() const { return Operands.begin(); } + const_iterator end() const { return Operands.end(); } + + const_range operands() const { return make_range(begin(), end()); } unsigned getTiedOperand() const { assert(isTied()); - return OtherTiedOperand; + return *begin(); } bool operator==(const ConstraintInfo &RHS) const { if (Kind != RHS.Kind) return false; - if (Kind == Tied && OtherTiedOperand != RHS.OtherTiedOperand) + if ((Kind == Tied || Kind == NotAllSame) && Operands != RHS.Operands) return false; return true; } diff --git a/llvm/utils/TableGen/CodeGenInstruction.cpp b/llvm/utils/TableGen/CodeGenInstruction.cpp --- a/llvm/utils/TableGen/CodeGenInstruction.cpp +++ b/llvm/utils/TableGen/CodeGenInstruction.cpp @@ -12,6 +12,7 @@ #include "CodeGenInstruction.h" #include "CodeGenTarget.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringMap.h" @@ -231,8 +232,244 @@ return std::make_pair(0U, 0U); } +// Represent a single function-style constraint argument. +struct ConstraintArg { + std::string Name; + unsigned OpNum; + unsigned SubOpNum; + + ConstraintArg(std::string N, unsigned O, unsigned S) : + Name(std::move(N)), OpNum(O), SubOpNum(S) {} + + ConstraintArg(std::string N, const std::pair &Op) : + Name(std::move(N)), OpNum(Op.first), SubOpNum(Op.second) {} +}; + +static bool operator<(const ConstraintArg &A, const ConstraintArg &B) { + return A.OpNum < B.OpNum || (A.OpNum == B.OpNum && A.SubOpNum < B.SubOpNum); +} + +// Parse a list of constraint parts. CStr is a string of comma-separated names +// with leading and trailing whitespace stripped. Whitespace may still appear +// after a comma and before the next name. +static SmallVector +ParseConstraintOperands(const StringRef ConstraintStr, + CGIOperandList &Ops, + Record *Rec) { + SmallVector List; + + SmallVector Args; + ConstraintStr.split(Args, ","); + + for (StringRef Arg : Args) { + std::string Name(Arg.trim().str()); + std::pair Op = Ops.ParseOperandName(Name, false); + + // Record the name for diagnostic purposes. + List.push_back(ConstraintArg(Name, Op.first, Op.second)); + } + return List; +} + +// Try to find an operand on which to place a not-all-same constraint. Return +// true on success, false otherwise. + +static bool TryToPlaceNotAllSameConstraint( + CGIOperandList::ConstraintInfo NewConstraint, + CGIOperandList &Ops) { + assert(NewConstraint.isNotAllSame() && "Wrong constraint type!"); + + // We need to decide where to put this constraint. It seems that this should + // be straightforward but unfortunately we don't know if we might see a + // constraint in the future. The tied constraint will go on the source so we + // would prefer not to put it there. In addition, a destination may have an + // early clobber constraint, so we'd prefer to not put this on a destination. + // Since we can't know which source might be tied to the destination and we + // can't know if the destination might be an early clobber, we just need to + // pick an operand and adjust placement later if we see any conflicts. + // + // Early clobber seems like a much less common constraint, but it also seems + // that tied operands are most likely to tie the first source operand to the + // destination. So if we have two or more source operands in our list, put + // the constraint on the last source operand. That way the destination can be + // tied and early clobber and we won't have to adjust anything. If we only + // have one source operand and one destination operand, put the constraint on + // the destination on the theory that early clobber is less common than tied. + + SmallVector Operands; + + std::transform(NewConstraint.begin(), NewConstraint.end(), + std::back_inserter(Operands), + [&Ops] (unsigned Op) { + return ConstraintArg("", Ops.getSubOperandNumber(Op)); + }); + + // Sort so that destinations will appear first. + std::sort(Operands.begin(), Operands.end()); + + int ConstraintOpNum = -1; + + // As a first stab, try to place the constraint on the highest-numbered source + // operand first. This is less likely to conflict with early clobber and tied + // constraints. + for (int i = Operands.size() - 1; i >= 0; --i) { + const ConstraintArg &OpToCheck = Operands[i]; + if (Ops[OpToCheck.OpNum].Constraints[OpToCheck.SubOpNum].isNone()) { + ConstraintOpNum = i; + break; + } + } + + if (ConstraintOpNum > 0 && Operands.size() == 2 && + Operands[0].OpNum < Ops.NumDefs) { + // We have two operands, a source and a destination. Prefer placement on + // the destination if possible, because early clobber is less common than + // tied. + const ConstraintArg &DstArg = Operands[0]; + + if (Ops[DstArg.OpNum].Constraints[DstArg.SubOpNum].isNone()) + ConstraintOpNum = 0; + } + + if (ConstraintOpNum < 0) + // We have no place to put the constraint. + return false; + + const ConstraintArg &OpToConstrain = Operands[ConstraintOpNum]; + + Ops[OpToConstrain.OpNum].Constraints[OpToConstrain.SubOpNum] = + std::move(NewConstraint); + + return true; +} + +// Attempt to place the given constraint on the given operand. Move any +// existing constraints if necessary and possible. Return true on success, +// false otherwise. +static bool +TryToPlaceConstraint(CGIOperandList::ConstraintInfo NewConstraint, + const std::pair &Op, + CGIOperandList &Ops) { + if (NewConstraint.isNotAllSame()) + return TryToPlaceNotAllSameConstraint(NewConstraint, Ops); + + CGIOperandList::ConstraintInfo &ExistingConstraint = + Ops[Op.first].Constraints[Op.second]; + + if (!ExistingConstraint.isNone() && !ExistingConstraint.isNotAllSame()) + // We can't move this constraint. + return false; + + // Avoid a copy if we don't need it. + using OptionalConstraint = Optional; + + OptionalConstraint NotAllSameConstraint( + ExistingConstraint.isNotAllSame() ? + OptionalConstraint(ExistingConstraint) : OptionalConstraint()); + + Ops[Op.first].Constraints[Op.second] = std::move(NewConstraint); + + if (NotAllSameConstraint.hasValue()) + // Find a new place for the not_all_same constraint that we just removed. + if (!TryToPlaceNotAllSameConstraint(*NotAllSameConstraint, Ops)) + return false; + + return true; +} + +static void ParseFuncLikeConstraint(const std::string &ConstraintStr, + CGIOperandList &Ops, + Record *Rec) { + std::string::size_type LParen = ConstraintStr.find_first_of("("); + if (LParen == std::string::npos) { + PrintFatalError( + Rec->getLoc(), "Missing opening '(' in '" + + Rec->getName() + "': '" + ConstraintStr + "'"); + } + + const char *Whitespace = " \t"; + const char *ArgOpener = "( \t"; + + std::string Name(ConstraintStr.substr(0, LParen)); + + std::string::size_type ArgStart = ConstraintStr.find_first_not_of(ArgOpener, LParen); + std::string::size_type RParen = ConstraintStr.find_first_of(")", ArgStart); + if (RParen == std::string::npos) { + PrintFatalError( + Rec->getLoc(), "Missing closing ')' in '" + + Rec->getName() + "': '" + ConstraintStr + "'"); + } + std::string::size_type ArgEnd = ConstraintStr.find_last_not_of(Whitespace, RParen); + + std::string Args(ConstraintStr.substr(ArgStart, ArgEnd - ArgStart)); + + SmallVector Operands = + ParseConstraintOperands(Args, Ops, Rec); + + if (Name == "not_all_same") { + // not_all_same(op1, op2, ...) + // + // Requirement: At least one of the operands must be assigned a register + // different from that of some other operand. There is no need to have an + // operand register completely unique from all the others. It is ok, for + // example, to assign a four-operand instruction like this: + // + // A = op(A, B, B) + + // If we only have a single source or single destination operand listed, + // then why are we using this constraint in the first place? + if (Operands.size() < 2) { + PrintFatalError( + Rec->getLoc(), "Single not_all_same operand in '" + + Rec->getName() + "': '" + ConstraintStr + "'"); + } + + SmallVector ConstraintOps; + + std::transform(Operands.begin(), Operands.end(), + std::back_inserter(ConstraintOps), + [&Ops] (const ConstraintArg &Op) { + return Ops.getFlattenedOperandNumber(std::make_pair( + Op.OpNum, + Op.SubOpNum)); + }); + + + // Due to MC representation limitations, we can only have four operands + // participate in this constraint and they must be operands 0-3. See + // MCOperandInfo::Constraints. + for (unsigned i = 0; i < ConstraintOps.size(); ++i) { + if (ConstraintOps[i] > 3) { + PrintFatalError( + Rec->getLoc(), "not_all_same operand '" + Operands[i].Name + + "' [Num = " + utostr(i) + "] out of bounds in '" + + Rec->getName() + "': '" + ConstraintStr + "'"); + } + } + + if (!TryToPlaceNotAllSameConstraint( + CGIOperandList::ConstraintInfo::getNotAllSame(ConstraintOps), + Ops)) { + PrintFatalError( + Rec->getLoc(), "Constraint conflict for not_all_same operand in '" + + Rec->getName() + "': '" + ConstraintStr + "'"); + } + return; + } + + PrintFatalError( + Rec->getLoc(), "Unrecognized constraint '" + ConstraintStr + + "' in '" + Rec->getName() + "'"); +} + static void ParseConstraint(const std::string &CStr, CGIOperandList &Ops, Record *Rec) { + std::string::size_type LParen = CStr.find_first_of("("); + if (LParen != std::string::npos) { + ParseFuncLikeConstraint(CStr, Ops, Rec); + return; + } + // EARLY_CLOBBER: @early $reg std::string::size_type wpos = CStr.find_first_of(" \t"); std::string::size_type start = CStr.find_first_not_of(" \t"); @@ -247,13 +484,12 @@ Name = Name.substr(wpos); std::pair Op = Ops.ParseOperandName(Name, false); - // Build the string for the operand - if (!Ops[Op.first].Constraints[Op.second].isNone()) + if (!TryToPlaceConstraint(CGIOperandList::ConstraintInfo::getEarlyClobber(), + Op, Ops)) PrintFatalError( - Rec->getLoc(), "Operand '" + Name + "' of '" + Rec->getName() + - "' cannot have multiple constraints!"); - Ops[Op.first].Constraints[Op.second] = - CGIOperandList::ConstraintInfo::getEarlyClobber(); + Rec->getLoc(), "Operand '" + Name + "' of '" + Rec->getName() + + "' cannot have multiple constraints!"); + return; } @@ -301,14 +537,6 @@ Rec->getLoc(), "Output operands '" + LHSOpName + "' and '" + RHSOpName + "' of '" + Rec->getName() + "' cannot be tied!"); - // The constraint has to go on the operand with higher index, i.e. - // the source one. Check there isn't another constraint there - // already. - if (!Ops[SrcOp.first].Constraints[SrcOp.second].isNone()) - PrintFatalError( - Rec->getLoc(), "Operand '" + SrcOpName + "' of '" + Rec->getName() + - "' cannot have multiple constraints!"); - unsigned DestFlatOpNo = Ops.getFlattenedOperandNumber(DestOp); auto NewConstraint = CGIOperandList::ConstraintInfo::getTied(DestFlatOpNo); @@ -322,7 +550,13 @@ "' cannot have multiple operands tied to it!"); } - Ops[SrcOp.first].Constraints[SrcOp.second] = NewConstraint; + // The constraint has to go on the operand with higher index, i.e. + // the source one. Check there isn't another constraint there + // already. + if (!TryToPlaceConstraint(NewConstraint, SrcOp, Ops)) + PrintFatalError( + Rec->getLoc(), "Operand '" + SrcOpName + "' of '" + Rec->getName() + + "' cannot have multiple constraints!"); } static void ParseConstraints(const std::string &CStr, CGIOperandList &Ops, @@ -330,16 +564,37 @@ if (CStr.empty()) return; const std::string delims(","); + const std::string ws(" \t"); std::string::size_type bidx, eidx; - bidx = CStr.find_first_not_of(delims); + bidx = CStr.find_first_not_of(delims + ws); while (bidx != std::string::npos) { - eidx = CStr.find_first_of(delims, bidx); + std::string::size_type delim_search_start_idx = bidx; + + // Skip paren pairs. Note that this DOES NOT handle nested parens. + std::string::size_type delim = CStr.find_first_of(delims, + delim_search_start_idx); + std::string::size_type lparen = CStr.find_first_of("(", bidx); + + if (lparen != std::string::npos && + (delim == std::string::npos || lparen < delim)) { + // Parenthesis comes first, parse the whole function-like constraint. + std::string::size_type rparen = CStr.find_first_of(")", lparen); + if (rparen == std::string::npos) { + PrintFatalError( + Rec->getLoc(), "Missing closing ')' in '" + + Rec->getName() + "': '" + CStr + "'"); + } + // Don't look for a delimiter until after the right parenthesis. + delim_search_start_idx = rparen; + } + + eidx = CStr.find_first_of(delims, delim_search_start_idx); if (eidx == std::string::npos) eidx = CStr.length(); ParseConstraint(CStr.substr(bidx, eidx - bidx), Ops, Rec); - bidx = CStr.find_first_not_of(delims, eidx); + bidx = CStr.find_first_not_of(delims + ws, eidx); } } diff --git a/llvm/utils/TableGen/InstrInfoEmitter.cpp b/llvm/utils/TableGen/InstrInfoEmitter.cpp --- a/llvm/utils/TableGen/InstrInfoEmitter.cpp +++ b/llvm/utils/TableGen/InstrInfoEmitter.cpp @@ -183,7 +183,20 @@ Res += "0"; else if (Constraint.isEarlyClobber()) Res += "MCOI_EARLY_CLOBBER"; - else { + else if (Constraint.isNotAllSame()) { + // We have four bits to work with. Consider the four bits to represent + // a set of operands, where a 1 bit means that operand participates in + // the constraint. This means we are limited to representing a total of + // four operands. An instruction could have more than four operands but + // only the operands numbered 0-3 can participate in this constraint. + unsigned Bitset = 0; + for (CGIOperandList::ConstraintInfo::Operand Op : Constraint) { + assert(Op < 4 && "Constraint operand overflow"); + Bitset |= (1 << Op); + } + + Res += "MCOI_NOT_ALL_SAME(0x" + utohexstr(Bitset) + ")"; + } else { assert(Constraint.isTied()); Res += "MCOI_TIED_TO(" + utostr(Constraint.getTiedOperand()) + ")"; }