| //===-- MveEmitter.cpp - Generate arm_mve.h for use with clang ------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This set of linked tablegen backends is responsible for emitting the bits | |
| // and pieces that implement <arm_mve.h>, which is defined by the ACLE standard | |
| // and provides a set of types and functions for (more or less) direct access | |
| // to the MVE instruction set, including the scalar shifts as well as the | |
| // vector instructions. | |
| // | |
| // MVE's standard intrinsic functions are unusual in that they have a system of | |
| // polymorphism. For example, the function vaddq() can behave like vaddq_u16(), | |
| // vaddq_f32(), vaddq_s8(), etc., depending on the types of the vector | |
| // arguments you give it. | |
| // | |
| // This constrains the implementation strategies. The usual approach to making | |
| // the user-facing functions polymorphic would be to either use | |
| // __attribute__((overloadable)) to make a set of vaddq() functions that are | |
| // all inline wrappers on the underlying clang builtins, or to define a single | |
| // vaddq() macro which expands to an instance of _Generic. | |
| // | |
| // The inline-wrappers approach would work fine for most intrinsics, except for | |
| // the ones that take an argument required to be a compile-time constant, | |
| // because if you wrap an inline function around a call to a builtin, the | |
| // constant nature of the argument is not passed through. | |
| // | |
| // The _Generic approach can be made to work with enough effort, but it takes a | |
| // lot of machinery, because of the design feature of _Generic that even the | |
| // untaken branches are required to pass all front-end validity checks such as | |
| // type-correctness. You can work around that by nesting further _Generics all | |
| // over the place to coerce things to the right type in untaken branches, but | |
| // what you get out is complicated, hard to guarantee its correctness, and | |
| // worst of all, gives _completely unreadable_ error messages if the user gets | |
| // the types wrong for an intrinsic call. | |
| // | |
| // Therefore, my strategy is to introduce a new __attribute__ that allows a | |
| // function to be mapped to a clang builtin even though it doesn't have the | |
| // same name, and then declare all the user-facing MVE function names with that | |
| // attribute, mapping each one directly to the clang builtin. And the | |
| // polymorphic ones have __attribute__((overloadable)) as well. So once the | |
| // compiler has resolved the overload, it knows the internal builtin ID of the | |
| // selected function, and can check the immediate arguments against that; and | |
| // if the user gets the types wrong in a call to a polymorphic intrinsic, they | |
| // get a completely clear error message showing all the declarations of that | |
| // function in the header file and explaining why each one doesn't fit their | |
| // call. | |
| // | |
| // The downside of this is that if every clang builtin has to correspond | |
| // exactly to a user-facing ACLE intrinsic, then you can't save work in the | |
| // frontend by doing it in the header file: CGBuiltin.cpp has to do the entire | |
| // job of converting an ACLE intrinsic call into LLVM IR. So the Tablegen | |
| // description for an MVE intrinsic has to contain a full description of the | |
| // sequence of IRBuilder calls that clang will need to make. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ADT/APInt.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include "llvm/TableGen/Error.h" | |
| #include "llvm/TableGen/Record.h" | |
| #include "llvm/TableGen/StringToOffsetTable.h" | |
| #include <cassert> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <list> | |
| #include <map> | |
| #include <memory> | |
| #include <set> | |
| #include <string> | |
| #include <vector> | |
| using namespace llvm; | |
| namespace { | |
| class EmitterBase; | |
| class Result; | |
| // ----------------------------------------------------------------------------- | |
| // A system of classes to represent all the types we'll need to deal with in | |
| // the prototypes of intrinsics. | |
| // | |
| // Query methods include finding out the C name of a type; the "LLVM name" in | |
| // the sense of a C++ code snippet that can be used in the codegen function; | |
| // the suffix that represents the type in the ACLE intrinsic naming scheme | |
| // (e.g. 's32' represents int32_t in intrinsics such as vaddq_s32); whether the | |
| // type is floating-point related (hence should be under #ifdef in the MVE | |
| // header so that it isn't included in integer-only MVE mode); and the type's | |
| // size in bits. Not all subtypes support all these queries. | |
| class Type { | |
| public: | |
| enum class TypeKind { | |
| // Void appears as a return type (for store intrinsics, which are pure | |
| // side-effect). It's also used as the parameter type in the Tablegen | |
| // when an intrinsic doesn't need to come in various suffixed forms like | |
| // vfooq_s8,vfooq_u16,vfooq_f32. | |
| Void, | |
| // Scalar is used for ordinary int and float types of all sizes. | |
| Scalar, | |
| // Vector is used for anything that occupies exactly one MVE vector | |
| // register, i.e. {uint,int,float}NxM_t. | |
| Vector, | |
| // MultiVector is used for the {uint,int,float}NxMxK_t types used by the | |
| // interleaving load/store intrinsics v{ld,st}{2,4}q. | |
| MultiVector, | |
| // Predicate is used by all the predicated intrinsics. Its C | |
| // representation is mve_pred16_t (which is just an alias for uint16_t). | |
| // But we give more detail here, by indicating that a given predicate | |
| // instruction is logically regarded as a vector of i1 containing the | |
| // same number of lanes as the input vector type. So our Predicate type | |
| // comes with a lane count, which we use to decide which kind of <n x i1> | |
| // we'll invoke the pred_i2v IR intrinsic to translate it into. | |
| Predicate, | |
| // Pointer is used for pointer types (obviously), and comes with a flag | |
| // indicating whether it's a pointer to a const or mutable instance of | |
| // the pointee type. | |
| Pointer, | |
| }; | |
| private: | |
| const TypeKind TKind; | |
| protected: | |
| Type(TypeKind K) : TKind(K) {} | |
| public: | |
| TypeKind typeKind() const { return TKind; } | |
| virtual ~Type() = default; | |
| virtual bool requiresFloat() const = 0; | |
| virtual bool requiresMVE() const = 0; | |
| virtual unsigned sizeInBits() const = 0; | |
| virtual std::string cName() const = 0; | |
| virtual std::string llvmName() const { | |
| PrintFatalError("no LLVM type name available for type " + cName()); | |
| } | |
| virtual std::string acleSuffix(std::string) const { | |
| PrintFatalError("no ACLE suffix available for this type"); | |
| } | |
| }; | |
| enum class ScalarTypeKind { SignedInt, UnsignedInt, Float }; | |
| inline std::string toLetter(ScalarTypeKind kind) { | |
| switch (kind) { | |
| case ScalarTypeKind::SignedInt: | |
| return "s"; | |
| case ScalarTypeKind::UnsignedInt: | |
| return "u"; | |
| case ScalarTypeKind::Float: | |
| return "f"; | |
| } | |
| llvm_unreachable("Unhandled ScalarTypeKind enum"); | |
| } | |
| inline std::string toCPrefix(ScalarTypeKind kind) { | |
| switch (kind) { | |
| case ScalarTypeKind::SignedInt: | |
| return "int"; | |
| case ScalarTypeKind::UnsignedInt: | |
| return "uint"; | |
| case ScalarTypeKind::Float: | |
| return "float"; | |
| } | |
| llvm_unreachable("Unhandled ScalarTypeKind enum"); | |
| } | |
| class VoidType : public Type { | |
| public: | |
| VoidType() : Type(TypeKind::Void) {} | |
| unsigned sizeInBits() const override { return 0; } | |
| bool requiresFloat() const override { return false; } | |
| bool requiresMVE() const override { return false; } | |
| std::string cName() const override { return "void"; } | |
| static bool classof(const Type *T) { return T->typeKind() == TypeKind::Void; } | |
| std::string acleSuffix(std::string) const override { return ""; } | |
| }; | |
| class PointerType : public Type { | |
| const Type *Pointee; | |
| bool Const; | |
| public: | |
| PointerType(const Type *Pointee, bool Const) | |
| : Type(TypeKind::Pointer), Pointee(Pointee), Const(Const) {} | |
| unsigned sizeInBits() const override { return 32; } | |
| bool requiresFloat() const override { return Pointee->requiresFloat(); } | |
| bool requiresMVE() const override { return Pointee->requiresMVE(); } | |
| std::string cName() const override { | |
| std::string Name = Pointee->cName(); | |
| // The syntax for a pointer in C is different when the pointee is | |
| // itself a pointer. The MVE intrinsics don't contain any double | |
| // pointers, so we don't need to worry about that wrinkle. | |
| assert(!isa<PointerType>(Pointee) && "Pointer to pointer not supported"); | |
| if (Const) | |
| Name = "const " + Name; | |
| return Name + " *"; | |
| } | |
| std::string llvmName() const override { return "Builder.getPtrTy()"; } | |
| const Type *getPointeeType() const { return Pointee; } | |
| static bool classof(const Type *T) { | |
| return T->typeKind() == TypeKind::Pointer; | |
| } | |
| }; | |
| // Base class for all the types that have a name of the form | |
| // [prefix][numbers]_t, like int32_t, uint16x8_t, float32x4x2_t. | |
| // | |
| // For this sub-hierarchy we invent a cNameBase() method which returns the | |
| // whole name except for the trailing "_t", so that Vector and MultiVector can | |
| // append an extra "x2" or whatever to their element type's cNameBase(). Then | |
| // the main cName() query method puts "_t" on the end for the final type name. | |
| class CRegularNamedType : public Type { | |
| using Type::Type; | |
| virtual std::string cNameBase() const = 0; | |
| public: | |
| std::string cName() const override { return cNameBase() + "_t"; } | |
| }; | |
| class ScalarType : public CRegularNamedType { | |
| ScalarTypeKind Kind; | |
| unsigned Bits; | |
| std::string NameOverride; | |
| public: | |
| ScalarType(const Record *Record) : CRegularNamedType(TypeKind::Scalar) { | |
| Kind = StringSwitch<ScalarTypeKind>(Record->getValueAsString("kind")) | |
| .Case("s", ScalarTypeKind::SignedInt) | |
| .Case("u", ScalarTypeKind::UnsignedInt) | |
| .Case("f", ScalarTypeKind::Float); | |
| Bits = Record->getValueAsInt("size"); | |
| NameOverride = std::string(Record->getValueAsString("nameOverride")); | |
| } | |
| unsigned sizeInBits() const override { return Bits; } | |
| ScalarTypeKind kind() const { return Kind; } | |
| std::string suffix() const { return toLetter(Kind) + utostr(Bits); } | |
| std::string cNameBase() const override { | |
| return toCPrefix(Kind) + utostr(Bits); | |
| } | |
| std::string cName() const override { | |
| if (NameOverride.empty()) | |
| return CRegularNamedType::cName(); | |
| return NameOverride; | |
| } | |
| std::string llvmName() const override { | |
| if (Kind == ScalarTypeKind::Float) { | |
| if (Bits == 16) | |
| return "HalfTy"; | |
| if (Bits == 32) | |
| return "FloatTy"; | |
| if (Bits == 64) | |
| return "DoubleTy"; | |
| PrintFatalError("bad size for floating type"); | |
| } | |
| return "Int" + utostr(Bits) + "Ty"; | |
| } | |
| std::string acleSuffix(std::string overrideLetter) const override { | |
| return "_" + (overrideLetter.size() ? overrideLetter : toLetter(Kind)) | |
| + utostr(Bits); | |
| } | |
| bool isInteger() const { return Kind != ScalarTypeKind::Float; } | |
| bool requiresFloat() const override { return !isInteger(); } | |
| bool requiresMVE() const override { return false; } | |
| bool hasNonstandardName() const { return !NameOverride.empty(); } | |
| static bool classof(const Type *T) { | |
| return T->typeKind() == TypeKind::Scalar; | |
| } | |
| }; | |
| class VectorType : public CRegularNamedType { | |
| const ScalarType *Element; | |
| unsigned Lanes; | |
| public: | |
| VectorType(const ScalarType *Element, unsigned Lanes) | |
| : CRegularNamedType(TypeKind::Vector), Element(Element), Lanes(Lanes) {} | |
| unsigned sizeInBits() const override { return Lanes * Element->sizeInBits(); } | |
| unsigned lanes() const { return Lanes; } | |
| bool requiresFloat() const override { return Element->requiresFloat(); } | |
| bool requiresMVE() const override { return true; } | |
| std::string cNameBase() const override { | |
| return Element->cNameBase() + "x" + utostr(Lanes); | |
| } | |
| std::string llvmName() const override { | |
| return "llvm::FixedVectorType::get(" + Element->llvmName() + ", " + | |
| utostr(Lanes) + ")"; | |
| } | |
| static bool classof(const Type *T) { | |
| return T->typeKind() == TypeKind::Vector; | |
| } | |
| }; | |
| class MultiVectorType : public CRegularNamedType { | |
| const VectorType *Element; | |
| unsigned Registers; | |
| public: | |
| MultiVectorType(unsigned Registers, const VectorType *Element) | |
| : CRegularNamedType(TypeKind::MultiVector), Element(Element), | |
| Registers(Registers) {} | |
| unsigned sizeInBits() const override { | |
| return Registers * Element->sizeInBits(); | |
| } | |
| unsigned registers() const { return Registers; } | |
| bool requiresFloat() const override { return Element->requiresFloat(); } | |
| bool requiresMVE() const override { return true; } | |
| std::string cNameBase() const override { | |
| return Element->cNameBase() + "x" + utostr(Registers); | |
| } | |
| // MultiVectorType doesn't override llvmName, because we don't expect to do | |
| // automatic code generation for the MVE intrinsics that use it: the {vld2, | |
| // vld4, vst2, vst4} family are the only ones that use these types, so it was | |
| // easier to hand-write the codegen for dealing with these structs than to | |
| // build in lots of extra automatic machinery that would only be used once. | |
| static bool classof(const Type *T) { | |
| return T->typeKind() == TypeKind::MultiVector; | |
| } | |
| }; | |
| class PredicateType : public CRegularNamedType { | |
| unsigned Lanes; | |
| public: | |
| PredicateType(unsigned Lanes) | |
| : CRegularNamedType(TypeKind::Predicate), Lanes(Lanes) {} | |
| unsigned sizeInBits() const override { return 16; } | |
| std::string cNameBase() const override { return "mve_pred16"; } | |
| bool requiresFloat() const override { return false; }; | |
| bool requiresMVE() const override { return true; } | |
| std::string llvmName() const override { | |
| return "llvm::FixedVectorType::get(Builder.getInt1Ty(), " + utostr(Lanes) + | |
| ")"; | |
| } | |
| static bool classof(const Type *T) { | |
| return T->typeKind() == TypeKind::Predicate; | |
| } | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // Class to facilitate merging together the code generation for many intrinsics | |
| // by means of varying a few constant or type parameters. | |
| // | |
| // Most obviously, the intrinsics in a single parametrised family will have | |
| // code generation sequences that only differ in a type or two, e.g. vaddq_s8 | |
| // and vaddq_u16 will look the same apart from putting a different vector type | |
| // in the call to CGM.getIntrinsic(). But also, completely different intrinsics | |
| // will often code-generate in the same way, with only a different choice of | |
| // _which_ IR intrinsic they lower to (e.g. vaddq_m_s8 and vmulq_m_s8), but | |
| // marshalling the arguments and return values of the IR intrinsic in exactly | |
| // the same way. And others might differ only in some other kind of constant, | |
| // such as a lane index. | |
| // | |
| // So, when we generate the IR-building code for all these intrinsics, we keep | |
| // track of every value that could possibly be pulled out of the code and | |
| // stored ahead of time in a local variable. Then we group together intrinsics | |
| // by textual equivalence of the code that would result if _all_ those | |
| // parameters were stored in local variables. That gives us maximal sets that | |
| // can be implemented by a single piece of IR-building code by changing | |
| // parameter values ahead of time. | |
| // | |
| // After we've done that, we do a second pass in which we only allocate _some_ | |
| // of the parameters into local variables, by tracking which ones have the same | |
| // values as each other (so that a single variable can be reused) and which | |
| // ones are the same across the whole set (so that no variable is needed at | |
| // all). | |
| // | |
| // Hence the class below. Its allocParam method is invoked during code | |
| // generation by every method of a Result subclass (see below) that wants to | |
| // give it the opportunity to pull something out into a switchable parameter. | |
| // It returns a variable name for the parameter, or (if it's being used in the | |
| // second pass once we've decided that some parameters don't need to be stored | |
| // in variables after all) it might just return the input expression unchanged. | |
| struct CodeGenParamAllocator { | |
| // Accumulated during code generation | |
| std::vector<std::string> *ParamTypes = nullptr; | |
| std::vector<std::string> *ParamValues = nullptr; | |
| // Provided ahead of time in pass 2, to indicate which parameters are being | |
| // assigned to what. This vector contains an entry for each call to | |
| // allocParam expected during code gen (which we counted up in pass 1), and | |
| // indicates the number of the parameter variable that should be returned, or | |
| // -1 if this call shouldn't allocate a parameter variable at all. | |
| // | |
| // We rely on the recursive code generation working identically in passes 1 | |
| // and 2, so that the same list of calls to allocParam happen in the same | |
| // order. That guarantees that the parameter numbers recorded in pass 1 will | |
| // match the entries in this vector that store what EmitterBase::EmitBuiltinCG | |
| // decided to do about each one in pass 2. | |
| std::vector<int> *ParamNumberMap = nullptr; | |
| // Internally track how many things we've allocated | |
| unsigned nparams = 0; | |
| std::string allocParam(StringRef Type, StringRef Value) { | |
| unsigned ParamNumber; | |
| if (!ParamNumberMap) { | |
| // In pass 1, unconditionally assign a new parameter variable to every | |
| // value we're asked to process. | |
| ParamNumber = nparams++; | |
| } else { | |
| // In pass 2, consult the map provided by the caller to find out which | |
| // variable we should be keeping things in. | |
| int MapValue = (*ParamNumberMap)[nparams++]; | |
| if (MapValue < 0) | |
| return std::string(Value); | |
| ParamNumber = MapValue; | |
| } | |
| // If we've allocated a new parameter variable for the first time, store | |
| // its type and value to be retrieved after codegen. | |
| if (ParamTypes && ParamTypes->size() == ParamNumber) | |
| ParamTypes->push_back(std::string(Type)); | |
| if (ParamValues && ParamValues->size() == ParamNumber) | |
| ParamValues->push_back(std::string(Value)); | |
| // Unimaginative naming scheme for parameter variables. | |
| return "Param" + utostr(ParamNumber); | |
| } | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // System of classes that represent all the intermediate values used during | |
| // code-generation for an intrinsic. | |
| // | |
| // The base class 'Result' can represent a value of the LLVM type 'Value', or | |
| // sometimes 'Address' (for loads/stores, including an alignment requirement). | |
| // | |
| // In the case where the Tablegen provides a value in the codegen dag as a | |
| // plain integer literal, the Result object we construct here will be one that | |
| // returns true from hasIntegerConstantValue(). This allows the generated C++ | |
| // code to use the constant directly in contexts which can take a literal | |
| // integer, such as Builder.CreateExtractValue(thing, 1), without going to the | |
| // effort of calling llvm::ConstantInt::get() and then pulling the constant | |
| // back out of the resulting llvm:Value later. | |
| class Result { | |
| public: | |
| // Convenient shorthand for the pointer type we'll be using everywhere. | |
| using Ptr = std::shared_ptr<Result>; | |
| private: | |
| Ptr Predecessor; | |
| std::string VarName; | |
| bool VarNameUsed = false; | |
| unsigned Visited = 0; | |
| public: | |
| virtual ~Result() = default; | |
| using Scope = std::map<std::string, Ptr, std::less<>>; | |
| virtual void genCode(raw_ostream &OS, CodeGenParamAllocator &) const = 0; | |
| virtual bool hasIntegerConstantValue() const { return false; } | |
| virtual uint32_t integerConstantValue() const { return 0; } | |
| virtual bool hasIntegerValue() const { return false; } | |
| virtual std::string getIntegerValue(const std::string &) { | |
| llvm_unreachable("non-working Result::getIntegerValue called"); | |
| } | |
| virtual std::string typeName() const { return "Value *"; } | |
| // Mostly, when a code-generation operation has a dependency on prior | |
| // operations, it's because it uses the output values of those operations as | |
| // inputs. But there's one exception, which is the use of 'seq' in Tablegen | |
| // to indicate that operations have to be performed in sequence regardless of | |
| // whether they use each others' output values. | |
| // | |
| // So, the actual generation of code is done by depth-first search, using the | |
| // prerequisites() method to get a list of all the other Results that have to | |
| // be computed before this one. That method divides into the 'predecessor', | |
| // set by setPredecessor() while processing a 'seq' dag node, and the list | |
| // returned by 'morePrerequisites', which each subclass implements to return | |
| // a list of the Results it uses as input to whatever its own computation is | |
| // doing. | |
| virtual void morePrerequisites(std::vector<Ptr> &output) const {} | |
| std::vector<Ptr> prerequisites() const { | |
| std::vector<Ptr> ToRet; | |
| if (Predecessor) | |
| ToRet.push_back(Predecessor); | |
| morePrerequisites(ToRet); | |
| return ToRet; | |
| } | |
| void setPredecessor(Ptr p) { | |
| // If the user has nested one 'seq' node inside another, and this | |
| // method is called on the return value of the inner 'seq' (i.e. | |
| // the final item inside it), then we can't link _this_ node to p, | |
| // because it already has a predecessor. Instead, walk the chain | |
| // until we find the first item in the inner seq, and link that to | |
| // p, so that nesting seqs has the obvious effect of linking | |
| // everything together into one long sequential chain. | |
| Result *r = this; | |
| while (r->Predecessor) | |
| r = r->Predecessor.get(); | |
| r->Predecessor = p; | |
| } | |
| // Each Result will be assigned a variable name in the output code, but not | |
| // all those variable names will actually be used (e.g. the return value of | |
| // Builder.CreateStore has void type, so nobody will want to refer to it). To | |
| // prevent annoying compiler warnings, we track whether each Result's | |
| // variable name was ever actually mentioned in subsequent statements, so | |
| // that it can be left out of the final generated code. | |
| std::string varname() { | |
| VarNameUsed = true; | |
| return VarName; | |
| } | |
| void setVarname(const StringRef s) { VarName = std::string(s); } | |
| bool varnameUsed() const { return VarNameUsed; } | |
| // Emit code to generate this result as a Value *. | |
| virtual std::string asValue() { | |
| return varname(); | |
| } | |
| // Code generation happens in multiple passes. This method tracks whether a | |
| // Result has yet been visited in a given pass, without the need for a | |
| // tedious loop in between passes that goes through and resets a 'visited' | |
| // flag back to false: you just set Pass=1 the first time round, and Pass=2 | |
| // the second time. | |
| bool needsVisiting(unsigned Pass) { | |
| bool ToRet = Visited < Pass; | |
| Visited = Pass; | |
| return ToRet; | |
| } | |
| }; | |
| // Result subclass that retrieves one of the arguments to the clang builtin | |
| // function. In cases where the argument has pointer type, we call | |
| // EmitPointerWithAlignment and store the result in a variable of type Address, | |
| // so that load and store IR nodes can know the right alignment. Otherwise, we | |
| // call EmitScalarExpr. | |
| // | |
| // There are aggregate parameters in the MVE intrinsics API, but we don't deal | |
| // with them in this Tablegen back end: they only arise in the vld2q/vld4q and | |
| // vst2q/vst4q family, which is few enough that we just write the code by hand | |
| // for those in CGBuiltin.cpp. | |
| class BuiltinArgResult : public Result { | |
| public: | |
| unsigned ArgNum; | |
| bool AddressType; | |
| bool Immediate; | |
| BuiltinArgResult(unsigned ArgNum, bool AddressType, bool Immediate) | |
| : ArgNum(ArgNum), AddressType(AddressType), Immediate(Immediate) {} | |
| void genCode(raw_ostream &OS, CodeGenParamAllocator &) const override { | |
| OS << (AddressType ? "EmitPointerWithAlignment" : "EmitScalarExpr") | |
| << "(E->getArg(" << ArgNum << "))"; | |
| } | |
| std::string typeName() const override { | |
| return AddressType ? "Address" : Result::typeName(); | |
| } | |
| // Emit code to generate this result as a Value *. | |
| std::string asValue() override { | |
| if (AddressType) | |
| return "(" + varname() + ".emitRawPointer(*this))"; | |
| return Result::asValue(); | |
| } | |
| bool hasIntegerValue() const override { return Immediate; } | |
| std::string getIntegerValue(const std::string &IntType) override { | |
| return "GetIntegerConstantValue<" + IntType + ">(E->getArg(" + | |
| utostr(ArgNum) + "), getContext())"; | |
| } | |
| }; | |
| // Result subclass for an integer literal appearing in Tablegen. This may need | |
| // to be turned into an llvm::Result by means of llvm::ConstantInt::get(), or | |
| // it may be used directly as an integer, depending on which IRBuilder method | |
| // it's being passed to. | |
| class IntLiteralResult : public Result { | |
| public: | |
| const ScalarType *IntegerType; | |
| uint32_t IntegerValue; | |
| IntLiteralResult(const ScalarType *IntegerType, uint32_t IntegerValue) | |
| : IntegerType(IntegerType), IntegerValue(IntegerValue) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << "llvm::ConstantInt::get(" | |
| << ParamAlloc.allocParam("llvm::Type *", IntegerType->llvmName()) | |
| << ", "; | |
| OS << ParamAlloc.allocParam(IntegerType->cName(), utostr(IntegerValue)) | |
| << ")"; | |
| } | |
| bool hasIntegerConstantValue() const override { return true; } | |
| uint32_t integerConstantValue() const override { return IntegerValue; } | |
| }; | |
| // Result subclass representing a cast between different integer types. We use | |
| // our own ScalarType abstraction as the representation of the target type, | |
| // which gives both size and signedness. | |
| class IntCastResult : public Result { | |
| public: | |
| const ScalarType *IntegerType; | |
| Ptr V; | |
| IntCastResult(const ScalarType *IntegerType, Ptr V) | |
| : IntegerType(IntegerType), V(V) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << "Builder.CreateIntCast(" << V->varname() << ", " | |
| << ParamAlloc.allocParam("llvm::Type *", IntegerType->llvmName()) << ", " | |
| << ParamAlloc.allocParam("bool", | |
| IntegerType->kind() == ScalarTypeKind::SignedInt | |
| ? "true" | |
| : "false") | |
| << ")"; | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| output.push_back(V); | |
| } | |
| }; | |
| // Result subclass representing a cast between different pointer types. | |
| class PointerCastResult : public Result { | |
| public: | |
| const PointerType *PtrType; | |
| Ptr V; | |
| PointerCastResult(const PointerType *PtrType, Ptr V) | |
| : PtrType(PtrType), V(V) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << "Builder.CreatePointerCast(" << V->asValue() << ", " | |
| << ParamAlloc.allocParam("llvm::Type *", PtrType->llvmName()) << ")"; | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| output.push_back(V); | |
| } | |
| }; | |
| // Result subclass representing a call to an IRBuilder method. Each IRBuilder | |
| // method we want to use will have a Tablegen record giving the method name and | |
| // describing any important details of how to call it, such as whether a | |
| // particular argument should be an integer constant instead of an llvm::Value. | |
| class IRBuilderResult : public Result { | |
| public: | |
| StringRef CallPrefix; | |
| std::vector<Ptr> Args; | |
| std::set<unsigned> AddressArgs; | |
| std::map<unsigned, std::string> IntegerArgs; | |
| IRBuilderResult(StringRef CallPrefix, const std::vector<Ptr> &Args, | |
| const std::set<unsigned> &AddressArgs, | |
| const std::map<unsigned, std::string> &IntegerArgs) | |
| : CallPrefix(CallPrefix), Args(Args), AddressArgs(AddressArgs), | |
| IntegerArgs(IntegerArgs) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << CallPrefix; | |
| const char *Sep = ""; | |
| for (unsigned i = 0, e = Args.size(); i < e; ++i) { | |
| Ptr Arg = Args[i]; | |
| auto it = IntegerArgs.find(i); | |
| OS << Sep; | |
| Sep = ", "; | |
| if (it != IntegerArgs.end()) { | |
| if (Arg->hasIntegerConstantValue()) | |
| OS << "static_cast<" << it->second << ">(" | |
| << ParamAlloc.allocParam(it->second, | |
| utostr(Arg->integerConstantValue())) | |
| << ")"; | |
| else if (Arg->hasIntegerValue()) | |
| OS << ParamAlloc.allocParam(it->second, | |
| Arg->getIntegerValue(it->second)); | |
| } else { | |
| OS << Arg->varname(); | |
| } | |
| } | |
| OS << ")"; | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| for (unsigned i = 0, e = Args.size(); i < e; ++i) { | |
| Ptr Arg = Args[i]; | |
| if (IntegerArgs.find(i) != IntegerArgs.end()) | |
| continue; | |
| output.push_back(Arg); | |
| } | |
| } | |
| }; | |
| // Result subclass representing making an Address out of a Value. | |
| class AddressResult : public Result { | |
| public: | |
| Ptr Arg; | |
| const Type *Ty; | |
| unsigned Align; | |
| AddressResult(Ptr Arg, const Type *Ty, unsigned Align) | |
| : Arg(Arg), Ty(Ty), Align(Align) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << "Address(" << Arg->varname() << ", " << Ty->llvmName() | |
| << ", CharUnits::fromQuantity(" << Align << "))"; | |
| } | |
| std::string typeName() const override { | |
| return "Address"; | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| output.push_back(Arg); | |
| } | |
| }; | |
| // Result subclass representing a call to an IR intrinsic, which we first have | |
| // to look up using an Intrinsic::ID constant and an array of types. | |
| class IRIntrinsicResult : public Result { | |
| public: | |
| std::string IntrinsicID; | |
| std::vector<const Type *> ParamTypes; | |
| std::vector<Ptr> Args; | |
| IRIntrinsicResult(StringRef IntrinsicID, | |
| const std::vector<const Type *> &ParamTypes, | |
| const std::vector<Ptr> &Args) | |
| : IntrinsicID(std::string(IntrinsicID)), ParamTypes(ParamTypes), | |
| Args(Args) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| std::string IntNo = ParamAlloc.allocParam( | |
| "Intrinsic::ID", "Intrinsic::" + IntrinsicID); | |
| OS << "Builder.CreateCall(CGM.getIntrinsic(" << IntNo; | |
| if (!ParamTypes.empty()) { | |
| OS << ", {"; | |
| const char *Sep = ""; | |
| for (auto T : ParamTypes) { | |
| OS << Sep << ParamAlloc.allocParam("llvm::Type *", T->llvmName()); | |
| Sep = ", "; | |
| } | |
| OS << "}"; | |
| } | |
| OS << "), {"; | |
| const char *Sep = ""; | |
| for (auto Arg : Args) { | |
| OS << Sep << Arg->asValue(); | |
| Sep = ", "; | |
| } | |
| OS << "})"; | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| llvm::append_range(output, Args); | |
| } | |
| }; | |
| // Result subclass that generates | |
| // Builder.getIsFPConstrained() ? <Standard> : <StrictFp> | |
| class StrictFpAltResult : public Result { | |
| public: | |
| Ptr Standard; | |
| Ptr StrictFp; | |
| StrictFpAltResult(Ptr Standard, Ptr StrictFp) | |
| : Standard(Standard), StrictFp(StrictFp) {} | |
| void genCode(raw_ostream &OS, | |
| CodeGenParamAllocator &ParamAlloc) const override { | |
| OS << "!Builder.getIsFPConstrained() ? "; | |
| Standard->genCode(OS, ParamAlloc); | |
| OS << " : "; | |
| StrictFp->genCode(OS, ParamAlloc); | |
| } | |
| void morePrerequisites(std::vector<Ptr> &output) const override { | |
| Standard->morePrerequisites(output); | |
| } | |
| }; | |
| // Result subclass that specifies a type, for use in IRBuilder operations such | |
| // as CreateBitCast that take a type argument. | |
| class TypeResult : public Result { | |
| public: | |
| const Type *T; | |
| TypeResult(const Type *T) : T(T) {} | |
| void genCode(raw_ostream &OS, CodeGenParamAllocator &) const override { | |
| OS << T->llvmName(); | |
| } | |
| std::string typeName() const override { | |
| return "llvm::Type *"; | |
| } | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // Class that describes a single ACLE intrinsic. | |
| // | |
| // A Tablegen record will typically describe more than one ACLE intrinsic, by | |
| // means of setting the 'list<Type> Params' field to a list of multiple | |
| // parameter types, so as to define vaddq_{s8,u8,...,f16,f32} all in one go. | |
| // We'll end up with one instance of ACLEIntrinsic for *each* parameter type, | |
| // rather than a single one for all of them. Hence, the constructor takes both | |
| // a Tablegen record and the current value of the parameter type. | |
| class ACLEIntrinsic { | |
| // Structure documenting that one of the intrinsic's arguments is required to | |
| // be a compile-time constant integer, and what constraints there are on its | |
| // value. Used when generating Sema checking code. | |
| struct ImmediateArg { | |
| enum class BoundsType { ExplicitRange, UInt }; | |
| BoundsType boundsType; | |
| int64_t i1, i2; | |
| StringRef ExtraCheckType, ExtraCheckArgs; | |
| const Type *ArgType; | |
| }; | |
| // For polymorphic intrinsics, FullName is the explicit name that uniquely | |
| // identifies this variant of the intrinsic, and ShortName is the name it | |
| // shares with at least one other intrinsic. | |
| std::string ShortName, FullName; | |
| // Name of the architecture extension, used in the Clang builtin name | |
| StringRef BuiltinExtension; | |
| // A very small number of intrinsics _only_ have a polymorphic | |
| // variant (vuninitializedq taking an unevaluated argument). | |
| bool PolymorphicOnly; | |
| // Another rarely-used flag indicating that the builtin doesn't | |
| // evaluate its argument(s) at all. | |
| bool NonEvaluating; | |
| // True if the intrinsic needs only the C header part (no codegen, semantic | |
| // checks, etc). Used for redeclaring MVE intrinsics in the arm_cde.h header. | |
| bool HeaderOnly; | |
| const Type *ReturnType; | |
| std::vector<const Type *> ArgTypes; | |
| std::map<unsigned, ImmediateArg> ImmediateArgs; | |
| Result::Ptr Code; | |
| std::map<std::string, std::string> CustomCodeGenArgs; | |
| // Recursive function that does the internals of code generation. | |
| void genCodeDfs(Result::Ptr V, std::list<Result::Ptr> &Used, | |
| unsigned Pass) const { | |
| if (!V->needsVisiting(Pass)) | |
| return; | |
| for (Result::Ptr W : V->prerequisites()) | |
| genCodeDfs(W, Used, Pass); | |
| Used.push_back(V); | |
| } | |
| public: | |
| const std::string &shortName() const { return ShortName; } | |
| const std::string &fullName() const { return FullName; } | |
| StringRef builtinExtension() const { return BuiltinExtension; } | |
| const Type *returnType() const { return ReturnType; } | |
| const std::vector<const Type *> &argTypes() const { return ArgTypes; } | |
| bool requiresFloat() const { | |
| if (ReturnType->requiresFloat()) | |
| return true; | |
| for (const Type *T : ArgTypes) | |
| if (T->requiresFloat()) | |
| return true; | |
| return false; | |
| } | |
| bool requiresMVE() const { | |
| return ReturnType->requiresMVE() || | |
| any_of(ArgTypes, [](const Type *T) { return T->requiresMVE(); }); | |
| } | |
| bool polymorphic() const { return ShortName != FullName; } | |
| bool polymorphicOnly() const { return PolymorphicOnly; } | |
| bool nonEvaluating() const { return NonEvaluating; } | |
| bool headerOnly() const { return HeaderOnly; } | |
| // External entry point for code generation, called from EmitterBase. | |
| void genCode(raw_ostream &OS, CodeGenParamAllocator &ParamAlloc, | |
| unsigned Pass) const { | |
| assert(!headerOnly() && "Called genCode for header-only intrinsic"); | |
| if (!hasCode()) { | |
| for (auto kv : CustomCodeGenArgs) | |
| OS << " " << kv.first << " = " << kv.second << ";\n"; | |
| OS << " break; // custom code gen\n"; | |
| return; | |
| } | |
| std::list<Result::Ptr> Used; | |
| genCodeDfs(Code, Used, Pass); | |
| unsigned varindex = 0; | |
| for (Result::Ptr V : Used) | |
| if (V->varnameUsed()) | |
| V->setVarname("Val" + utostr(varindex++)); | |
| for (Result::Ptr V : Used) { | |
| OS << " "; | |
| if (V == Used.back()) { | |
| assert(!V->varnameUsed()); | |
| OS << "return "; // FIXME: what if the top-level thing is void? | |
| } else if (V->varnameUsed()) { | |
| std::string Type = V->typeName(); | |
| OS << V->typeName(); | |
| if (!StringRef(Type).ends_with("*")) | |
| OS << " "; | |
| OS << V->varname() << " = "; | |
| } | |
| V->genCode(OS, ParamAlloc); | |
| OS << ";\n"; | |
| } | |
| } | |
| bool hasCode() const { return Code != nullptr; } | |
| static std::string signedHexLiteral(const APInt &iOrig) { | |
| APInt i = iOrig.trunc(64); | |
| SmallString<40> s; | |
| i.toString(s, 16, true, true); | |
| return std::string(s); | |
| } | |
| std::string genSema() const { | |
| assert(!headerOnly() && "Called genSema for header-only intrinsic"); | |
| std::vector<std::string> SemaChecks; | |
| for (const auto &kv : ImmediateArgs) { | |
| const ImmediateArg &IA = kv.second; | |
| APInt lo(128, 0), hi(128, 0); | |
| switch (IA.boundsType) { | |
| case ImmediateArg::BoundsType::ExplicitRange: | |
| lo = IA.i1; | |
| hi = IA.i2; | |
| break; | |
| case ImmediateArg::BoundsType::UInt: | |
| lo = 0; | |
| hi = APInt::getMaxValue(IA.i1).zext(128); | |
| break; | |
| } | |
| std::string Index = utostr(kv.first); | |
| // Emit a range check if the legal range of values for the | |
| // immediate is smaller than the _possible_ range of values for | |
| // its type. | |
| unsigned ArgTypeBits = IA.ArgType->sizeInBits(); | |
| APInt ArgTypeRange = APInt::getMaxValue(ArgTypeBits).zext(128); | |
| APInt ActualRange = (hi - lo).trunc(64).sext(128); | |
| if (ActualRange.ult(ArgTypeRange)) | |
| SemaChecks.push_back("SemaRef.BuiltinConstantArgRange(TheCall, " + | |
| Index + ", " + signedHexLiteral(lo) + ", " + | |
| signedHexLiteral(hi) + ")"); | |
| if (!IA.ExtraCheckType.empty()) { | |
| std::string Suffix; | |
| if (!IA.ExtraCheckArgs.empty()) { | |
| std::string tmp; | |
| StringRef Arg = IA.ExtraCheckArgs; | |
| if (Arg == "!lanesize") { | |
| tmp = utostr(IA.ArgType->sizeInBits()); | |
| Arg = tmp; | |
| } | |
| Suffix = (Twine(", ") + Arg).str(); | |
| } | |
| SemaChecks.push_back((Twine("SemaRef.BuiltinConstantArg") + | |
| IA.ExtraCheckType + "(TheCall, " + Index + | |
| Suffix + ")") | |
| .str()); | |
| } | |
| assert(!SemaChecks.empty()); | |
| } | |
| if (SemaChecks.empty()) | |
| return ""; | |
| return join(std::begin(SemaChecks), std::end(SemaChecks), | |
| " ||\n ") + | |
| ";\n"; | |
| } | |
| ACLEIntrinsic(EmitterBase &ME, const Record *R, const Type *Param); | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // The top-level class that holds all the state from analyzing the entire | |
| // Tablegen input. | |
| class EmitterBase { | |
| protected: | |
| // EmitterBase holds a collection of all the types we've instantiated. | |
| VoidType Void; | |
| std::map<std::string, std::unique_ptr<ScalarType>> ScalarTypes; | |
| std::map<std::tuple<ScalarTypeKind, unsigned, unsigned>, | |
| std::unique_ptr<VectorType>> | |
| VectorTypes; | |
| std::map<std::pair<std::string, unsigned>, std::unique_ptr<MultiVectorType>> | |
| MultiVectorTypes; | |
| std::map<unsigned, std::unique_ptr<PredicateType>> PredicateTypes; | |
| std::map<std::string, std::unique_ptr<PointerType>> PointerTypes; | |
| // And all the ACLEIntrinsic instances we've created. | |
| std::map<std::string, std::unique_ptr<ACLEIntrinsic>> ACLEIntrinsics; | |
| public: | |
| // Methods to create a Type object, or return the right existing one from the | |
| // maps stored in this object. | |
| const VoidType *getVoidType() { return &Void; } | |
| const ScalarType *getScalarType(StringRef Name) { | |
| return ScalarTypes[std::string(Name)].get(); | |
| } | |
| const ScalarType *getScalarType(const Record *R) { | |
| return getScalarType(R->getName()); | |
| } | |
| const VectorType *getVectorType(const ScalarType *ST, unsigned Lanes) { | |
| std::tuple<ScalarTypeKind, unsigned, unsigned> key(ST->kind(), | |
| ST->sizeInBits(), Lanes); | |
| auto [It, Inserted] = VectorTypes.try_emplace(key); | |
| if (Inserted) | |
| It->second = std::make_unique<VectorType>(ST, Lanes); | |
| return It->second.get(); | |
| } | |
| const VectorType *getVectorType(const ScalarType *ST) { | |
| return getVectorType(ST, 128 / ST->sizeInBits()); | |
| } | |
| const MultiVectorType *getMultiVectorType(unsigned Registers, | |
| const VectorType *VT) { | |
| std::pair<std::string, unsigned> key(VT->cNameBase(), Registers); | |
| auto [It, Inserted] = MultiVectorTypes.try_emplace(key); | |
| if (Inserted) | |
| It->second = std::make_unique<MultiVectorType>(Registers, VT); | |
| return It->second.get(); | |
| } | |
| const PredicateType *getPredicateType(unsigned Lanes) { | |
| unsigned key = Lanes; | |
| auto [It, Inserted] = PredicateTypes.try_emplace(key); | |
| if (Inserted) | |
| It->second = std::make_unique<PredicateType>(Lanes); | |
| return It->second.get(); | |
| } | |
| const PointerType *getPointerType(const Type *T, bool Const) { | |
| PointerType PT(T, Const); | |
| std::string key = PT.cName(); | |
| auto [It, Inserted] = PointerTypes.try_emplace(key); | |
| if (Inserted) | |
| It->second = std::make_unique<PointerType>(PT); | |
| return It->second.get(); | |
| } | |
| // Methods to construct a type from various pieces of Tablegen. These are | |
| // always called in the context of setting up a particular ACLEIntrinsic, so | |
| // there's always an ambient parameter type (because we're iterating through | |
| // the Params list in the Tablegen record for the intrinsic), which is used | |
| // to expand Tablegen classes like 'Vector' which mean something different in | |
| // each member of a parametric family. | |
| const Type *getType(const Record *R, const Type *Param); | |
| const Type *getType(const DagInit *D, const Type *Param); | |
| const Type *getType(const Init *I, const Type *Param); | |
| // Functions that translate the Tablegen representation of an intrinsic's | |
| // code generation into a collection of Value objects (which will then be | |
| // reprocessed to read out the actual C++ code included by CGBuiltin.cpp). | |
| Result::Ptr getCodeForDag(const DagInit *D, const Result::Scope &Scope, | |
| const Type *Param); | |
| Result::Ptr getCodeForDagArg(const DagInit *D, unsigned ArgNum, | |
| const Result::Scope &Scope, const Type *Param); | |
| Result::Ptr getCodeForArg(unsigned ArgNum, const Type *ArgType, bool Promote, | |
| bool Immediate); | |
| void GroupSemaChecks(std::map<std::string, std::set<std::string>> &Checks); | |
| // Constructor and top-level functions. | |
| EmitterBase(const RecordKeeper &Records); | |
| virtual ~EmitterBase() = default; | |
| virtual void EmitHeader(raw_ostream &OS) = 0; | |
| virtual void EmitBuiltinDef(raw_ostream &OS) = 0; | |
| virtual void EmitBuiltinSema(raw_ostream &OS) = 0; | |
| void EmitBuiltinCG(raw_ostream &OS); | |
| void EmitBuiltinAliases(raw_ostream &OS); | |
| }; | |
| const Type *EmitterBase::getType(const Init *I, const Type *Param) { | |
| if (const auto *Dag = dyn_cast<DagInit>(I)) | |
| return getType(Dag, Param); | |
| if (const auto *Def = dyn_cast<DefInit>(I)) | |
| return getType(Def->getDef(), Param); | |
| PrintFatalError("Could not convert this value into a type"); | |
| } | |
| const Type *EmitterBase::getType(const Record *R, const Type *Param) { | |
| // Pass to a subfield of any wrapper records. We don't expect more than one | |
| // of these: immediate operands are used as plain numbers rather than as | |
| // llvm::Value, so it's meaningless to promote their type anyway. | |
| if (R->isSubClassOf("Immediate")) | |
| R = R->getValueAsDef("type"); | |
| else if (R->isSubClassOf("unpromoted")) | |
| R = R->getValueAsDef("underlying_type"); | |
| if (R->getName() == "Void") | |
| return getVoidType(); | |
| if (R->isSubClassOf("PrimitiveType")) | |
| return getScalarType(R); | |
| if (R->isSubClassOf("ComplexType")) | |
| return getType(R->getValueAsDag("spec"), Param); | |
| PrintFatalError(R->getLoc(), "Could not convert this record into a type"); | |
| } | |
| const Type *EmitterBase::getType(const DagInit *D, const Type *Param) { | |
| // The meat of the getType system: types in the Tablegen are represented by a | |
| // dag whose operators select sub-cases of this function. | |
| const Record *Op = cast<DefInit>(D->getOperator())->getDef(); | |
| if (!Op->isSubClassOf("ComplexTypeOp")) | |
| PrintFatalError( | |
| "Expected ComplexTypeOp as dag operator in type expression"); | |
| if (Op->getName() == "CTO_Parameter") { | |
| if (isa<VoidType>(Param)) | |
| PrintFatalError("Parametric type in unparametrised context"); | |
| return Param; | |
| } | |
| if (Op->getName() == "CTO_Vec") { | |
| const Type *Element = getType(D->getArg(0), Param); | |
| if (D->getNumArgs() == 1) { | |
| return getVectorType(cast<ScalarType>(Element)); | |
| } else { | |
| const Type *ExistingVector = getType(D->getArg(1), Param); | |
| return getVectorType(cast<ScalarType>(Element), | |
| cast<VectorType>(ExistingVector)->lanes()); | |
| } | |
| } | |
| if (Op->getName() == "CTO_Pred") { | |
| const Type *Element = getType(D->getArg(0), Param); | |
| return getPredicateType(128 / Element->sizeInBits()); | |
| } | |
| if (Op->isSubClassOf("CTO_Tuple")) { | |
| unsigned Registers = Op->getValueAsInt("n"); | |
| const Type *Element = getType(D->getArg(0), Param); | |
| return getMultiVectorType(Registers, cast<VectorType>(Element)); | |
| } | |
| if (Op->isSubClassOf("CTO_Pointer")) { | |
| const Type *Pointee = getType(D->getArg(0), Param); | |
| return getPointerType(Pointee, Op->getValueAsBit("const")); | |
| } | |
| if (Op->getName() == "CTO_CopyKind") { | |
| const ScalarType *STSize = cast<ScalarType>(getType(D->getArg(0), Param)); | |
| const ScalarType *STKind = cast<ScalarType>(getType(D->getArg(1), Param)); | |
| for (const auto &kv : ScalarTypes) { | |
| const ScalarType *RT = kv.second.get(); | |
| if (RT->kind() == STKind->kind() && RT->sizeInBits() == STSize->sizeInBits()) | |
| return RT; | |
| } | |
| PrintFatalError("Cannot find a type to satisfy CopyKind"); | |
| } | |
| if (Op->isSubClassOf("CTO_ScaleSize")) { | |
| const ScalarType *STKind = cast<ScalarType>(getType(D->getArg(0), Param)); | |
| int Num = Op->getValueAsInt("num"), Denom = Op->getValueAsInt("denom"); | |
| unsigned DesiredSize = STKind->sizeInBits() * Num / Denom; | |
| for (const auto &kv : ScalarTypes) { | |
| const ScalarType *RT = kv.second.get(); | |
| if (RT->kind() == STKind->kind() && RT->sizeInBits() == DesiredSize) | |
| return RT; | |
| } | |
| PrintFatalError("Cannot find a type to satisfy ScaleSize"); | |
| } | |
| PrintFatalError("Bad operator in type dag expression"); | |
| } | |
| Result::Ptr EmitterBase::getCodeForDag(const DagInit *D, | |
| const Result::Scope &Scope, | |
| const Type *Param) { | |
| const Record *Op = cast<DefInit>(D->getOperator())->getDef(); | |
| if (Op->getName() == "seq") { | |
| Result::Scope SubScope = Scope; | |
| Result::Ptr PrevV = nullptr; | |
| for (unsigned i = 0, e = D->getNumArgs(); i < e; ++i) { | |
| // We don't use getCodeForDagArg here, because the argument name | |
| // has different semantics in a seq | |
| Result::Ptr V = | |
| getCodeForDag(cast<DagInit>(D->getArg(i)), SubScope, Param); | |
| StringRef ArgName = D->getArgNameStr(i); | |
| if (!ArgName.empty()) | |
| SubScope[std::string(ArgName)] = V; | |
| if (PrevV) | |
| V->setPredecessor(PrevV); | |
| PrevV = V; | |
| } | |
| return PrevV; | |
| } else if (Op->isSubClassOf("Type")) { | |
| if (D->getNumArgs() != 1) | |
| PrintFatalError("Type casts should have exactly one argument"); | |
| const Type *CastType = getType(Op, Param); | |
| Result::Ptr Arg = getCodeForDagArg(D, 0, Scope, Param); | |
| if (const auto *ST = dyn_cast<ScalarType>(CastType)) { | |
| if (!ST->requiresFloat()) { | |
| if (Arg->hasIntegerConstantValue()) | |
| return std::make_shared<IntLiteralResult>( | |
| ST, Arg->integerConstantValue()); | |
| else | |
| return std::make_shared<IntCastResult>(ST, Arg); | |
| } | |
| } else if (const auto *PT = dyn_cast<PointerType>(CastType)) { | |
| return std::make_shared<PointerCastResult>(PT, Arg); | |
| } | |
| PrintFatalError("Unsupported type cast"); | |
| } else if (Op->getName() == "address") { | |
| if (D->getNumArgs() != 2) | |
| PrintFatalError("'address' should have two arguments"); | |
| Result::Ptr Arg = getCodeForDagArg(D, 0, Scope, Param); | |
| const Type *Ty = nullptr; | |
| if (const auto *DI = dyn_cast<DagInit>(D->getArg(0))) | |
| if (auto *PTy = dyn_cast<PointerType>(getType(DI->getOperator(), Param))) | |
| Ty = PTy->getPointeeType(); | |
| if (!Ty) | |
| PrintFatalError("'address' pointer argument should be a pointer"); | |
| unsigned Alignment; | |
| if (const auto *II = dyn_cast<IntInit>(D->getArg(1))) { | |
| Alignment = II->getValue(); | |
| } else { | |
| PrintFatalError("'address' alignment argument should be an integer"); | |
| } | |
| return std::make_shared<AddressResult>(Arg, Ty, Alignment); | |
| } else if (Op->getName() == "unsignedflag") { | |
| if (D->getNumArgs() != 1) | |
| PrintFatalError("unsignedflag should have exactly one argument"); | |
| const Record *TypeRec = cast<DefInit>(D->getArg(0))->getDef(); | |
| if (!TypeRec->isSubClassOf("Type")) | |
| PrintFatalError("unsignedflag's argument should be a type"); | |
| if (const auto *ST = dyn_cast<ScalarType>(getType(TypeRec, Param))) { | |
| return std::make_shared<IntLiteralResult>( | |
| getScalarType("u32"), ST->kind() == ScalarTypeKind::UnsignedInt); | |
| } else { | |
| PrintFatalError("unsignedflag's argument should be a scalar type"); | |
| } | |
| } else if (Op->getName() == "bitsize") { | |
| if (D->getNumArgs() != 1) | |
| PrintFatalError("bitsize should have exactly one argument"); | |
| const Record *TypeRec = cast<DefInit>(D->getArg(0))->getDef(); | |
| if (!TypeRec->isSubClassOf("Type")) | |
| PrintFatalError("bitsize's argument should be a type"); | |
| if (const auto *ST = dyn_cast<ScalarType>(getType(TypeRec, Param))) { | |
| return std::make_shared<IntLiteralResult>(getScalarType("u32"), | |
| ST->sizeInBits()); | |
| } else { | |
| PrintFatalError("bitsize's argument should be a scalar type"); | |
| } | |
| } else { | |
| std::vector<Result::Ptr> Args; | |
| for (unsigned i = 0, e = D->getNumArgs(); i < e; ++i) | |
| Args.push_back(getCodeForDagArg(D, i, Scope, Param)); | |
| auto GenIRBuilderBase = [&](const Record *Op) -> Result::Ptr { | |
| assert(Op->isSubClassOf("IRBuilderBase") && | |
| "Expected IRBuilderBase in GenIRBuilderBase\n"); | |
| std::set<unsigned> AddressArgs; | |
| std::map<unsigned, std::string> IntegerArgs; | |
| for (const Record *sp : Op->getValueAsListOfDefs("special_params")) { | |
| unsigned Index = sp->getValueAsInt("index"); | |
| if (sp->isSubClassOf("IRBuilderAddrParam")) { | |
| AddressArgs.insert(Index); | |
| } else if (sp->isSubClassOf("IRBuilderIntParam")) { | |
| IntegerArgs[Index] = std::string(sp->getValueAsString("type")); | |
| } | |
| } | |
| return std::make_shared<IRBuilderResult>(Op->getValueAsString("prefix"), | |
| Args, AddressArgs, IntegerArgs); | |
| }; | |
| auto GenIRIntBase = [&](const Record *Op) -> Result::Ptr { | |
| assert(Op->isSubClassOf("IRIntBase") && | |
| "Expected IRIntBase in GenIRIntBase\n"); | |
| std::vector<const Type *> ParamTypes; | |
| for (const Record *RParam : Op->getValueAsListOfDefs("params")) | |
| ParamTypes.push_back(getType(RParam, Param)); | |
| std::string IntName = std::string(Op->getValueAsString("intname")); | |
| if (Op->getValueAsBit("appendKind")) | |
| IntName += "_" + toLetter(cast<ScalarType>(Param)->kind()); | |
| return std::make_shared<IRIntrinsicResult>(IntName, ParamTypes, Args); | |
| }; | |
| if (Op->isSubClassOf("IRBuilderBase")) { | |
| return GenIRBuilderBase(Op); | |
| } else if (Op->isSubClassOf("IRIntBase")) { | |
| return GenIRIntBase(Op); | |
| } else if (Op->isSubClassOf("strictFPAlt")) { | |
| auto StardardBuilder = Op->getValueAsDef("standard"); | |
| Result::Ptr Standard = StardardBuilder->isSubClassOf("IRBuilderBase") | |
| ? GenIRBuilderBase(StardardBuilder) | |
| : GenIRIntBase(StardardBuilder); | |
| auto StrictBuilder = Op->getValueAsDef("strictfp"); | |
| Result::Ptr StrictFp = StrictBuilder->isSubClassOf("IRBuilderBase") | |
| ? GenIRBuilderBase(StrictBuilder) | |
| : GenIRIntBase(StrictBuilder); | |
| return std::make_shared<StrictFpAltResult>(Standard, StrictFp); | |
| } else { | |
| PrintFatalError("Unsupported dag node " + Op->getName()); | |
| } | |
| } | |
| } | |
| Result::Ptr EmitterBase::getCodeForDagArg(const DagInit *D, unsigned ArgNum, | |
| const Result::Scope &Scope, | |
| const Type *Param) { | |
| const Init *Arg = D->getArg(ArgNum); | |
| StringRef Name = D->getArgNameStr(ArgNum); | |
| if (!Name.empty()) { | |
| if (!isa<UnsetInit>(Arg)) | |
| PrintFatalError( | |
| "dag operator argument should not have both a value and a name"); | |
| auto it = Scope.find(Name); | |
| if (it == Scope.end()) | |
| PrintFatalError("unrecognized variable name '" + Name + "'"); | |
| return it->second; | |
| } | |
| // Sometimes the Arg is a bit. Prior to multiclass template argument | |
| // checking, integers would sneak through the bit declaration, | |
| // but now they really are bits. | |
| if (const auto *BI = dyn_cast<BitInit>(Arg)) | |
| return std::make_shared<IntLiteralResult>(getScalarType("u32"), | |
| BI->getValue()); | |
| if (const auto *II = dyn_cast<IntInit>(Arg)) | |
| return std::make_shared<IntLiteralResult>(getScalarType("u32"), | |
| II->getValue()); | |
| if (const auto *DI = dyn_cast<DagInit>(Arg)) | |
| return getCodeForDag(DI, Scope, Param); | |
| if (const auto *DI = dyn_cast<DefInit>(Arg)) { | |
| const Record *Rec = DI->getDef(); | |
| if (Rec->isSubClassOf("Type")) { | |
| const Type *T = getType(Rec, Param); | |
| return std::make_shared<TypeResult>(T); | |
| } | |
| } | |
| PrintError("bad DAG argument type for code generation"); | |
| PrintNote("DAG: " + D->getAsString()); | |
| if (const auto *Typed = dyn_cast<TypedInit>(Arg)) | |
| PrintNote("argument type: " + Typed->getType()->getAsString()); | |
| PrintFatalNote("argument number " + Twine(ArgNum) + ": " + Arg->getAsString()); | |
| } | |
| Result::Ptr EmitterBase::getCodeForArg(unsigned ArgNum, const Type *ArgType, | |
| bool Promote, bool Immediate) { | |
| Result::Ptr V = std::make_shared<BuiltinArgResult>( | |
| ArgNum, isa<PointerType>(ArgType), Immediate); | |
| if (Promote) { | |
| if (const auto *ST = dyn_cast<ScalarType>(ArgType)) { | |
| if (ST->isInteger() && ST->sizeInBits() < 32) | |
| V = std::make_shared<IntCastResult>(getScalarType("u32"), V); | |
| } else if (const auto *PT = dyn_cast<PredicateType>(ArgType)) { | |
| V = std::make_shared<IntCastResult>(getScalarType("u32"), V); | |
| V = std::make_shared<IRIntrinsicResult>("arm_mve_pred_i2v", | |
| std::vector<const Type *>{PT}, | |
| std::vector<Result::Ptr>{V}); | |
| } | |
| } | |
| return V; | |
| } | |
| ACLEIntrinsic::ACLEIntrinsic(EmitterBase &ME, const Record *R, | |
| const Type *Param) | |
| : ReturnType(ME.getType(R->getValueAsDef("ret"), Param)) { | |
| // Derive the intrinsic's full name, by taking the name of the | |
| // Tablegen record (or override) and appending the suffix from its | |
| // parameter type. (If the intrinsic is unparametrised, its | |
| // parameter type will be given as Void, which returns the empty | |
| // string for acleSuffix.) | |
| StringRef BaseName = | |
| (R->isSubClassOf("NameOverride") ? R->getValueAsString("basename") | |
| : R->getName()); | |
| StringRef overrideLetter = R->getValueAsString("overrideKindLetter"); | |
| FullName = | |
| (Twine(BaseName) + Param->acleSuffix(std::string(overrideLetter))).str(); | |
| // Derive the intrinsic's polymorphic name, by removing components from the | |
| // full name as specified by its 'pnt' member ('polymorphic name type'), | |
| // which indicates how many type suffixes to remove, and any other piece of | |
| // the name that should be removed. | |
| const Record *PolymorphicNameType = R->getValueAsDef("pnt"); | |
| SmallVector<StringRef, 8> NameParts; | |
| StringRef(FullName).split(NameParts, '_'); | |
| for (unsigned i = 0, e = PolymorphicNameType->getValueAsInt( | |
| "NumTypeSuffixesToDiscard"); | |
| i < e; ++i) | |
| NameParts.pop_back(); | |
| if (!PolymorphicNameType->isValueUnset("ExtraSuffixToDiscard")) { | |
| StringRef ExtraSuffix = | |
| PolymorphicNameType->getValueAsString("ExtraSuffixToDiscard"); | |
| auto it = NameParts.end(); | |
| while (it != NameParts.begin()) { | |
| --it; | |
| if (*it == ExtraSuffix) { | |
| NameParts.erase(it); | |
| break; | |
| } | |
| } | |
| } | |
| ShortName = join(std::begin(NameParts), std::end(NameParts), "_"); | |
| BuiltinExtension = R->getValueAsString("builtinExtension"); | |
| PolymorphicOnly = R->getValueAsBit("polymorphicOnly"); | |
| NonEvaluating = R->getValueAsBit("nonEvaluating"); | |
| HeaderOnly = R->getValueAsBit("headerOnly"); | |
| // Process the intrinsic's argument list. | |
| const DagInit *ArgsDag = R->getValueAsDag("args"); | |
| Result::Scope Scope; | |
| for (unsigned i = 0, e = ArgsDag->getNumArgs(); i < e; ++i) { | |
| const Init *TypeInit = ArgsDag->getArg(i); | |
| bool Promote = true; | |
| if (const auto *TypeDI = dyn_cast<DefInit>(TypeInit)) | |
| if (TypeDI->getDef()->isSubClassOf("unpromoted")) | |
| Promote = false; | |
| // Work out the type of the argument, for use in the function prototype in | |
| // the header file. | |
| const Type *ArgType = ME.getType(TypeInit, Param); | |
| ArgTypes.push_back(ArgType); | |
| // If the argument is a subclass of Immediate, record the details about | |
| // what values it can take, for Sema checking. | |
| bool Immediate = false; | |
| if (const auto *TypeDI = dyn_cast<DefInit>(TypeInit)) { | |
| const Record *TypeRec = TypeDI->getDef(); | |
| if (TypeRec->isSubClassOf("Immediate")) { | |
| Immediate = true; | |
| const Record *Bounds = TypeRec->getValueAsDef("bounds"); | |
| ImmediateArg &IA = ImmediateArgs[i]; | |
| if (Bounds->isSubClassOf("IB_ConstRange")) { | |
| IA.boundsType = ImmediateArg::BoundsType::ExplicitRange; | |
| IA.i1 = Bounds->getValueAsInt("lo"); | |
| IA.i2 = Bounds->getValueAsInt("hi"); | |
| } else if (Bounds->getName() == "IB_UEltValue") { | |
| IA.boundsType = ImmediateArg::BoundsType::UInt; | |
| IA.i1 = Param->sizeInBits(); | |
| } else if (Bounds->getName() == "IB_LaneIndex") { | |
| IA.boundsType = ImmediateArg::BoundsType::ExplicitRange; | |
| IA.i1 = 0; | |
| IA.i2 = 128 / Param->sizeInBits() - 1; | |
| } else if (Bounds->isSubClassOf("IB_EltBit")) { | |
| IA.boundsType = ImmediateArg::BoundsType::ExplicitRange; | |
| IA.i1 = Bounds->getValueAsInt("base"); | |
| const Type *T = ME.getType(Bounds->getValueAsDef("type"), Param); | |
| IA.i2 = IA.i1 + T->sizeInBits() - 1; | |
| } else { | |
| PrintFatalError("unrecognised ImmediateBounds subclass"); | |
| } | |
| IA.ArgType = ArgType; | |
| if (!TypeRec->isValueUnset("extra")) { | |
| IA.ExtraCheckType = TypeRec->getValueAsString("extra"); | |
| if (!TypeRec->isValueUnset("extraarg")) | |
| IA.ExtraCheckArgs = TypeRec->getValueAsString("extraarg"); | |
| } | |
| } | |
| } | |
| // The argument will usually have a name in the arguments dag, which goes | |
| // into the variable-name scope that the code gen will refer to. | |
| StringRef ArgName = ArgsDag->getArgNameStr(i); | |
| if (!ArgName.empty()) | |
| Scope[std::string(ArgName)] = | |
| ME.getCodeForArg(i, ArgType, Promote, Immediate); | |
| } | |
| // Finally, go through the codegen dag and translate it into a Result object | |
| // (with an arbitrary DAG of depended-on Results hanging off it). | |
| const DagInit *CodeDag = R->getValueAsDag("codegen"); | |
| const Record *MainOp = cast<DefInit>(CodeDag->getOperator())->getDef(); | |
| if (MainOp->isSubClassOf("CustomCodegen")) { | |
| // Or, if it's the special case of CustomCodegen, just accumulate | |
| // a list of parameters we're going to assign to variables before | |
| // breaking from the loop. | |
| CustomCodeGenArgs["CustomCodeGenType"] = | |
| (Twine("CustomCodeGen::") + MainOp->getValueAsString("type")).str(); | |
| for (unsigned i = 0, e = CodeDag->getNumArgs(); i < e; ++i) { | |
| StringRef Name = CodeDag->getArgNameStr(i); | |
| if (Name.empty()) { | |
| PrintFatalError("Operands to CustomCodegen should have names"); | |
| } else if (const auto *II = dyn_cast<IntInit>(CodeDag->getArg(i))) { | |
| CustomCodeGenArgs[std::string(Name)] = itostr(II->getValue()); | |
| } else if (const auto *SI = dyn_cast<StringInit>(CodeDag->getArg(i))) { | |
| CustomCodeGenArgs[std::string(Name)] = std::string(SI->getValue()); | |
| } else { | |
| PrintFatalError("Operands to CustomCodegen should be integers"); | |
| } | |
| } | |
| } else { | |
| Code = ME.getCodeForDag(CodeDag, Scope, Param); | |
| } | |
| } | |
| EmitterBase::EmitterBase(const RecordKeeper &Records) { | |
| // Construct the whole EmitterBase. | |
| // First, look up all the instances of PrimitiveType. This gives us the list | |
| // of vector typedefs we have to put in arm_mve.h, and also allows us to | |
| // collect all the useful ScalarType instances into a big list so that we can | |
| // use it for operations such as 'find the unsigned version of this signed | |
| // integer type'. | |
| for (const Record *R : Records.getAllDerivedDefinitions("PrimitiveType")) | |
| ScalarTypes[std::string(R->getName())] = std::make_unique<ScalarType>(R); | |
| // Now go through the instances of Intrinsic, and for each one, iterate | |
| // through its list of type parameters making an ACLEIntrinsic for each one. | |
| for (const Record *R : Records.getAllDerivedDefinitions("Intrinsic")) { | |
| for (const Record *RParam : R->getValueAsListOfDefs("params")) { | |
| const Type *Param = getType(RParam, getVoidType()); | |
| auto Intrinsic = std::make_unique<ACLEIntrinsic>(*this, R, Param); | |
| ACLEIntrinsics[Intrinsic->fullName()] = std::move(Intrinsic); | |
| } | |
| } | |
| } | |
| /// A wrapper on raw_string_ostream that contains its own buffer rather than | |
| /// having to point it at one elsewhere. (In other words, it works just like | |
| /// std::ostringstream; also, this makes it convenient to declare a whole array | |
| /// of them at once.) | |
| /// | |
| /// We have to set this up using multiple inheritance, to ensure that the | |
| /// string member has been constructed before raw_string_ostream's constructor | |
| /// is given a pointer to it. | |
| class string_holder { | |
| protected: | |
| std::string S; | |
| }; | |
| class raw_self_contained_string_ostream : private string_holder, | |
| public raw_string_ostream { | |
| public: | |
| raw_self_contained_string_ostream() : raw_string_ostream(S) {} | |
| }; | |
| const char LLVMLicenseHeader[] = | |
| " *\n" | |
| " *\n" | |
| " * Part of the LLVM Project, under the Apache License v2.0 with LLVM" | |
| " Exceptions.\n" | |
| " * See https://llvm.org/LICENSE.txt for license information.\n" | |
| " * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n" | |
| " *\n" | |
| " *===-----------------------------------------------------------------" | |
| "------===\n" | |
| " */\n" | |
| "\n"; | |
| // Machinery for the grouping of intrinsics by similar codegen. | |
| // | |
| // The general setup is that 'MergeableGroup' stores the things that a set of | |
| // similarly shaped intrinsics have in common: the text of their code | |
| // generation, and the number and type of their parameter variables. | |
| // MergeableGroup is the key in a std::map whose value is a set of | |
| // OutputIntrinsic, which stores the ways in which a particular intrinsic | |
| // specializes the MergeableGroup's generic description: the function name and | |
| // the _values_ of the parameter variables. | |
| struct ComparableStringVector : std::vector<std::string> { | |
| // Infrastructure: a derived class of vector<string> which comes with an | |
| // ordering, so that it can be used as a key in maps and an element in sets. | |
| // There's no requirement on the ordering beyond being deterministic. | |
| bool operator<(const ComparableStringVector &rhs) const { | |
| if (size() != rhs.size()) | |
| return size() < rhs.size(); | |
| for (size_t i = 0, e = size(); i < e; ++i) | |
| if ((*this)[i] != rhs[i]) | |
| return (*this)[i] < rhs[i]; | |
| return false; | |
| } | |
| }; | |
| struct OutputIntrinsic { | |
| const ACLEIntrinsic *Int; | |
| std::string Name; | |
| ComparableStringVector ParamValues; | |
| bool operator<(const OutputIntrinsic &rhs) const { | |
| return std::tie(Name, ParamValues) < std::tie(rhs.Name, rhs.ParamValues); | |
| } | |
| }; | |
| struct MergeableGroup { | |
| std::string Code; | |
| ComparableStringVector ParamTypes; | |
| bool operator<(const MergeableGroup &rhs) const { | |
| return std::tie(Code, ParamTypes) < std::tie(rhs.Code, rhs.ParamTypes); | |
| } | |
| }; | |
| void EmitterBase::EmitBuiltinCG(raw_ostream &OS) { | |
| // Pass 1: generate code for all the intrinsics as if every type or constant | |
| // that can possibly be abstracted out into a parameter variable will be. | |
| // This identifies the sets of intrinsics we'll group together into a single | |
| // piece of code generation. | |
| std::map<MergeableGroup, std::set<OutputIntrinsic>> MergeableGroupsPrelim; | |
| for (const auto &kv : ACLEIntrinsics) { | |
| const ACLEIntrinsic &Int = *kv.second; | |
| if (Int.headerOnly()) | |
| continue; | |
| MergeableGroup MG; | |
| OutputIntrinsic OI; | |
| OI.Int = ∬ | |
| OI.Name = Int.fullName(); | |
| CodeGenParamAllocator ParamAllocPrelim{&MG.ParamTypes, &OI.ParamValues}; | |
| raw_string_ostream OS(MG.Code); | |
| Int.genCode(OS, ParamAllocPrelim, 1); | |
| MergeableGroupsPrelim[MG].insert(OI); | |
| } | |
| // Pass 2: for each of those groups, optimize the parameter variable set by | |
| // eliminating 'parameters' that are the same for all intrinsics in the | |
| // group, and merging together pairs of parameter variables that take the | |
| // same values as each other for all intrinsics in the group. | |
| std::map<MergeableGroup, std::set<OutputIntrinsic>> MergeableGroups; | |
| for (const auto &kv : MergeableGroupsPrelim) { | |
| const MergeableGroup &MG = kv.first; | |
| std::vector<int> ParamNumbers; | |
| std::map<ComparableStringVector, int> ParamNumberMap; | |
| // Loop over the parameters for this group. | |
| for (size_t i = 0, e = MG.ParamTypes.size(); i < e; ++i) { | |
| // Is this parameter the same for all intrinsics in the group? | |
| const OutputIntrinsic &OI_first = *kv.second.begin(); | |
| bool Constant = all_of(kv.second, [&](const OutputIntrinsic &OI) { | |
| return OI.ParamValues[i] == OI_first.ParamValues[i]; | |
| }); | |
| // If so, record it as -1, meaning 'no parameter variable needed'. Then | |
| // the corresponding call to allocParam in pass 2 will not generate a | |
| // variable at all, and just use the value inline. | |
| if (Constant) { | |
| ParamNumbers.push_back(-1); | |
| continue; | |
| } | |
| // Otherwise, make a list of the values this parameter takes for each | |
| // intrinsic, and see if that value vector matches anything we already | |
| // have. We also record the parameter type, so that we don't accidentally | |
| // match up two parameter variables with different types. (Not that | |
| // there's much chance of them having textually equivalent values, but in | |
| // _principle_ it could happen.) | |
| ComparableStringVector key; | |
| key.push_back(MG.ParamTypes[i]); | |
| for (const auto &OI : kv.second) | |
| key.push_back(OI.ParamValues[i]); | |
| // Obtain a new parameter variable if we don't have one. | |
| int ParamNum = | |
| ParamNumberMap.try_emplace(key, ParamNumberMap.size()).first->second; | |
| ParamNumbers.push_back(ParamNum); | |
| } | |
| // Now we're ready to do the pass 2 code generation, which will emit the | |
| // reduced set of parameter variables we've just worked out. | |
| for (const auto &OI_prelim : kv.second) { | |
| const ACLEIntrinsic *Int = OI_prelim.Int; | |
| MergeableGroup MG; | |
| OutputIntrinsic OI; | |
| OI.Int = OI_prelim.Int; | |
| OI.Name = OI_prelim.Name; | |
| CodeGenParamAllocator ParamAlloc{&MG.ParamTypes, &OI.ParamValues, | |
| &ParamNumbers}; | |
| raw_string_ostream OS(MG.Code); | |
| Int->genCode(OS, ParamAlloc, 2); | |
| MergeableGroups[MG].insert(OI); | |
| } | |
| } | |
| // Output the actual C++ code. | |
| for (const auto &kv : MergeableGroups) { | |
| const MergeableGroup &MG = kv.first; | |
| // List of case statements in the main switch on BuiltinID, and an open | |
| // brace. | |
| const char *prefix = ""; | |
| for (const auto &OI : kv.second) { | |
| OS << prefix << "case ARM::BI__builtin_arm_" << OI.Int->builtinExtension() | |
| << "_" << OI.Name << ":"; | |
| prefix = "\n"; | |
| } | |
| OS << " {\n"; | |
| if (!MG.ParamTypes.empty()) { | |
| // If we've got some parameter variables, then emit their declarations... | |
| for (size_t i = 0, e = MG.ParamTypes.size(); i < e; ++i) { | |
| StringRef Type = MG.ParamTypes[i]; | |
| OS << " " << Type; | |
| if (!Type.ends_with("*")) | |
| OS << " "; | |
| OS << " Param" << utostr(i) << ";\n"; | |
| } | |
| // ... and an inner switch on BuiltinID that will fill them in with each | |
| // individual intrinsic's values. | |
| OS << " switch (BuiltinID) {\n"; | |
| for (const auto &OI : kv.second) { | |
| OS << " case ARM::BI__builtin_arm_" << OI.Int->builtinExtension() | |
| << "_" << OI.Name << ":\n"; | |
| for (size_t i = 0, e = MG.ParamTypes.size(); i < e; ++i) | |
| OS << " Param" << utostr(i) << " = static_cast<" | |
| << MG.ParamTypes[i] << ">(" << OI.ParamValues[i] << ");\n"; | |
| OS << " break;\n"; | |
| } | |
| OS << " }\n"; | |
| } | |
| // And finally, output the code, and close the outer pair of braces. (The | |
| // code will always end with a 'return' statement, so we need not insert a | |
| // 'break' here.) | |
| OS << MG.Code << "}\n"; | |
| } | |
| } | |
| void EmitterBase::EmitBuiltinAliases(raw_ostream &OS) { | |
| // Build a sorted table of: | |
| // - intrinsic id number | |
| // - full name | |
| // - polymorphic name or -1 | |
| StringToOffsetTable StringTable; | |
| OS << "static const IntrinToName MapData[] = {\n"; | |
| for (const auto &kv : ACLEIntrinsics) { | |
| const ACLEIntrinsic &Int = *kv.second; | |
| if (Int.headerOnly()) | |
| continue; | |
| int32_t ShortNameOffset = | |
| Int.polymorphic() ? StringTable.GetOrAddStringOffset(Int.shortName()) | |
| : -1; | |
| OS << " { ARM::BI__builtin_arm_" << Int.builtinExtension() << "_" | |
| << Int.fullName() << ", " | |
| << StringTable.GetOrAddStringOffset(Int.fullName()) << ", " | |
| << ShortNameOffset << "},\n"; | |
| } | |
| OS << "};\n\n"; | |
| OS << "ArrayRef<IntrinToName> Map(MapData);\n\n"; | |
| OS << "static const char IntrinNames[] = {\n"; | |
| StringTable.EmitString(OS); | |
| OS << "};\n\n"; | |
| } | |
| void EmitterBase::GroupSemaChecks( | |
| std::map<std::string, std::set<std::string>> &Checks) { | |
| for (const auto &kv : ACLEIntrinsics) { | |
| const ACLEIntrinsic &Int = *kv.second; | |
| if (Int.headerOnly()) | |
| continue; | |
| std::string Check = Int.genSema(); | |
| if (!Check.empty()) | |
| Checks[Check].insert(Int.fullName()); | |
| } | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // The class used for generating arm_mve.h and related Clang bits | |
| // | |
| class MveEmitter : public EmitterBase { | |
| public: | |
| MveEmitter(const RecordKeeper &Records) : EmitterBase(Records) {} | |
| void EmitHeader(raw_ostream &OS) override; | |
| void EmitBuiltinDef(raw_ostream &OS) override; | |
| void EmitBuiltinSema(raw_ostream &OS) override; | |
| }; | |
| void MveEmitter::EmitHeader(raw_ostream &OS) { | |
| // Accumulate pieces of the header file that will be enabled under various | |
| // different combinations of #ifdef. The index into parts[] is made up of | |
| // the following bit flags. | |
| constexpr unsigned Float = 1; | |
| constexpr unsigned UseUserNamespace = 2; | |
| constexpr unsigned NumParts = 4; | |
| raw_self_contained_string_ostream parts[NumParts]; | |
| // Write typedefs for all the required vector types, and a few scalar | |
| // types that don't already have the name we want them to have. | |
| parts[0] << "typedef uint16_t mve_pred16_t;\n"; | |
| parts[Float] << "typedef __fp16 float16_t;\n" | |
| "typedef float float32_t;\n"; | |
| for (const auto &kv : ScalarTypes) { | |
| const ScalarType *ST = kv.second.get(); | |
| if (ST->hasNonstandardName()) | |
| continue; | |
| raw_ostream &OS = parts[ST->requiresFloat() ? Float : 0]; | |
| const VectorType *VT = getVectorType(ST); | |
| OS << "typedef __attribute__((__neon_vector_type__(" << VT->lanes() | |
| << "), __clang_arm_mve_strict_polymorphism)) " << ST->cName() << " " | |
| << VT->cName() << ";\n"; | |
| // Every vector type also comes with a pair of multi-vector types for | |
| // the VLD2 and VLD4 instructions. | |
| for (unsigned n = 2; n <= 4; n += 2) { | |
| const MultiVectorType *MT = getMultiVectorType(n, VT); | |
| OS << "typedef struct { " << VT->cName() << " val[" << n << "]; } " | |
| << MT->cName() << ";\n"; | |
| } | |
| } | |
| parts[0] << "\n"; | |
| parts[Float] << "\n"; | |
| // Write declarations for all the intrinsics. | |
| for (const auto &kv : ACLEIntrinsics) { | |
| const ACLEIntrinsic &Int = *kv.second; | |
| // We generate each intrinsic twice, under its full unambiguous | |
| // name and its shorter polymorphic name (if the latter exists). | |
| for (bool Polymorphic : {false, true}) { | |
| if (Polymorphic && !Int.polymorphic()) | |
| continue; | |
| if (!Polymorphic && Int.polymorphicOnly()) | |
| continue; | |
| // We also generate each intrinsic under a name like __arm_vfooq | |
| // (which is in C language implementation namespace, so it's | |
| // safe to define in any conforming user program) and a shorter | |
| // one like vfooq (which is in user namespace, so a user might | |
| // reasonably have used it for something already). If so, they | |
| // can #define __ARM_MVE_PRESERVE_USER_NAMESPACE before | |
| // including the header, which will suppress the shorter names | |
| // and leave only the implementation-namespace ones. Then they | |
| // have to write __arm_vfooq everywhere, of course. | |
| for (bool UserNamespace : {false, true}) { | |
| raw_ostream &OS = parts[(Int.requiresFloat() ? Float : 0) | | |
| (UserNamespace ? UseUserNamespace : 0)]; | |
| // Make the name of the function in this declaration. | |
| std::string FunctionName = | |
| Polymorphic ? Int.shortName() : Int.fullName(); | |
| if (!UserNamespace) | |
| FunctionName = "__arm_" + FunctionName; | |
| // Make strings for the types involved in the function's | |
| // prototype. | |
| std::string RetTypeName = Int.returnType()->cName(); | |
| if (!StringRef(RetTypeName).ends_with("*")) | |
| RetTypeName += " "; | |
| std::vector<std::string> ArgTypeNames; | |
| for (const Type *ArgTypePtr : Int.argTypes()) | |
| ArgTypeNames.push_back(ArgTypePtr->cName()); | |
| std::string ArgTypesString = | |
| join(std::begin(ArgTypeNames), std::end(ArgTypeNames), ", "); | |
| // Emit the actual declaration. All these functions are | |
| // declared 'static inline' without a body, which is fine | |
| // provided clang recognizes them as builtins, and has the | |
| // effect that this type signature is used in place of the one | |
| // that Builtins.td didn't provide. That's how we can get | |
| // structure types that weren't defined until this header was | |
| // included to be part of the type signature of a builtin that | |
| // was known to clang already. | |
| // | |
| // The declarations use __attribute__(__clang_arm_builtin_alias), | |
| // so that each function declared will be recognized as the | |
| // appropriate MVE builtin in spite of its user-facing name. | |
| // | |
| // (That's better than making them all wrapper functions, | |
| // partly because it avoids any compiler error message citing | |
| // the wrapper function definition instead of the user's code, | |
| // and mostly because some MVE intrinsics have arguments | |
| // required to be compile-time constants, and that property | |
| // can't be propagated through a wrapper function. It can be | |
| // propagated through a macro, but macros can't be overloaded | |
| // on argument types very easily - you have to use _Generic, | |
| // which makes error messages very confusing when the user | |
| // gets it wrong.) | |
| // | |
| // Finally, the polymorphic versions of the intrinsics are | |
| // also defined with __attribute__(overloadable), so that when | |
| // the same name is defined with several type signatures, the | |
| // right thing happens. Each one of the overloaded | |
| // declarations is given a different builtin id, which | |
| // has exactly the effect we want: first clang resolves the | |
| // overload to the right function, then it knows which builtin | |
| // it's referring to, and then the Sema checking for that | |
| // builtin can check further things like the constant | |
| // arguments. | |
| // | |
| // One more subtlety is the newline just before the return | |
| // type name. That's a cosmetic tweak to make the error | |
| // messages legible if the user gets the types wrong in a call | |
| // to a polymorphic function: this way, clang will print just | |
| // the _final_ line of each declaration in the header, to show | |
| // the type signatures that would have been legal. So all the | |
| // confusing machinery with __attribute__ is left out of the | |
| // error message, and the user sees something that's more or | |
| // less self-documenting: "here's a list of actually readable | |
| // type signatures for vfooq(), and here's why each one didn't | |
| // match your call". | |
| OS << "static __inline__ __attribute__((" | |
| << (Polymorphic ? "__overloadable__, " : "") | |
| << "__clang_arm_builtin_alias(__builtin_arm_mve_" << Int.fullName() | |
| << ")))\n" | |
| << RetTypeName << FunctionName << "(" << ArgTypesString << ");\n"; | |
| } | |
| } | |
| } | |
| for (auto &part : parts) | |
| part << "\n"; | |
| // Now we've finished accumulating bits and pieces into the parts[] array. | |
| // Put it all together to write the final output file. | |
| OS << "/*===---- arm_mve.h - ARM MVE intrinsics " | |
| "-----------------------------------===\n" | |
| << LLVMLicenseHeader | |
| << "#ifndef __ARM_MVE_H\n" | |
| "#define __ARM_MVE_H\n" | |
| "\n" | |
| "#if !__ARM_FEATURE_MVE\n" | |
| "#error \"MVE support not enabled\"\n" | |
| "#endif\n" | |
| "\n" | |
| "#include <stdint.h>\n" | |
| "\n" | |
| "#ifdef __cplusplus\n" | |
| "extern \"C\" {\n" | |
| "#endif\n" | |
| "\n"; | |
| for (size_t i = 0; i < NumParts; ++i) { | |
| std::vector<std::string> conditions; | |
| if (i & Float) | |
| conditions.push_back("(__ARM_FEATURE_MVE & 2)"); | |
| if (i & UseUserNamespace) | |
| conditions.push_back("(!defined __ARM_MVE_PRESERVE_USER_NAMESPACE)"); | |
| std::string condition = | |
| join(std::begin(conditions), std::end(conditions), " && "); | |
| if (!condition.empty()) | |
| OS << "#if " << condition << "\n\n"; | |
| OS << parts[i].str(); | |
| if (!condition.empty()) | |
| OS << "#endif /* " << condition << " */\n\n"; | |
| } | |
| OS << "#ifdef __cplusplus\n" | |
| "} /* extern \"C\" */\n" | |
| "#endif\n" | |
| "\n" | |
| "#endif /* __ARM_MVE_H */\n"; | |
| } | |
| void MveEmitter::EmitBuiltinDef(raw_ostream &OS) { | |
| llvm::StringToOffsetTable Table; | |
| Table.GetOrAddStringOffset("n"); | |
| Table.GetOrAddStringOffset("nt"); | |
| Table.GetOrAddStringOffset("ntu"); | |
| Table.GetOrAddStringOffset("vi."); | |
| for (const auto &[_, Int] : ACLEIntrinsics) | |
| Table.GetOrAddStringOffset(Int->fullName()); | |
| std::map<std::string, ACLEIntrinsic *> ShortNameIntrinsics; | |
| for (const auto &[_, Int] : ACLEIntrinsics) { | |
| if (!Int->polymorphic()) | |
| continue; | |
| StringRef Name = Int->shortName(); | |
| if (ShortNameIntrinsics.insert({Name.str(), Int.get()}).second) | |
| Table.GetOrAddStringOffset(Name); | |
| } | |
| OS << "#ifdef GET_MVE_BUILTIN_ENUMERATORS\n"; | |
| for (const auto &[_, Int] : ACLEIntrinsics) { | |
| OS << " BI__builtin_arm_mve_" << Int->fullName() << ",\n"; | |
| } | |
| for (const auto &[Name, _] : ShortNameIntrinsics) { | |
| OS << " BI__builtin_arm_mve_" << Name << ",\n"; | |
| } | |
| OS << "#endif // GET_MVE_BUILTIN_ENUMERATORS\n\n"; | |
| OS << "#ifdef GET_MVE_BUILTIN_STR_TABLE\n"; | |
| Table.EmitStringTableDef(OS, "BuiltinStrings"); | |
| OS << "#endif // GET_MVE_BUILTIN_STR_TABLE\n\n"; | |
| OS << "#ifdef GET_MVE_BUILTIN_INFOS\n"; | |
| for (const auto &[_, Int] : ACLEIntrinsics) { | |
| OS << " Builtin::Info{Builtin::Info::StrOffsets{" | |
| << Table.GetStringOffset(Int->fullName()) << " /* " << Int->fullName() | |
| << " */, " << Table.GetStringOffset("") << ", " | |
| << Table.GetStringOffset("n") << " /* n */}},\n"; | |
| } | |
| for (const auto &[Name, Int] : ShortNameIntrinsics) { | |
| StringRef Attrs = Int->nonEvaluating() ? "ntu" : "nt"; | |
| OS << " Builtin::Info{Builtin::Info::StrOffsets{" | |
| << Table.GetStringOffset(Name) << " /* " << Name << " */, " | |
| << Table.GetStringOffset("vi.") << " /* vi. */, " | |
| << Table.GetStringOffset(Attrs) << " /* " << Attrs << " */}},\n"; | |
| } | |
| OS << "#endif // GET_MVE_BUILTIN_INFOS\n\n"; | |
| } | |
| void MveEmitter::EmitBuiltinSema(raw_ostream &OS) { | |
| std::map<std::string, std::set<std::string>> Checks; | |
| GroupSemaChecks(Checks); | |
| for (const auto &kv : Checks) { | |
| for (StringRef Name : kv.second) | |
| OS << "case ARM::BI__builtin_arm_mve_" << Name << ":\n"; | |
| OS << " return " << kv.first; | |
| } | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // Class that describes an ACLE intrinsic implemented as a macro. | |
| // | |
| // This class is used when the intrinsic is polymorphic in 2 or 3 types, but we | |
| // want to avoid a combinatorial explosion by reinterpreting the arguments to | |
| // fixed types. | |
| class FunctionMacro { | |
| std::vector<StringRef> Params; | |
| StringRef Definition; | |
| public: | |
| FunctionMacro(const Record &R); | |
| const std::vector<StringRef> &getParams() const { return Params; } | |
| StringRef getDefinition() const { return Definition; } | |
| }; | |
| FunctionMacro::FunctionMacro(const Record &R) { | |
| Params = R.getValueAsListOfStrings("params"); | |
| Definition = R.getValueAsString("definition"); | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // The class used for generating arm_cde.h and related Clang bits | |
| // | |
| class CdeEmitter : public EmitterBase { | |
| std::map<StringRef, FunctionMacro> FunctionMacros; | |
| public: | |
| CdeEmitter(const RecordKeeper &Records); | |
| void EmitHeader(raw_ostream &OS) override; | |
| void EmitBuiltinDef(raw_ostream &OS) override; | |
| void EmitBuiltinSema(raw_ostream &OS) override; | |
| }; | |
| CdeEmitter::CdeEmitter(const RecordKeeper &Records) : EmitterBase(Records) { | |
| for (const Record *R : Records.getAllDerivedDefinitions("FunctionMacro")) | |
| FunctionMacros.emplace(R->getName(), FunctionMacro(*R)); | |
| } | |
| void CdeEmitter::EmitHeader(raw_ostream &OS) { | |
| // Accumulate pieces of the header file that will be enabled under various | |
| // different combinations of #ifdef. The index into parts[] is one of the | |
| // following: | |
| constexpr unsigned None = 0; | |
| constexpr unsigned MVE = 1; | |
| constexpr unsigned MVEFloat = 2; | |
| constexpr unsigned NumParts = 3; | |
| raw_self_contained_string_ostream parts[NumParts]; | |
| // Write typedefs for all the required vector types, and a few scalar | |
| // types that don't already have the name we want them to have. | |
| parts[MVE] << "typedef uint16_t mve_pred16_t;\n"; | |
| parts[MVEFloat] << "typedef __fp16 float16_t;\n" | |
| "typedef float float32_t;\n"; | |
| for (const auto &kv : ScalarTypes) { | |
| const ScalarType *ST = kv.second.get(); | |
| if (ST->hasNonstandardName()) | |
| continue; | |
| // We don't have float64x2_t | |
| if (ST->kind() == ScalarTypeKind::Float && ST->sizeInBits() == 64) | |
| continue; | |
| raw_ostream &OS = parts[ST->requiresFloat() ? MVEFloat : MVE]; | |
| const VectorType *VT = getVectorType(ST); | |
| OS << "typedef __attribute__((__neon_vector_type__(" << VT->lanes() | |
| << "), __clang_arm_mve_strict_polymorphism)) " << ST->cName() << " " | |
| << VT->cName() << ";\n"; | |
| } | |
| parts[MVE] << "\n"; | |
| parts[MVEFloat] << "\n"; | |
| // Write declarations for all the intrinsics. | |
| for (const auto &kv : ACLEIntrinsics) { | |
| const ACLEIntrinsic &Int = *kv.second; | |
| // We generate each intrinsic twice, under its full unambiguous | |
| // name and its shorter polymorphic name (if the latter exists). | |
| for (bool Polymorphic : {false, true}) { | |
| if (Polymorphic && !Int.polymorphic()) | |
| continue; | |
| if (!Polymorphic && Int.polymorphicOnly()) | |
| continue; | |
| raw_ostream &OS = | |
| parts[Int.requiresFloat() ? MVEFloat | |
| : Int.requiresMVE() ? MVE : None]; | |
| // Make the name of the function in this declaration. | |
| std::string FunctionName = | |
| "__arm_" + (Polymorphic ? Int.shortName() : Int.fullName()); | |
| // Make strings for the types involved in the function's | |
| // prototype. | |
| std::string RetTypeName = Int.returnType()->cName(); | |
| if (!StringRef(RetTypeName).ends_with("*")) | |
| RetTypeName += " "; | |
| std::vector<std::string> ArgTypeNames; | |
| for (const Type *ArgTypePtr : Int.argTypes()) | |
| ArgTypeNames.push_back(ArgTypePtr->cName()); | |
| std::string ArgTypesString = | |
| join(std::begin(ArgTypeNames), std::end(ArgTypeNames), ", "); | |
| // Emit the actual declaration. See MveEmitter::EmitHeader for detailed | |
| // comments | |
| OS << "static __inline__ __attribute__((" | |
| << (Polymorphic ? "__overloadable__, " : "") | |
| << "__clang_arm_builtin_alias(__builtin_arm_" << Int.builtinExtension() | |
| << "_" << Int.fullName() << ")))\n" | |
| << RetTypeName << FunctionName << "(" << ArgTypesString << ");\n"; | |
| } | |
| } | |
| for (const auto &kv : FunctionMacros) { | |
| StringRef Name = kv.first; | |
| const FunctionMacro &FM = kv.second; | |
| raw_ostream &OS = parts[MVE]; | |
| OS << "#define " | |
| << "__arm_" << Name << "(" << join(FM.getParams(), ", ") << ") " | |
| << FM.getDefinition() << "\n"; | |
| } | |
| for (auto &part : parts) | |
| part << "\n"; | |
| // Now we've finished accumulating bits and pieces into the parts[] array. | |
| // Put it all together to write the final output file. | |
| OS << "/*===---- arm_cde.h - ARM CDE intrinsics " | |
| "-----------------------------------===\n" | |
| << LLVMLicenseHeader | |
| << "#ifndef __ARM_CDE_H\n" | |
| "#define __ARM_CDE_H\n" | |
| "\n" | |
| "#if !__ARM_FEATURE_CDE\n" | |
| "#error \"CDE support not enabled\"\n" | |
| "#endif\n" | |
| "\n" | |
| "#include <stdint.h>\n" | |
| "\n" | |
| "#ifdef __cplusplus\n" | |
| "extern \"C\" {\n" | |
| "#endif\n" | |
| "\n"; | |
| for (size_t i = 0; i < NumParts; ++i) { | |
| std::string condition; | |
| if (i == MVEFloat) | |
| condition = "__ARM_FEATURE_MVE & 2"; | |
| else if (i == MVE) | |
| condition = "__ARM_FEATURE_MVE"; | |
| if (!condition.empty()) | |
| OS << "#if " << condition << "\n\n"; | |
| OS << parts[i].str(); | |
| if (!condition.empty()) | |
| OS << "#endif /* " << condition << " */\n\n"; | |
| } | |
| OS << "#ifdef __cplusplus\n" | |
| "} /* extern \"C\" */\n" | |
| "#endif\n" | |
| "\n" | |
| "#endif /* __ARM_CDE_H */\n"; | |
| } | |
| void CdeEmitter::EmitBuiltinDef(raw_ostream &OS) { | |
| llvm::StringToOffsetTable Table; | |
| Table.GetOrAddStringOffset("ncU"); | |
| for (const auto &[_, Int] : ACLEIntrinsics) | |
| if (!Int->headerOnly()) | |
| Table.GetOrAddStringOffset(Int->fullName()); | |
| OS << "#ifdef GET_CDE_BUILTIN_ENUMERATORS\n"; | |
| for (const auto &[_, Int] : ACLEIntrinsics) | |
| if (!Int->headerOnly()) | |
| OS << " BI__builtin_arm_cde_" << Int->fullName() << ",\n"; | |
| OS << "#endif // GET_CDE_BUILTIN_ENUMERATORS\n\n"; | |
| OS << "#ifdef GET_CDE_BUILTIN_STR_TABLE\n"; | |
| Table.EmitStringTableDef(OS, "BuiltinStrings"); | |
| OS << "#endif // GET_CDE_BUILTIN_STR_TABLE\n\n"; | |
| OS << "#ifdef GET_CDE_BUILTIN_INFOS\n"; | |
| for (const auto &[_, Int] : ACLEIntrinsics) | |
| if (!Int->headerOnly()) | |
| OS << " Builtin::Info{Builtin::Info::StrOffsets{" | |
| << Table.GetStringOffset(Int->fullName()) << " /* " << Int->fullName() | |
| << " */, " << Table.GetStringOffset("") << ", " | |
| << Table.GetStringOffset("ncU") << " /* ncU */}},\n"; | |
| OS << "#endif // GET_CDE_BUILTIN_INFOS\n\n"; | |
| } | |
| void CdeEmitter::EmitBuiltinSema(raw_ostream &OS) { | |
| std::map<std::string, std::set<std::string>> Checks; | |
| GroupSemaChecks(Checks); | |
| for (const auto &kv : Checks) { | |
| for (StringRef Name : kv.second) | |
| OS << "case ARM::BI__builtin_arm_cde_" << Name << ":\n"; | |
| OS << " Err = " << kv.first << " break;\n"; | |
| } | |
| } | |
| } // namespace | |
| namespace clang { | |
| // MVE | |
| void EmitMveHeader(const RecordKeeper &Records, raw_ostream &OS) { | |
| MveEmitter(Records).EmitHeader(OS); | |
| } | |
| void EmitMveBuiltinDef(const RecordKeeper &Records, raw_ostream &OS) { | |
| MveEmitter(Records).EmitBuiltinDef(OS); | |
| } | |
| void EmitMveBuiltinSema(const RecordKeeper &Records, raw_ostream &OS) { | |
| MveEmitter(Records).EmitBuiltinSema(OS); | |
| } | |
| void EmitMveBuiltinCG(const RecordKeeper &Records, raw_ostream &OS) { | |
| MveEmitter(Records).EmitBuiltinCG(OS); | |
| } | |
| void EmitMveBuiltinAliases(const RecordKeeper &Records, raw_ostream &OS) { | |
| MveEmitter(Records).EmitBuiltinAliases(OS); | |
| } | |
| // CDE | |
| void EmitCdeHeader(const RecordKeeper &Records, raw_ostream &OS) { | |
| CdeEmitter(Records).EmitHeader(OS); | |
| } | |
| void EmitCdeBuiltinDef(const RecordKeeper &Records, raw_ostream &OS) { | |
| CdeEmitter(Records).EmitBuiltinDef(OS); | |
| } | |
| void EmitCdeBuiltinSema(const RecordKeeper &Records, raw_ostream &OS) { | |
| CdeEmitter(Records).EmitBuiltinSema(OS); | |
| } | |
| void EmitCdeBuiltinCG(const RecordKeeper &Records, raw_ostream &OS) { | |
| CdeEmitter(Records).EmitBuiltinCG(OS); | |
| } | |
| void EmitCdeBuiltinAliases(const RecordKeeper &Records, raw_ostream &OS) { | |
| CdeEmitter(Records).EmitBuiltinAliases(OS); | |
| } | |
| } // end namespace clang |