Difference between revisions of "NaplesPU Clang Documentation"
(→Defining Target Features) |
|||
(11 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | This section shows how the Clang frontend is modified to match | + | This section shows how the Clang frontend is modified to match NaplesPU requirements concerning intrinsic recognition and target registration. |
== Builtins Definition == | == Builtins Definition == | ||
Line 10: | Line 10: | ||
For example, the following is a builtin definition: | For example, the following is a builtin definition: | ||
− | <code> BUILTIN( | + | <code> BUILTIN(__builtin_npu_flush, vi, n) </code> |
− | In this case, the function '' | + | In this case, the function ''__builtin_npu_flush'' has got only one argument of ''int'' type and it is a ''void'' return function. ''n'' attribute means that it is a ''nothrow'' function. |
To get a full list of attributes and types you may check the just cited [https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/Builtins.def Builtins.def] | To get a full list of attributes and types you may check the just cited [https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/Builtins.def Builtins.def] | ||
− | At this point builtins are defined but | + | At this point, builtins are defined but cannot be recognised by frontend tools. To realise this behaviour it is necessary to collect all builtin definitions in a ''.def'' file and then include it in the ''TargetBuiltins'' header file as shown below: |
<syntaxhighlight lang="cpp" line='line'> | <syntaxhighlight lang="cpp" line='line'> | ||
− | namespace | + | namespace NaplesPU { |
enum { | enum { | ||
LastTIBuiltin = clang::Builtin::FirstTSBuiltin-1, | LastTIBuiltin = clang::Builtin::FirstTSBuiltin-1, | ||
#define BUILTIN(ID, TYPE, ATTRS) BI##ID, | #define BUILTIN(ID, TYPE, ATTRS) BI##ID, | ||
− | #include "clang/Basic/ | + | #include "clang/Basic/BuiltinsNaplesPU.def" |
LastTSBuiltin | LastTSBuiltin | ||
}; | }; | ||
Line 33: | Line 33: | ||
To be able to use this class, it is required to inherit it by creating a specialized ''TargetInfo'' class, defining the target features and register it as new supported target. | To be able to use this class, it is required to inherit it by creating a specialized ''TargetInfo'' class, defining the target features and register it as new supported target. | ||
− | The following code sample is extracted by | + | The following code sample is extracted by NaplesPU.h in ''clang/lib/Basic/Targets''. |
<syntaxhighlight> | <syntaxhighlight> | ||
− | class | + | class NaplesPUTargetInfo : public TargetInfo { |
static const char *const GCCRegNames[]; | static const char *const GCCRegNames[]; | ||
static const Builtin::Info BuiltinInfo[]; | static const Builtin::Info BuiltinInfo[]; | ||
public: | public: | ||
− | + | NaplesPUTargetInfo(const llvm::Triple &Triple) : TargetInfo(Triple) { | |
BigEndian = false; | BigEndian = false; | ||
IntWidth = IntAlign = 32; | IntWidth = IntAlign = 32; | ||
Line 54: | Line 54: | ||
The ''TargetInfo'' class implementation provides two ''static'' variables, ''GCCRegNames'' and ''BuiltinInfo'', of array type. The first contains the list of the registers' names supported by the target platform, while the latter is the definition of the target ''builtin'' format. | The ''TargetInfo'' class implementation provides two ''static'' variables, ''GCCRegNames'' and ''BuiltinInfo'', of array type. The first contains the list of the registers' names supported by the target platform, while the latter is the definition of the target ''builtin'' format. | ||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight> | <syntaxhighlight> | ||
− | + | const char *const NaplesPUTargetInfo::GCCRegNames[] = { | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | const char *const | ||
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", | "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", | ||
"s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", | "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", | ||
Line 160: | Line 75: | ||
}; | }; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | Another implemented method is ''validateAsmConstraint''. This function is used to validate the constraint used for inputs, outputs and clobbers for inline assembly code declarations. Recall that the inline assembly code is the way to embed ''asm'' code into an HLL source file. | |
− | |||
− | |||
− | |||
− | |||
− | + | To be correctly recognised, 'naplespu'' has to be added in the ''llvm::Triple'' namespace, by defining it in Triple.h: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <syntaxhighlight> | |
− | + | enum ArchType { | |
− | + | UnknownArch, | |
− | + | arm, // ARM (little endian): arm, armv.*, xscale | |
− | + | ... | |
− | + | naplespu, // ADDING NaplesPU TARGET | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | Once the custom target is declared, it can be allocated by frontend. To enable this operation, it is necessary to define a link between the clang function ''AllocateTarget'' and the ''TargetInfo'' object constructor in clang/lib/Basic/Targets.cpp: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | <syntaxhighlight | + | <syntaxhighlight> |
static TargetInfo *AllocateTarget(const llvm::Triple &Triple, const TargetOptions &Opts) { | static TargetInfo *AllocateTarget(const llvm::Triple &Triple, const TargetOptions &Opts) { | ||
switch (Triple.getArch()) { | switch (Triple.getArch()) { | ||
... | ... | ||
− | case llvm::Triple:: | + | case llvm::Triple::naplespu: |
− | return new | + | return new NaplesPUTargetInfo(Triple); |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | == | + | == Mapping Builtins on LLVM Intrinsics == |
+ | The next step in adding frontend support for the custom target is to create a mapping between the defined builtins and the corresponding LLVM Intrinsics, that are used to build the intermediate representation. | ||
+ | |||
+ | LLVM provides a simple way to define the intrinsics by using the TableGen language. Let's take a look on the ''Intrinsics'' class definition showed below. | ||
− | <syntaxhighlight | + | <syntaxhighlight> |
− | + | class Intrinsic<list<LLVMType> ret_types, | |
− | + | list<LLVMType> param_types = [], | |
− | + | list<IntrinsicProperty> intr_properties = [], | |
− | + | string name = "" > : SDPatternOperator { | |
− | |||
... | ... | ||
− | |||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | As shown, the ''Intrinsic'' class has several arguments: | |
+ | * ''ret_types'' is the field containing the list of return types of the defined function. It is a list of ''LLVMType'' objects, each of them is described in Intrinsics.td. However, it is possible to define a custom type by deriving it from the defined ones. For example: | ||
− | < | + | <code>def my_dummy_type : LLVMType<v16i32> </code> |
− | |||
− | </ | ||
− | |||
− | + | The ''LLVMType'' object has a single argument that is a ''ValueType'' object. It is defined in ValueTypes.td. For example the following is the definition of ''v16i32'': | |
− | <syntaxhighlight | + | <syntaxhighlight> |
− | + | //ValueType <Size, Value> | |
− | + | def v16i32 : ValueType<512, 42>; | |
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | + | * ''param_types'' is a list of parameter types, each one must be a ''LLVMType'' object. | |
− | + | * ''properties'' is an array of ''IntrinsicProperty'', used to describe the behaviour of the intrinsic. For example ''IntrNoMem'' is the property to tell that the intrinsic does not access to memory or have side effects. | |
− | + | * ''name'' is the string name of the intrinsic, prefixes included. | |
− | |||
− | |||
− | |||
− | |||
− | + | Each intrinsic must be defined as an object of the just cited class, and each definition should be of the following format: | |
− | |||
− | |||
− | |||
+ | <code>def int_[name] : Intrinsic <...> </code> | ||
− | <syntaxhighlight | + | where ''name'' is the intrinsic name excluded of the prefixes. For example, the following one is a correct definition: |
− | + | <syntaxhighlight> | |
− | + | def int_npu_write_control_reg : Intrinsic<[], | |
− | + | [llvm_i32_ty, llvm_i32_ty], [], | |
− | + | "llvm.npu.__builtin_npu_write_control_reg"> | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | In order to enable the frontend to generate the IR for our custom platform, it is needed to define a function to map builtins on the intrinsics. This function has to be defined in [https://clang.llvm.org/doxygen/classclang_1_1CodeGen_1_1CodeGenFunction.html CodeGenFunction] header and then implemented by switching among builtins and returning the associate ''Intrinsic'' object. | |
− | |||
− | |||
− | |||
− | |||
− | + | <syntaxhighlight> | |
− | + | llvm::Value *CodeGenFunction::EmitNaplesPUBuiltinExpr(unsigned BuiltinID, const CallExpr *E) { | |
− | + | ... | |
− | + | SmallVector<Value*, 2> Ops; | |
− | + | for (unsigned i = 0; i < E->getNumArgs(); i++){ | |
− | + | Ops.push_back(EmitScalarExpr(E->getArg(i))); | |
− | { | ||
− | |||
− | |||
− | < | ||
− | |||
− | |||
− | < | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
+ | llvm::Function *F; | ||
+ | ... | ||
+ | case MyTarget::BI__builtin_mytarget_ctzv8i64: | ||
+ | F = CGM.getIntrinsic(Intrinsic::npu_ctzv8i64); | ||
+ | break; | ||
+ | .. | ||
+ | return Builder.CreateCall(F, Ops, ""); | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | + | ||
− | + | When ''clang'' tries to convert a builtin in an intrinsic, it calls a method, that is ''EmitTargetArchBuiltinExpr'', that switches among the different target to get the proper function to call. This method has got a static implementation in CGBuiltin.cpp | |
− | |||
− | |||
− | |||
− | |||
− | <syntaxhighlight | + | <syntaxhighlight> |
− | + | static Value *EmitTargetArchBuiltinExpr(CodeGenFunction *CGF, unsigned BuiltinID, const CallExpr *E, llvm::Triple::ArchType Arch) { | |
− | + | switch (Arch) { | |
− | + | ... | |
+ | case llvm::Triple::naplespu: | ||
+ | return CGF->EmitNaplesPUBuiltinExpr(BuiltinID, E); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | == | + | == Toolchain properties definition == |
+ | In order to define the features of a toolchain, LLVM abstracts it through the ''ToolChain'' class. It is possible to define a new toolchain by using the inheritance mechanism and then specialising methods and attributes. | ||
− | <syntaxhighlight | + | <syntaxhighlight> |
− | class | + | class NaplesPUToolChain : public ToolChain { |
− | + | NaplesPUToolChain(const Driver &D, const llvm::Triple &Triple, const llvm::opt::ArgList &Args); | |
− | + | ... | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | For example, if it is required to disable standard ''include'' directories, it is necessary to override the ''addClangTargetOptions'' function as follows: | |
− | <syntaxhighlight | + | <syntaxhighlight> |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | void NaplesPUToolChain::addClangTargetOptions(const ArgList &DriverArgs, | |
− | + | ArgStringList &CC1Args) const { | |
− | + | CC1Args.push_back("-nostdsysteminc"); | |
− | + | ... | |
− | + | } | |
− | . | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | clang/lib/Driver/Driver.cpp has been modified to be able to instance a toolchain object as follows: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <syntaxhighlight> | |
− | + | const ToolChain &Driver::getToolChain(const ArgList &Args, | |
− | + | const llvm::Triple &Target) const { | |
− | + | case llvm::Triple::naplespu: | |
− | + | TC = llvm::make_unique<toolchains::NaplesPUToolChain>(*this, Target, Args); | |
− | + | break; | |
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | clang/lib/Driver/ToolChains/NaplesPU.h also contains the class definition for the ''Linker'' tool. | |
− | <syntaxhighlight | + | <syntaxhighlight> |
− | + | class NaplesPULinker : public Tools { | |
− | + | ConstructJob(...); | |
− | + | ... | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 15:29, 21 June 2019
This section shows how the Clang frontend is modified to match NaplesPU requirements concerning intrinsic recognition and target registration.
Contents
Builtins Definition
When implementing support for a new target, a frontend implementation must be able to recognise target-specific instructions, also called builtins. Clang provides a simple mechanism to add to frontend the capability to identify builtins, described in the file Builtins.def. It looks like a database for builtin functions, in which each entry is of the following form:
BUILTIN(<name of the builtin>, <types>, <function attributes>)
Referring to the above schema, the first value is the function name, the second one is the concatenation of types for the result value and for each argument, the latter is the concatenation of codes, each one is a function attribute. For example, the following is a builtin definition:
BUILTIN(__builtin_npu_flush, vi, n)
In this case, the function __builtin_npu_flush has got only one argument of int type and it is a void return function. n attribute means that it is a nothrow function. To get a full list of attributes and types you may check the just cited Builtins.def
At this point, builtins are defined but cannot be recognised by frontend tools. To realise this behaviour it is necessary to collect all builtin definitions in a .def file and then include it in the TargetBuiltins header file as shown below:
namespace NaplesPU {
enum {
LastTIBuiltin = clang::Builtin::FirstTSBuiltin-1,
#define BUILTIN(ID, TYPE, ATTRS) BI##ID,
#include "clang/Basic/BuiltinsNaplesPU.def"
LastTSBuiltin
};
}
Defining Target Features
In order to provide target informations, the LLVM team provides a class named TargetInfo. It contains information fields, each of them is a potentially supported feature. To be able to use this class, it is required to inherit it by creating a specialized TargetInfo class, defining the target features and register it as new supported target.
The following code sample is extracted by NaplesPU.h in clang/lib/Basic/Targets.
class NaplesPUTargetInfo : public TargetInfo {
static const char *const GCCRegNames[];
static const Builtin::Info BuiltinInfo[];
public:
NaplesPUTargetInfo(const llvm::Triple &Triple) : TargetInfo(Triple) {
BigEndian = false;
IntWidth = IntAlign = 32;
...
resetDataLayout("e-m:e-p:32:32-i32:32:32-f32:32:32");
}
Referring to the code sample above, the DataLayout field describes the target in terms of its features. Each of them is separated by a - character. By looking at the example above, the first feature defines the endianness as little. The other ones are related to size and alignment of pointers, integers and floats respectively. They should be read as follows:
<feature_code>:size:alignment
More about the data layout parsing can be found in the DataLayout documentation.
The TargetInfo class implementation provides two static variables, GCCRegNames and BuiltinInfo, of array type. The first contains the list of the registers' names supported by the target platform, while the latter is the definition of the target builtin format.
const char *const NaplesPUTargetInfo::GCCRegNames[] = {
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
"s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15",
"s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23",
"s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31",
"s32", "s33", "s34", "s35", "s36", "s37", "s38", "s39",
"s40", "s41", "s42", "s43", "s44", "s45", "s46", "s47",
"s48", "s49", "s50", "s51", "s52", "s53", "s54", "s55",
"s56", "s57", "TR", "RM", "FP", "SP", "RA", "PC",
"v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
"v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15",
"v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
"v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31",
"v32", "v33", "v34", "v35", "v36", "v37", "v38", "v39",
"v40", "v41", "v42", "v43", "v44", "v45", "v46", "v47",
"v48", "v49", "v50", "v51", "v52", "v53", "v54", "v55",
"v56", "v57", "v58", "v59", "v60", "v61", "v62", "v63"
};
Another implemented method is validateAsmConstraint. This function is used to validate the constraint used for inputs, outputs and clobbers for inline assembly code declarations. Recall that the inline assembly code is the way to embed asm code into an HLL source file.
To be correctly recognised, 'naplespu has to be added in the llvm::Triple namespace, by defining it in Triple.h:
enum ArchType {
UnknownArch,
arm, // ARM (little endian): arm, armv.*, xscale
...
naplespu, // ADDING NaplesPU TARGET
Once the custom target is declared, it can be allocated by frontend. To enable this operation, it is necessary to define a link between the clang function AllocateTarget and the TargetInfo object constructor in clang/lib/Basic/Targets.cpp:
static TargetInfo *AllocateTarget(const llvm::Triple &Triple, const TargetOptions &Opts) {
switch (Triple.getArch()) {
...
case llvm::Triple::naplespu:
return new NaplesPUTargetInfo(Triple);
Mapping Builtins on LLVM Intrinsics
The next step in adding frontend support for the custom target is to create a mapping between the defined builtins and the corresponding LLVM Intrinsics, that are used to build the intermediate representation.
LLVM provides a simple way to define the intrinsics by using the TableGen language. Let's take a look on the Intrinsics class definition showed below.
class Intrinsic<list<LLVMType> ret_types,
list<LLVMType> param_types = [],
list<IntrinsicProperty> intr_properties = [],
string name = "" > : SDPatternOperator {
...
}
As shown, the Intrinsic class has several arguments:
- ret_types is the field containing the list of return types of the defined function. It is a list of LLVMType objects, each of them is described in Intrinsics.td. However, it is possible to define a custom type by deriving it from the defined ones. For example:
def my_dummy_type : LLVMType<v16i32>
The LLVMType object has a single argument that is a ValueType object. It is defined in ValueTypes.td. For example the following is the definition of v16i32:
//ValueType <Size, Value>
def v16i32 : ValueType<512, 42>;
- param_types is a list of parameter types, each one must be a LLVMType object.
- properties is an array of IntrinsicProperty, used to describe the behaviour of the intrinsic. For example IntrNoMem is the property to tell that the intrinsic does not access to memory or have side effects.
- name is the string name of the intrinsic, prefixes included.
Each intrinsic must be defined as an object of the just cited class, and each definition should be of the following format:
def int_[name] : Intrinsic <...>
where name is the intrinsic name excluded of the prefixes. For example, the following one is a correct definition:
def int_npu_write_control_reg : Intrinsic<[],
[llvm_i32_ty, llvm_i32_ty], [],
"llvm.npu.__builtin_npu_write_control_reg">
In order to enable the frontend to generate the IR for our custom platform, it is needed to define a function to map builtins on the intrinsics. This function has to be defined in CodeGenFunction header and then implemented by switching among builtins and returning the associate Intrinsic object.
llvm::Value *CodeGenFunction::EmitNaplesPUBuiltinExpr(unsigned BuiltinID, const CallExpr *E) {
...
SmallVector<Value*, 2> Ops;
for (unsigned i = 0; i < E->getNumArgs(); i++){
Ops.push_back(EmitScalarExpr(E->getArg(i)));
}
llvm::Function *F;
...
case MyTarget::BI__builtin_mytarget_ctzv8i64:
F = CGM.getIntrinsic(Intrinsic::npu_ctzv8i64);
break;
..
return Builder.CreateCall(F, Ops, "");
}
When clang tries to convert a builtin in an intrinsic, it calls a method, that is EmitTargetArchBuiltinExpr, that switches among the different target to get the proper function to call. This method has got a static implementation in CGBuiltin.cpp
static Value *EmitTargetArchBuiltinExpr(CodeGenFunction *CGF, unsigned BuiltinID, const CallExpr *E, llvm::Triple::ArchType Arch) {
switch (Arch) {
...
case llvm::Triple::naplespu:
return CGF->EmitNaplesPUBuiltinExpr(BuiltinID, E);
Toolchain properties definition
In order to define the features of a toolchain, LLVM abstracts it through the ToolChain class. It is possible to define a new toolchain by using the inheritance mechanism and then specialising methods and attributes.
class NaplesPUToolChain : public ToolChain {
NaplesPUToolChain(const Driver &D, const llvm::Triple &Triple, const llvm::opt::ArgList &Args);
...
}
For example, if it is required to disable standard include directories, it is necessary to override the addClangTargetOptions function as follows:
void NaplesPUToolChain::addClangTargetOptions(const ArgList &DriverArgs,
ArgStringList &CC1Args) const {
CC1Args.push_back("-nostdsysteminc");
...
}
clang/lib/Driver/Driver.cpp has been modified to be able to instance a toolchain object as follows:
const ToolChain &Driver::getToolChain(const ArgList &Args,
const llvm::Triple &Target) const {
case llvm::Triple::naplespu:
TC = llvm::make_unique<toolchains::NaplesPUToolChain>(*this, Target, Args);
break;
}
clang/lib/Driver/ToolChains/NaplesPU.h also contains the class definition for the Linker tool.
class NaplesPULinker : public Tools {
ConstructJob(...);
...
}