27constexpr unsigned NumAMDGPUSubArches =
32 StringTable::Offset AltName;
38 StringTable::Offset Name;
42 StringTable::Offset FamilyName;
43 uint8_t MaxWavesPerEU;
44 uint32_t MaxHWAddressableLocalMemorySize;
46 uint8_t BufferResourceNumRecordsWidth;
51 StringTable::Offset Name;
55#define GET_AMDGPU_NAME_TABLE
56#define GET_AMDGPU_GPU_TABLE
57#define GET_AMDGPU_GPU_ALIAS_TABLE
58#define GET_AMDGPU_MAJOR_SUBARCH
59#define GET_AMDGPU_SUBARCH_NAME
60#define GET_AMDGPU_FEATURE_NAME_TABLE
61#include "llvm/TargetParser/AMDGPUTargetParserDef.inc"
63#define GET_R600_NAME_TABLE
64#define GET_R600_GPU_TABLE
65#define GET_R600_GPU_ALIAS_TABLE
66#define GET_R600_FEATURE_NAME_TABLE
67#include "llvm/TargetParser/R600TargetParserDef.inc"
71constexpr StringTable AMDGPUNameStrTab = AMDGPUNameTable;
72constexpr StringTable R600NameStrTab = R600NameTable;
76const GPUInfo *getAMDGPUInfo(
GPUKind AK) {
77 if (AK < AMDGPUFirstGPUKind)
79 unsigned Idx = AK - AMDGPUFirstGPUKind;
80 if (
Idx >= std::size(AMDGPUGPUTable))
82 return &AMDGPUGPUTable[
Idx];
86const R600Info *getR600Info(
GPUKind AK) {
87 if (AK < R600FirstGPUKind)
89 unsigned Idx = AK - R600FirstGPUKind;
90 if (
Idx >= std::size(R600GPUTable))
92 return &R600GPUTable[
Idx];
96template <
typename InfoT,
size_t N,
size_t M>
99 const GPUNameAlias (&Aliases)[M]) {
100 for (
unsigned I = 0;
I !=
N; ++
I) {
101 if (CPU == StrTab[
Table[
I].Name])
102 return static_cast<GPUKind>(FirstKind +
I);
105 for (
const GPUNameAlias &
A : Aliases) {
106 if (CPU == StrTab[
A.AltName])
115constexpr std::array<GPUKind, NumAMDGPUSubArches> AMDGPUSubArchToGPUKind = [] {
116 std::array<GPUKind, NumAMDGPUSubArches>
Map{};
118 for (
unsigned I = 0;
I < std::size(AMDGPUGPUTable); ++
I) {
122 static_cast<GPUKind>(AMDGPUFirstGPUKind +
I);
129constexpr std::array<Triple::SubArchType, NumAMDGPUSubArches>
130 AMDGPUMajorFamilies = [] {
131 std::array<Triple::SubArchType, NumAMDGPUSubArches>
Map{};
133 for (
unsigned I = 0;
I < NumAMDGPUSubArches; ++
I) {
138 for (
const AMDGPUMajorSubArchEntry &Entry : AMDGPUMajorSubArch)
145constexpr std::array<StringTable::Offset, NumAMDGPUSubArches>
146 AMDGPUSubArchNameOffsets = [] {
147 std::array<StringTable::Offset, NumAMDGPUSubArches>
Map{};
148 for (
const AMDGPUSubArchNameEntry &Entry : AMDGPUSubArchNames)
155constexpr std::array<StringTable::Offset, NumAMDGPUSubArches>
156 AMDGPUSubArchTripleNameOffsets = [] {
157 std::array<StringTable::Offset, NumAMDGPUSubArches>
Map{};
158 for (
const AMDGPUSubArchNameEntry &Entry : AMDGPUSubArchNames)
160 Entry.TripleNameOffset;
166 const GPUInfo *Info = getAMDGPUInfo(AK);
167 return Info ? AMDGPUNameStrTab[Info->FamilyName] :
"";
171 const GPUInfo *Info = getAMDGPUInfo(AK);
202 return MajorA == MajorB;
204 return MajorA == MajorB;
230 const GPUInfo *Info = getAMDGPUInfo(AK);
244 return A.getArchName().size() == 6;
247 return B.getArchName().size() == 6;
262 if (
A.getSubArch() == MajorA) {
263 if (MajorA == MajorB)
267 if (
B.getSubArch() == MajorB) {
268 if (MajorA == MajorB)
277 const GPUInfo *Info = getAMDGPUInfo(AK);
278 return Info ? AMDGPUNameStrTab[Info->Name] :
"";
285 return AMDGPUNameStrTab[AMDGPUSubArchNameOffsets[SubArch -
291 return AMDGPUNameStrTab[AMDGPUNoSubArchNameOffset];
295 "expected an AMDGPU subarch or NoSubArch");
296 return AMDGPUNameStrTab
301 const R600Info *Info = getR600Info(AK);
302 return Info ? R600NameStrTab[Info->Name] :
"";
306 return parseArchImpl(CPU, AMDGPUGPUTable, AMDGPUFirstGPUKind,
307 AMDGPUNameStrTab, AMDGPUGPUAliases);
311 return parseArchImpl(CPU, R600GPUTable, R600FirstGPUKind, R600NameStrTab,
317 const GPUInfo *Info = getAMDGPUInfo(AK);
318 return Info ? Info->Features :
Empty;
323 const R600Info *Info = getR600Info(AK);
324 return Info ? Info->Features :
Empty;
329 for (
unsigned I = 0;
I != NUM_FEATURES; ++
I) {
330 if (Features.
test(
I))
331 Names.
push_back(AMDGPUNameStrTab[AMDGPUFeatureNames[
I]]);
339 for (
unsigned I = 0;
I != std::size(AMDGPUGPUTable); ++
I) {
343 Values.push_back(AMDGPUNameStrTab[AMDGPUGPUTable[
I].Name]);
346 for (
const GPUNameAlias &
A : AMDGPUGPUAliases) {
348 Values.push_back(AMDGPUNameStrTab[
A.AltName]);
353 for (
const R600Info &Info : R600GPUTable)
354 Values.push_back(R600NameStrTab[Info.Name]);
355 for (
const GPUNameAlias &
A : R600GPUAliases)
356 Values.push_back(R600NameStrTab[
A.AltName]);
361 return Info ? Info->Version :
IsaVersion{0, 0, 0};
366 return Info ? Info->Version :
IsaVersion{0, 0, 0};
427 if (Features.
test(FEAT_GFX90A_INSTS))
429 if (Features.
test(FEAT_1536_PHYSICAL_VGPRS))
430 return IsWave32 ? 24 : 12;
431 if (Features.
test(FEAT_GFX10_3_INSTS))
432 return IsWave32 ? 16 : 8;
433 return IsWave32 ? 8 : 4;
443 if (Features.
test(FEAT_GFX90A_INSTS))
445 if (!Features.
test(FEAT_GFX10_INSTS))
447 if (Features.
test(FEAT_1536_PHYSICAL_VGPRS))
448 return IsWave32 ? 1536 : 768;
449 return IsWave32 ? 1024 : 512;
459 if (Features.
test(FEAT_GFX90A_INSTS))
463 constexpr unsigned OperandBits = 8;
464 constexpr unsigned MSBBits = 2;
465 unsigned Bits = OperandBits;
466 if (Features.
test(FEAT_VGPR_MSBS))
477 const GPUInfo *Info = getAMDGPUInfo(AK);
478 return Info ? Info->MaxHWAddressableLocalMemorySize : 32768;
516 const GPUInfo *Info = getAMDGPUInfo(AK);
517 return Info ? Info->LDSBankCount : 32;
525 const GPUInfo *Info = getAMDGPUInfo(AK);
526 if (!Info || Info->BufferResourceNumRecordsWidth == 0)
528 return Info->BufferResourceNumRecordsWidth;
531std::optional<unsigned>
540 assert((Features.
test(FEAT_LDS_ALLOC_GRANULARITY_256) ||
541 Features.
test(FEAT_LDS_ALLOC_GRANULARITY_512) ||
542 Features.
test(FEAT_LDS_ALLOC_GRANULARITY_1024) ||
543 Features.
test(FEAT_LDS_ALLOC_GRANULARITY_1280) ||
544 Features.
test(FEAT_LDS_ALLOC_GRANULARITY_2048)) &&
545 "missing LDS allocation granularity feature");
546 if (Features.
test(FEAT_LDS_ALLOC_GRANULARITY_256))
548 if (Features.
test(FEAT_LDS_ALLOC_GRANULARITY_512))
550 if (Features.
test(FEAT_LDS_ALLOC_GRANULARITY_1024))
552 if (Features.
test(FEAT_LDS_ALLOC_GRANULARITY_1280))
554 if (Features.
test(FEAT_LDS_ALLOC_GRANULARITY_2048))
566 if (Features.
test(FEAT_LDS_ENCODING_GRANULARITY_256))
568 if (Features.
test(FEAT_LDS_ENCODING_GRANULARITY_512))
570 if (Features.
test(FEAT_LDS_ENCODING_GRANULARITY_1024))
572 if (Features.
test(FEAT_LDS_ENCODING_GRANULARITY_1280))
574 if (Features.
test(FEAT_LDS_ENCODING_GRANULARITY_2048))
585 const GPUInfo *Info = getAMDGPUInfo(AK);
586 return Info ? Info->MaxWavesPerEU : 10;
609 FEAT_FAST_DENORMAL_F32,
610 FEAT_SUPPORTS_WAVE32,
613 FEAT_SRAMECC_SUPPORT,
614 FEAT_XNACK_ON_OFF_MODES,
615 FEAT_SRAMECC_ON_OFF_MODES,
617 FEAT_GET_DOORBELL_ID,
619 FEAT_1536_PHYSICAL_VGPRS,
620 FEAT_HALF_ADDRESSABLE_PHYSICAL_LOCAL_MEMORY,
622 FEAT_LDS_ALLOC_GRANULARITY_256,
623 FEAT_LDS_ALLOC_GRANULARITY_512,
624 FEAT_LDS_ALLOC_GRANULARITY_1024,
625 FEAT_LDS_ALLOC_GRANULARITY_1280,
626 FEAT_LDS_ALLOC_GRANULARITY_2048,
627 FEAT_LDS_ENCODING_GRANULARITY_256,
628 FEAT_LDS_ENCODING_GRANULARITY_512,
629 FEAT_LDS_ENCODING_GRANULARITY_1024,
630 FEAT_LDS_ENCODING_GRANULARITY_1280,
631 FEAT_LDS_ENCODING_GRANULARITY_2048};
641 Features[Name] =
true;
643 Features.
insert({Name,
true});
649static std::pair<FeatureError, StringRef>
656 const GPUInfo *Info = getAMDGPUInfo(Kind);
662 const bool TargetHasWave32 =
663 Info && Info->Features.test(FEAT_WAVEFRONTSIZE32);
664 const bool TargetHasWave64 =
665 Info && Info->Features.test(FEAT_WAVEFRONTSIZE64);
667 auto Wave32Itr = Features.
find(
"wavefrontsize32");
668 auto Wave64Itr = Features.
find(
"wavefrontsize64");
669 const bool EnableWave32 =
670 Wave32Itr != Features.
end() && Wave32Itr->getValue();
671 const bool EnableWave64 =
672 Wave64Itr != Features.
end() && Wave64Itr->getValue();
673 const bool DisableWave32 =
674 Wave32Itr != Features.
end() && !Wave32Itr->getValue();
675 const bool DisableWave64 =
676 Wave64Itr != Features.
end() && !Wave64Itr->getValue();
678 if (EnableWave32 && EnableWave64)
680 "'+wavefrontsize32' and '+wavefrontsize64' are mutually exclusive"};
681 if (DisableWave32 && DisableWave64)
683 "'-wavefrontsize32' and '-wavefrontsize64' are mutually exclusive"};
686 if (TargetHasWave64) {
693 if (TargetHasWave32) {
703 if (!IsNullGPU && !EnableWave32 && !EnableWave64 && !TargetHasWave32 &&
705 Features.
insert({
"wavefrontsize32",
true});
717std::pair<FeatureError, StringRef>
728 Features[
"wavefrontsize32"] =
true;
729 Features[
"wavefrontsize64"] =
true;
730 }
else if (
T.isAMDGCN()) {
766 TargetTripleString(TT.normalize(
Triple::CanonicalForm::FOUR_IDENT)),
767 XnackSetting(XnackSetting), SramEccSetting(SramEccSetting),
768 IsAMDHSA(TT.getOS() ==
Triple::AMDHSA) {}
774 return TargetIDSetting::On;
776 return TargetIDSetting::Off;
778 return TargetIDSetting::Unsupported;
785 return (CPUName.
empty() || CPUName ==
"generic")
798 if (!Features.
test(FEAT_XNACK_SUPPORT))
800 else if (Features.
test(FEAT_XNACK_ON_OFF_MODES))
804 if (!Features.
test(FEAT_SRAMECC_SUPPORT))
806 else if (Features.
test(FEAT_SRAMECC_ON_OFF_MODES))
826 TargetIDStr.
split(Split,
':');
827 bool SeenXnack =
false;
828 bool SeenSramEcc =
false;
830 for (
unsigned I = 1,
E = Split.size();
I !=
E; ++
I) {
836 if (SeenXnack || !Features.
test(FEAT_XNACK_ON_OFF_MODES) ||
837 Sign == TargetIDSetting::Unsupported)
844 if (SeenSramEcc || !Features.
test(FEAT_SRAMECC_ON_OFF_MODES) ||
845 Sign == TargetIDSetting::Unsupported)
874 bool XnackToggleable = XnackSetting == TargetIDSetting::Any;
875 bool SramEccToggleable = SramEccSetting == TargetIDSetting::Any;
877 FeatureString.
split(Features,
',', -1,
false);
881 if (Sign == TargetIDSetting::Unsupported)
884 if (Name ==
"xnack" && XnackToggleable)
886 else if (Name ==
"sramecc" && SramEccToggleable)
887 SramEccSetting = Sign;
890 return TargetID(Arch, TT, XnackSetting, SramEccSetting);
906 if (!CPUName.
empty() && CPUName !=
"generic" &&
918 return TargetID(Arch, TT, XnackSetting, SramEccSetting);
921std::optional<TargetID>
927 TargetIDDirective.
split(Parts,
'-', 4);
928 if (Parts.
size() < 5)
931 return parse(
Triple(Parts[0], Parts[1], Parts[2], Parts[3]), Parts[4]);
938 return Features.
test(FEAT_XNACK_SUPPORT) &&
939 !Features.
test(FEAT_XNACK_ON_OFF_MODES);
944 return Features.
test(FEAT_SRAMECC_SUPPORT) &&
945 !Features.
test(FEAT_SRAMECC_ON_OFF_MODES);
952 bool SramEccHardwiredOn,
953 bool XnackHardwiredOn) {
954 if (!SramEccHardwiredOn) {
955 if (SramEcc == TargetIDSetting::Off)
957 else if (SramEcc == TargetIDSetting::On)
961 if (XnackHardwiredOn)
964 if (Xnack == TargetIDSetting::Off)
966 else if (Xnack == TargetIDSetting::On)
1000 return Arch ==
Other.Arch && XnackSetting ==
Other.XnackSetting &&
1001 SramEccSetting ==
Other.SramEccSetting && IsAMDHSA ==
Other.IsAMDHSA &&
1002 TargetTripleString ==
Other.TargetTripleString;
1007 return Provided == TargetIDSetting::Any ||
1008 Provided == TargetIDSetting::Unsupported || Provided == Requested;
1013 if (Arch !=
Other.Arch || XnackSetting !=
Other.XnackSetting ||
1014 SramEccSetting !=
Other.SramEccSetting)
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
static cl::opt< bool > SramEccSetting("amdgpu-sramecc", cl::desc("Force amdgpu.sramecc for testing"), cl::ReallyHidden)
static cl::opt< bool > XnackSetting("amdgpu-xnack", cl::desc("Force amdgpu.xnack value for testing"), cl::ReallyHidden)
static GPUKind getGPUKindFromTargetID(const Triple &TT, StringRef TargetIDStr)
static std::pair< FeatureError, StringRef > fillAMDGCNFeatureMap(StringRef GPU, const Triple &T, StringMap< bool > &Features)
Add a GPU's default features to Features (preserving user overrides) and validate any requested waves...
static bool computeTargetIDFeatures(GPUKind Arch, StringRef TargetIDStr, TargetIDSetting &XnackSetting, TargetIDSetting &SramEccSetting)
static bool isSramEccHardwiredOn(GPUKind Arch)
static void getDefaultTargetIDFeatures(GPUKind Arch, TargetIDSetting &XnackSetting, TargetIDSetting &SramEccSetting)
static TargetIDSetting getTargetIDSettingFromFeatureString(StringRef Sign)
static bool featureProvidesFor(TargetIDSetting Provided, TargetIDSetting Requested)
static bool isXnackHardwiredOn(GPUKind Arch)
static void addGPUFeatures(const GPUInfo &Info, bool Overwrite, StringMap< bool > &Features)
static void printFeatureModifiers(raw_ostream &OS, TargetIDSetting SramEcc, TargetIDSetting Xnack, bool SramEccHardwiredOn, bool XnackHardwiredOn)
static const AMDGPUFeatureBitset FrontendOnlyFeatures
static GCRegistry::Add< ErlangGC > A("erlang", "erlang-compatible garbage collector")
static GCRegistry::Add< CoreCLRGC > E("coreclr", "CoreCLR-compatible GC")
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
This file defines the SmallVector class.
LocallyHashedType DenseMapInfo< LocallyHashedType >::Empty
static TargetID createFromSubtargetFeatures(const Triple &TT, StringRef CPU, StringRef FeatureString)
Construct a TargetID for triple TT and processor CPU, taking the xnack/sramecc modes from the subtarg...
void printCanonicalTargetIDString(raw_ostream &OS) const
Print the canonical processor name followed by any explicit xnack and sramecc feature modifiers (e....
static std::optional< TargetID > parseTargetIDString(StringRef TargetIDDirective)
Parse and validate a TargetID from a full "<triple>-<processor>:<features>" directive string.
void print(raw_ostream &OS) const
TargetIDSetting getXnackSetting() const
bool isEquivalent(const TargetID &Other) const
Returns true if Other denotes the same target as *this, i.e.
bool operator==(const TargetID &Other) const
bool providesFor(const TargetID &Other) const
Returns true if a device image for *this can provide the device code for a request for Other.
StringRef getTargetTripleString() const
std::string getCanonicalFeatureString() const
TargetID(GPUKind Arch, const Triple &TT, TargetIDSetting XnackSetting, TargetIDSetting SramEccSetting)
static std::optional< TargetID > parse(const Triple &TT, StringRef ProcAndFeatures)
Parse and validate a TargetID for triple TT from the processor+features string ProcAndFeatures (e....
std::string toString() const
TargetIDSetting getSramEccSetting() const
constexpr bool none() const
constexpr bool test(unsigned I) const
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
StringMap - This is an unconventional map that is specialized for handling keys that are "strings",...
iterator find(StringRef Key)
bool insert(MapEntryTy *KeyValue)
insert - Insert the specified key/value pair into the map.
Represent a constant reference to a string, i.e.
std::pair< StringRef, StringRef > split(char Separator) const
Split into two substrings around the first occurrence of a separator character.
constexpr bool empty() const
Check if the string is empty.
bool consume_front(char Prefix)
Returns true if this StringRef has the given prefix and removes that prefix.
A table of densely packed, null-terminated strings indexed by offset.
Triple - Helper class for working with autoconf configuration names.
LLVM_ABI bool isCompatibleWith(const Triple &Other) const
Test whether target triples are compatible.
This class implements an extremely fast bulk output stream that can only output to a stream.
A raw_ostream that writes to an std::string.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
LLVM_ABI StringRef getArchNameR600(GPUKind AK)
@ FIXED_NUM_SGPRS_FOR_INIT_BUG
LLVM_ABI void fillValidArchListAMDGCN(SmallVectorImpl< StringRef > &Values, Triple::SubArchType SubArch=Triple::NoSubArch)
Append the valid AMDGCN GPU names to Values.
LLVM_ABI unsigned getLDSEncodingGranule(GPUKind AK)
LLVM_ABI unsigned getLDSAllocGranule(GPUKind AK)
LLVM_ABI unsigned getMaxWavesPerEU(GPUKind AK)
LLVM_ABI StringRef getCanonicalArchName(const Triple &T, StringRef Arch)
LLVM_ABI unsigned getAddressableLocalMemorySize(GPUKind AK, bool FullSIMDMode)
LLVM_ABI void fillValidArchListR600(SmallVectorImpl< StringRef > &Values)
LLVM_ABI std::string mergeSubArch(const Triple &A, const Triple &B)
Returns the effective triple appropriate to use when linking B into A by merging the subarches in cas...
LLVM_ABI bool isCPUValidForSubArch(Triple::SubArchType SubArch, GPUKind AK)
Return true if the GPU AK is usable with the triple subarch SubArch.
LLVM_ABI bool isSubArchCompatible(const Triple &A, const Triple &B)
Return true if subarch A is compatible with subarch B, i.e.
LLVM_ABI unsigned getLDSBankCount(GPUKind AK)
LLVM_ABI unsigned getMaxHWAddressableLocalMemorySize(GPUKind AK)
LDS size queries.
LLVM_ABI StringRef getArchFamilyNameAMDGCN(GPUKind AK)
LLVM_ABI StringRef getSubArchName(Triple::SubArchType SubArch)
Returns the triple subarch name for an AMDGPU subarch, e.g.
LLVM_ABI unsigned getAddressableNumSGPRs(GPUKind AK)
LLVM_ABI IsaVersion getIsaVersion(StringRef GPU)
LLVM_ABI unsigned getTotalNumVGPRs(GPUKind AK, bool IsWave32)
LLVM_ABI unsigned getTotalNumSGPRs(GPUKind AK)
LLVM_ABI std::optional< unsigned > getBufferResourceNumRecordsWidth(GPUKind AK)
GPUKind
GPU kinds supported by the AMDGPU target.
Bitset< NUM_FEATURES > AMDGPUFeatureBitset
LLVM_ABI Triple::SubArchType getSubArchFromGPUName(StringRef CPU)
Returns the preferred subarch for a GPU name CPU, or NoSubArch if unrecognized.
LLVM_ABI unsigned getLocalMemorySize(GPUKind AK, bool FullSIMDMode)
LLVM_ABI unsigned getSGPRAllocGranule(GPUKind AK)
LLVM_ABI Triple::SubArchType getSubArch(GPUKind AK)
LLVM_ABI StringRef getArchNameFromSubArch(Triple::SubArchType SubArch)
Returns the canonical GPU name for an AMDGPU subarch, e.g.
LLVM_ABI unsigned getVGPRAllocGranule(GPUKind AK, bool IsWave32)
LLVM_ABI GPUKind parseArchAMDGCN(StringRef CPU)
LLVM_ABI bool isPseudoTarget(GPUKind AK)
Return true if AK is a pseudo target (e.g.
@ UNSUPPORTED_TARGET_FEATURE
@ INVALID_FEATURE_COMBINATION
LLVM_ABI GPUKind getGPUKindFromSubArch(Triple::SubArchType SubArch)
AMDGPU::TargetID TargetID
LLVM_ABI std::pair< FeatureError, StringRef > fillAMDGPUFeatureMap(StringRef GPU, const Triple &T, StringMap< bool > &Features)
Fills Features map with default values for given target GPU.
LLVM_ABI unsigned getAddressableNumVGPRs(GPUKind AK, bool IsWave32)
LLVM_ABI void getFeatureNames(const AMDGPUFeatureBitset &Features, SmallVectorImpl< StringRef > &Names)
Appends the feature name of each bit set in Features to Names.
LLVM_ABI StringRef getArchNameAMDGCN(GPUKind AK)
LLVM_ABI Triple::SubArchType getMajorSubArch(Triple::SubArchType SubArch)
LLVM_ABI const AMDGPUFeatureBitset & getFeatureBitset(GPUKind AK)
Returns AK's feature bitset, or an empty bitset if unknown.
LLVM_ABI const R600FeatureBitset & getFeatureBitsetR600(GPUKind AK)
Returns R600 GPU AK's feature bitset, or an empty bitset if unknown.
Bitset< R600_NUM_FEATURES > R600FeatureBitset
LLVM_ABI GPUKind parseArchR600(StringRef CPU)
This is an optimization pass for GlobalISel generic memory operations.
RelativeUniformCounterPtr Values
Instruction set architecture version.