blob: 1e74b3f71599750f22fa8035f7c31d20b0185d4b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "../src/target/parsers/aprofile.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cmath>
#include <string>
#include "../src/target/llvm/llvm_instance.h"
namespace tvm {
namespace target {
namespace parsers {
namespace aprofile {
using ::testing::HasSubstr;
static float defaultI8MM = 8.6;
static float optionalI8MM[] = {8.2, 8.3, 8.4, 8.5};
static float defaultDotProd = 8.4;
static float optionalDotProd[] = {8.2, 8.3};
static float optionalSME[] = {9.2, 9.3};
static bool CheckArchitectureAvailability() {
#if TVM_LLVM_VERSION > 120
auto llvm_instance = std::make_unique<codegen::LLVMInstance>();
codegen::LLVMTargetInfo llvm_backend(*llvm_instance, "llvm");
ffi::Array<ffi::String> targets = llvm_backend.GetAllLLVMTargets();
int expected_target_count = 0;
for (ffi::String target : targets) {
if (target == "aarch64" || target == "arm") {
expected_target_count += 1;
}
}
if (expected_target_count >= 2) {
return true;
}
#endif
return false;
}
static bool has_aarch64_and_arm_targets = CheckArchitectureAvailability();
class AProfileParser : public ::testing::Test {
public:
// Check that LLVM has been compiled with the required targets, otherwise skip the test.
// Unfortunately, googletest doesn't let you call GTEST_SKIP in SetUpTestSuite() to skip
// the whole suite of tests, so a cached result is checked before each test is run instead.
void SetUp() override {
if (!has_aarch64_and_arm_targets) {
GTEST_SKIP() << "Skipping as LLVM has not been built for Arm(R)-based targets.";
}
}
};
class AProfileParserTestWithParam : public AProfileParser,
public testing::WithParamInterface<float> {};
static TargetFeatures ParseTargetWithAttrs(ffi::String mcpu, ffi::String mtriple,
ffi::Array<ffi::String> mattr) {
TargetJSON target_json = {
{"kind", ffi::String("llvm")},
{"mtriple", mtriple},
{"mattr", mattr},
};
if (mcpu != "") {
target_json.Set("mcpu", mcpu);
}
return ParseTarget(target_json);
}
std::string FloatToStringWithoutTrailingZeros(float value) {
std::stringstream ss;
ss << value;
return ss.str();
}
TEST_F(AProfileParser, ParseTargetKeys) {
TargetJSON target = ParseTarget({{"kind", ffi::String("llvm")}});
ffi::Array<ffi::String> keys = Downcast<ffi::Array<ffi::String>>(target.at("keys"));
ASSERT_EQ(keys.size(), 2);
ASSERT_EQ(keys[0], "arm_cpu");
ASSERT_EQ(keys[1], "cpu");
}
TEST_F(AProfileParser, ParseTargetWithExistingKeys) {
TargetJSON target = ParseTarget({
{"kind", ffi::String("llvm")},
{"keys", ffi::Array<ffi::String>{"cpu"}},
});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ffi::Array<ffi::String> keys = Downcast<ffi::Array<ffi::String>>(target.at("keys"));
ASSERT_EQ(keys.size(), 2);
ASSERT_EQ(keys[0], "cpu");
ASSERT_EQ(keys[1], "arm_cpu");
}
TEST_F(AProfileParser, ParseTargetWithDuplicateKey) {
TargetJSON target = ParseTarget({
{"kind", ffi::String("llvm")},
{"keys", ffi::Array<ffi::String>{"cpu", "arm_cpu"}},
});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ffi::Array<ffi::String> keys = Downcast<ffi::Array<ffi::String>>(target.at("keys"));
ASSERT_EQ(keys.size(), 2);
ASSERT_EQ(keys[0], "cpu");
ASSERT_EQ(keys[1], "arm_cpu");
}
TEST_F(AProfileParser, ParseTargetDefaults) {
TargetJSON target = ParseTarget({{"kind", ffi::String("llvm")}});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
}
TEST_F(AProfileParser, IsAArch64Triple) {
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {""});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), true);
}
TEST_F(AProfileParser, IsAArch32Triple) {
TargetJSON target = ParseTargetWithAttrs("", "armv7a-arm-none-eabi", {""});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
target = ParseTargetWithAttrs("", "arm-unknown-linux-gnu", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
}
TEST_F(AProfileParser, IsAArch32BlankCPU) {
TargetJSON target = ParseTarget({
{"kind", ffi::String("llvm")},
{"mtriple", ffi::String("arm-unknown-linux-gnu")},
});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
}
TEST_F(AProfileParser, IsAArch32TripleWithAProfile) {
TargetJSON target = ParseTargetWithAttrs("cortex-a53", "armv7a-arm-none-eabi", {""});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
target = ParseTargetWithAttrs("cortex-a53", "armv8a-arm-none-eabi", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
target = ParseTargetWithAttrs("cortex-a53", "arm-unknown-linux-gnu", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("is_aarch64")), false);
}
TEST_F(AProfileParser, IsAArch32TripleWithMProfile) {
TargetJSON target = ParseTargetWithAttrs("cortex-m33", "armv7a-arm-none-eabi", {""});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), false);
target = ParseTargetWithAttrs("cortex-m33", "armv8a-arm-none-eabi", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), false);
target = ParseTargetWithAttrs("cortex-m33", "arm-unknown-linux-gnu", {""});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), false);
}
TEST_F(AProfileParser, AArch64HasASIMD) {
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {""});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
}
TEST_F(AProfileParser, AArch32ASIMD) {
TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
}
TEST_F(AProfileParser, AArch32HasASIMDWithOption) {
TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {"+simd"});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
}
TEST_F(AProfileParser, AArch32HasASIMDWithAlternativeOption) {
TargetJSON target = ParseTargetWithAttrs("", "armv8a-arm-none-eabi", {"+neon"});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_asimd")), true);
}
TEST_F(AProfileParser, DefaultI8MMSupport) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultI8MM) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), true);
}
TEST_F(AProfileParser, DefaultI8MMSupportDisable) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultI8MM) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "-i8mm"});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), false);
}
using AProfileOptionalI8MM = AProfileParserTestWithParam;
TEST_P(AProfileOptionalI8MM, OptionalI8MMSupport) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), false);
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+i8mm"});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_matmul_i8")), true);
}
TEST_F(AProfileParser, DefaultDotProdSupport) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), true);
}
TEST_F(AProfileParser, DefaultDotProdSupportDisable) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "-dotprod"});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
}
using AProfileOptionalDotProd = AProfileParserTestWithParam;
TEST_P(AProfileOptionalDotProd, OptionalDotProdSupport) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+dotprod"});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), true);
}
TEST_F(AProfileParser, ArchVersionInvalidLetter) {
std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(defaultDotProd) + "b";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_EQ(IsArch(target), true);
ASSERT_EQ(Downcast<Bool>(features.at("has_dotprod")), false);
}
using AProfileOptionalSVE = AProfileParserTestWithParam;
TEST_P(AProfileOptionalSVE, OptionalSVESupport) {
const std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a";
// Check that the "has_sve" feature is not set by default when "+sve" isn't set as an attribute.
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_FALSE(Downcast<Bool>(features.at("has_sve")));
// Check that the "has_sve" feature is set when "+sve" is explicitly set as an attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_sve")));
}
TEST_F(AProfileParser, DefaultSVESupportSVESupport) {
const std::string arch_attr = "+v9a";
// Check that the "has_sve" feature is not set by default when "+sve" isn't set as an attribute.
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
#if TVM_LLVM_VERSION >= 190 || (TVM_LLVM_VERSION / 10) == 13
// The generic aarch64 should not have SVE enabled
EXPECT_FALSE(Downcast<Bool>(features.at("has_sve")));
#else
EXPECT_TRUE(Downcast<Bool>(features.at("has_sve")));
#endif
// Check that the "has_sve" feature is set when "+sve" is explicitly set as an attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_sve")));
}
using AProfileOptionalFP16 = AProfileParserTestWithParam;
TEST_P(AProfileOptionalFP16, OptionalFP16Support) {
const std::string arch_attr = "+v" + FloatToStringWithoutTrailingZeros(GetParam()) + "a";
// Check that the "has_fp16_simd" feature is not set by default when "+fullfp16" isn't set as an
// attribute.
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_FALSE(Downcast<Bool>(features.at("has_fp16_simd")));
// Check that the "has_fp16_simd" feature is set when "+fullfp16" is explicitly set as an
// attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+fullfp16"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
// Check that the "has_fp16_simd" feature is set when "+sve" is explicitly set as an attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
}
TEST_F(AProfileParser, DefaultFP16Support) {
const std::string arch_attr = "+v9a";
// Check that the "has_fp16_simd" feature is not set by default when "+fullfp16" isn't set as an
// attribute.
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
#if TVM_LLVM_VERSION >= 190 || (TVM_LLVM_VERSION / 10) == 13
// The generic aarch64 should not have FP16 enabled
EXPECT_FALSE(Downcast<Bool>(features.at("has_fp16_simd")));
#else
EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
#endif
// Check that the "has_fp16_simd" feature is set when "+fullfp16" is explicitly set as an
// attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+fullfp16"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
// Check that the "has_fp16_simd" feature is set when "+sve" is explicitly set as an attribute.
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sve"});
features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(IsArch(target));
EXPECT_TRUE(Downcast<Bool>(features.at("has_fp16_simd")));
}
TEST_F(AProfileParser, ImpliedFeature) {
TargetJSON target = ParseTargetWithAttrs("", "aarch64-linux-gnu", {"+sve2"});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
EXPECT_TRUE(Downcast<Bool>(features.at("has_sve")));
EXPECT_TRUE(Downcast<Bool>(features.at("has_asimd")));
}
TEST_F(AProfileParser, UnexpectedTargetKind) {
EXPECT_THROW(
{
try {
ParseTarget({{"kind", ffi::String("c")}});
} catch (const tvm::InternalError& e) {
EXPECT_THAT(e.what(), HasSubstr("Expected target kind 'llvm', but got 'c'"));
throw;
}
},
tvm::InternalError);
}
TEST(AProfileParserInvalid, LLVMUnsupportedArchitecture) {
if (has_aarch64_and_arm_targets) {
GTEST_SKIP() << "LLVM has been compiled for the correct targets.";
}
TargetJSON target = ParseTarget({{"kind", ffi::String("llvm")}});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
for (auto feature : features) {
ASSERT_EQ(Downcast<Bool>(feature.second), false);
}
}
using AProfileOptionalSME = AProfileParserTestWithParam;
TEST_P(AProfileOptionalSME, OptionalSMESupport) {
const std::string arch_attr = "+v9a";
TargetJSON target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr});
TargetFeatures features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_TRUE(IsArch(target));
ASSERT_FALSE(Downcast<Bool>(features.at("has_sme")));
target = ParseTargetWithAttrs("", "aarch64-arm-none-eabi", {arch_attr, "+sme"});
features = Downcast<TargetFeatures>(target.at("features"));
ASSERT_TRUE(IsArch(target));
ASSERT_TRUE(Downcast<Bool>(features.at("has_sme")));
}
INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalI8MM, ::testing::ValuesIn(optionalI8MM));
INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalDotProd,
::testing::ValuesIn(optionalDotProd));
INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalSVE,
::testing::Values(8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9));
INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalFP16,
::testing::Values(8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9));
INSTANTIATE_TEST_SUITE_P(AProfileParser, AProfileOptionalSME, ::testing::ValuesIn(optionalSME));
} // namespace aprofile
} // namespace parsers
} // namespace target
} // namespace tvm