Skip to content

Conversation

@banach-space
Copy link
Contributor

@banach-space banach-space commented Mar 25, 2024

Integration tests for ArmSME require an emulator (there's no hardware
available). Make sure that CMake complains if MLIR_RUN_ARM_SME_TESTS
is set while ARM_EMULATOR_EXECUTABLE is empty.

I'm also adding a note in the docs for future reference.

Integration tests for ArmSME require an emulator (there's no hardware
available). Disable the tests when an emulator is not available.

I'm also adding a note in the docs for future reference.
@llvmbot
Copy link
Member

llvmbot commented Mar 25, 2024

@llvm/pr-subscribers-mlir-sme
@llvm/pr-subscribers-mlir-vector
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-linalg

Author: Andrzej Warzyński (banach-space)

Changes

Integration tests for ArmSME require an emulator (there's no hardware
available). Disable the tests when an emulator is not available.

I'm also adding a note in the docs for future reference.


Full diff: https://github.com/llvm/llvm-project/pull/86489.diff

3 Files Affected:

  • (modified) mlir/docs/Dialects/ArmSME.md (+8)
  • (modified) mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/lit.local.cfg (+4)
  • (modified) mlir/test/Integration/Dialect/Vector/CPU/ArmSME/lit.local.cfg (+4)
diff --git a/mlir/docs/Dialects/ArmSME.md b/mlir/docs/Dialects/ArmSME.md
index 7326150bcd1156..66f62d07a78545 100644
--- a/mlir/docs/Dialects/ArmSME.md
+++ b/mlir/docs/Dialects/ArmSME.md
@@ -14,6 +14,14 @@ integration tests for reference:
 * [Linalg/CPU/ArmSME/matmul.mlir](https://github.com/llvm/llvm-project/blob/main/mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/matmul.mlir)
 * [Vector/CPU/ArmSME/test-outerproduct-f64.mlir](https://github.com/llvm/llvm-project/blob/main/mlir/test/Integration/Dialect/Vector/CPU/ArmSME/test-outerproduct-f64.mlir)
 
+In order to run ArmSME integration tests, include these flags in the CMake
+invokation when configuring LLVM and MLIR:
+```bash
+  -DMLIR_INCLUDE_INTEGRATION_TESTS=On
+  -DMLIR_RUN_ARM_SME_TESTS=On
+  -DARM_EMULATOR_EXECUTABLE=<path-to-emulator> 
+```
+
 These tests are run "post-commit" by the
 [clang-aarch64-sve-vla](https://lab.llvm.org/buildbot/#/builders/197) LLVM
 BuildBot worker.
diff --git a/mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/lit.local.cfg b/mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/lit.local.cfg
index 296b4419438e8a..083574b1af74d4 100644
--- a/mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/lit.local.cfg
+++ b/mlir/test/Integration/Dialect/Linalg/CPU/ArmSME/lit.local.cfg
@@ -4,6 +4,10 @@ import sys
 if not config.mlir_run_arm_sme_tests:
     config.unsupported = True
 
+# With no hardware available, ArmSME tests require emulation.
+if not config.arm_emulator_executable:
+    config.unsupported = True
+
 # No JIT on win32.
 if sys.platform == "win32":
     config.unsupported = True
diff --git a/mlir/test/Integration/Dialect/Vector/CPU/ArmSME/lit.local.cfg b/mlir/test/Integration/Dialect/Vector/CPU/ArmSME/lit.local.cfg
index 296b4419438e8a..083574b1af74d4 100644
--- a/mlir/test/Integration/Dialect/Vector/CPU/ArmSME/lit.local.cfg
+++ b/mlir/test/Integration/Dialect/Vector/CPU/ArmSME/lit.local.cfg
@@ -4,6 +4,10 @@ import sys
 if not config.mlir_run_arm_sme_tests:
     config.unsupported = True
 
+# With no hardware available, ArmSME tests require emulation.
+if not config.arm_emulator_executable:
+    config.unsupported = True
+
 # No JIT on win32.
 if sys.platform == "win32":
     config.unsupported = True

@github-actions
Copy link

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions
Copy link

✅ With the latest revision this PR passed the Python code formatter.

# With no hardware available, ArmSME tests require emulation.
if not config.arm_emulator_executable:
config.unsupported = True

Copy link
Collaborator

@joker-eph joker-eph Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have this be an error (inside CMake?): we shouldn't allow -DMLIR_RUN_ARM_SME_TESTS=On without the emulator.
Here the user will see ninja check-mlir complete successfully but ignoring these tests despite the CMake flag enabling them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, thanks! I've just updated this PR to use CMake instead.

@c-rhodes
Copy link
Collaborator

Who is this change for? I don't see why we need to do anything here, if anything this is creating work for our future selves.

@banach-space
Copy link
Contributor Author

Who is this change for? I don't see why we need to do anything here, if anything

This is for anyone who'd like to try the SME e2e tests in MLIR :) The intention is to document things better - both through "actual" documentation and config scripts (with the latest version, CMake will complain if you set MLIR_RUN_ARM_SME_TESTS but keep ARM_EMULATOR_EXECUTABLE empty).

this is creating work for our future selves

I'm happy to address any future issues that this may bring 😅 Also, it's just 3 lines of CMake.

@c-rhodes
Copy link
Collaborator

Who is this change for? I don't see why we need to do anything here, if anything

This is for anyone who'd like to try the SME e2e tests in MLIR :) The intention is to document things better - both through "actual" documentation and config scripts (with the latest version, CMake will complain if you set MLIR_RUN_ARM_SME_TESTS but keep ARM_EMULATOR_EXECUTABLE empty).

I think this is introducing unnecessary guardrails that aren't required to try the e2e tests. I would think it's pretty obvious an emulator is required when a user hits SIGILLs when trying to run the tests and that's reason enough that this isn't required, but this is also removing an option other users may want. I've seen setups in the past for example where emulation is enabled opaquely via binfmt_misc on Linux.

this is creating work for our future selves

I'm happy to address any future issues that this may bring 😅 Also, it's just 3 lines of CMake.

No lines is better than 3

@banach-space
Copy link
Contributor Author

I would think it's pretty obvious an emulator is required when a user hits SIGILLs when trying to run the tests

This change is trying to help folks for whom it would be less obvious :) And even for experts, a meaningful CMake error saying "you need to set-up this CMake variable" would be more informative compared to what we get today.

@joker-eph
Copy link
Collaborator

No lines is better than 3

This does not seem like a useful comment IMO.

A nice error message at configuration time is much better than a compile time failure, which is itself much better than a link-time failure, which itself is much better than a runtime test failure!

There is much more than "3 lines" in terms of complexity that exists in MLIR as safeguard against user mis-configuration.
For example detecting creation of operation or attribute when the dialect isn't loaded, or more recently the whole "promised" interfaces:

// Check that the current interface isn't an unresolved promise for the
// given operation.
if (Dialect *dialect = name.getDialect()) {
dialect_extension_detail::handleUseOfUndefinedPromisedInterface(
*dialect, name.getTypeID(), ConcreteType::getInterfaceID(),
llvm::getTypeName<ConcreteType>());
}

(same code exists in other places)
Leading to:
void handleUseOfUndefinedPromisedInterface(TypeID interfaceRequestorID,
TypeID interfaceID,
StringRef interfaceName = "") {
if (unresolvedPromisedInterfaces.count(
{interfaceRequestorID, interfaceID})) {
llvm::report_fatal_error(
"checking for an interface (`" + interfaceName +
"`) that was promised by dialect '" + getNamespace() +
"' but never implemented. This is generally an indication "
"that the dialect extension implementing the interface was never "
"registered.");
}
}

I've seen setups in the past for example where emulation is enabled opaquely via binfmt_misc on Linux.

That seems like a valid use-case, @banach-space how do you see this supported?
Also: what about someone who has the actual HW in the future?

@c-rhodes
Copy link
Collaborator

No lines is better than 3
This does not seem like a useful comment IMO.

Apologies, that wasn't helpful.

A nice error message at configuration time is much better than a compile time failure, which is itself much better than a link-time failure, which itself is much better than a runtime test failure!

I agree, hard to argue with that, failing early is a good thing.

I've seen setups in the past for example where emulation is enabled opaquely via binfmt_misc on Linux.

That seems like a valid use-case, @banach-space how do you see this supported? Also: what about someone who has the actual HW in the future?

You've touched on a further issue, if we had done this for SVE we wouldn't have been able to run the integration tests on hardware without removing it. However, we don't all have access to hardware at the same time, at what point do we decide this can be removed?

Lit has constraints [1] which are the typically used for situations like this. The REQUIRES constraint can be used to specify target but as far as I'm aware it doesn't support specific target features. It would be nice if the integration tests could have something like:

// REQUIRES: target=aarch64 features=sme

where config.available_features is filled in via host features or supported emulator features.

[1] https://llvm.org/docs/TestingGuide.html#constraining-test-execution

@c-rhodes
Copy link
Collaborator

I would think it's pretty obvious an emulator is required when a user hits SIGILLs when trying to run the tests

This change is trying to help folks for whom it would be less obvious :) And even for experts, a meaningful CMake error saying "you need to set-up this CMake variable" would be more informative compared to what we get today.

Apologies, that wasn't constructive. I can see the improvement you're trying to make and agree it's better to be pointed in the right direction. I'm just not sure about the current approach.

@banach-space
Copy link
Contributor Author

Thanks for the feedback!

That seems like a valid use-case, @banach-space how do you see this supported? Also: what about someone who has the actual HW in the future?

You've touched on a further issue, if we had done this for SVE we wouldn't have been able to run the integration tests on hardware without removing it. However, we don't all have access to hardware at the same time, at what point do we decide this can be removed?

There's a couple of points here.

1. binfmt_misc on Linux

I think that in terms of these integration tests, "less is more" and we should try to avoid supporting too many scenarios. When the set of scenarios is limited and well defined, reproducing failures and helping folks fix their issues over "e-mail" is much easier.

I also feel that we are making our job easier for ourselves if we clearly define what interfaces we'd like our users to use (e.g. ARM_EMULATOR_EXECUTABLE). Whatever that interface is, it should be documented. If we don't want to require ARM_EMULATOR_EXECUTABLE, then alternatives like binfmt_misc should be documented.

2. Hardware vs no-hardware

It's not clear to me how to best support such cases. SVE is a great example - some folks have access to hardware, others don't (i.e. some people require an emulator, others don't). But this isn't really SVE specific, right? It's more about the overall infra that we have in MLIR.

Today, there's no SME hardware, so the approach proposed here is valid. Once SME hardware is available, that won't be the case and that's when I'd revert this. Something longer term would be much better, I agree.

LIT constraints

Lit has constraints [1] which are the typically used for situations like this.

These constraints are "compile-time" rather than "run-time" constraints. For example, things like available targets (for stuff // REQUIRES: target=aarch64 ) are effectively taken from the list of LLVM targets that you decide to build:

config.target_triple = "@LLVM_TARGET_TRIPLE@"

and:
features.add("target=%s" % target_triple)

I don't see how we could use these here today (see below for an idea).

Alternatives

Option 1

Ultimately, on unsupported hardware, it's mlir-cpu-runner (instructed by the user) that's attempting to run code requiring feature "X" on hardware that has no such feature. So perhaps mlir-cpu-runner should query the host? But then I'm not sure how to "mix" that with potential emulation.

Option 2

One other option would involve updating CMake. There's LLVM_TARGET_ARCH and we could add LLVM_TARGET_ARCH_FEATURES. That could be wired up with the LIT logic, e.g.:

// REQUIRES: (target=aarch64 AND target-arch-feature=sme) OR arm-emulator-executable

I guess that this could scale nicely to other targets?

Thoughts?

@joker-eph
Copy link
Collaborator

joker-eph commented Mar 27, 2024

So perhaps mlir-cpu-runner should query the host? But then I'm not sure how to "mix" that with potential emulation.

The lit feature could be "support-sme" and would be a combination of "HW has native support" OR "the emulator is provided"?

(I think it's a bit like your option 2, I would just make this a feature of its own in our lit config)

@banach-space
Copy link
Contributor Author

So perhaps mlir-cpu-runner should query the host? But then I'm not sure how to "mix" that with potential emulation.

The lit feature could be "support-sme" and would be a combination of "HW has native support" OR "the emulator is provided"?

Sure, but I wouldn't try mixing mlir-cpu-runner with LIT features - these things are orthogonal. What I had in mind is to make mlir-cpu-runner query the host via a syscall or something similar. Option 2 (LIT + CMake) feels more natural to me.

@joker-eph
Copy link
Collaborator

joker-eph commented Mar 27, 2024

How do you query the HW support during the lit config phase though?

@banach-space
Copy link
Contributor Author

How do you query the HW support during the lit config phase though?

The information would have come from the user. Here's what I'm proposing:

  1. Introduce new CMake flag: LLVM_TARGET_ARCH_FEATURES. Propagate as config.target_features in LIT.
  2. Require one of the two:
  • -DMLIR_RUN_ARM_SME_TESTS -DARM_EMULATOR_EXECUTABLE=<> (run via emulator), or
  • -DMLIR_RUN_ARM_SME_TESTS -DLLVM_TARGET_ARCH_FEATURES="sme" (run on hardware once available).

Setting -DLLVM_TARGET_ARCH_FEATURES="sme" on hardware that doesn't support SME would be a user error.

Not ideal and not that automatic, but that's the best I have ATM. I don't know how to reliably query hardware, hence I am hesitant to explore that. Suggestions are very welcome though!

@joker-eph
Copy link
Collaborator

joker-eph commented Mar 28, 2024

I don't quite get the proposal, what is the meaning of LLVM_TARGET_ARCH_FEATURES?

It does not seem to provide value to me to do so, it seems to me that the user is already explicit with -DMLIR_RUN_ARM_SME_TESTS=ON, so I can see the value of detecting that something is missing to be able to run the tests and provide a good diagnostic to the user, but I can't quite see how asking the user to provide another extra flag is useful.

I don't know how to reliably query hardware,

Have cmake compile an input file, run it, and check the result?

@c-rhodes
Copy link
Collaborator

I don't know how to reliably query hardware,

Have cmake compile an input file, run it, and check the result?

There's an example in compiler-rt [1] checking if the host compiler supports SME, perhaps could use the same input/test but compile and run with CMake try_run [2] instead?

There's also the __builtin_cpu_supports builtin [3] which could be used in the input/test, but not sure how well supported that is. It seems for GCC it's implemented for x86 [4] but not AArch64 [5]. Host toolchain support can be queried with __has_builtin(__builtin_cpu_supports) as [3] mentions.

[1]

builtin_check_c_compiler_source(COMPILER_RT_HAS_AARCH64_SME
"
void foo(void) __arm_streaming_compatible {
asm(\".arch armv9-a+sme\");
asm(\"smstart\");
}
")

[2] https://cmake.org/cmake/help/latest/command/try_run.html#command:try_run
[3] https://clang.llvm.org/docs/LanguageExtensions.html#builtin-cpu-supports
[4] https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html#index-_005f_005fbuiltin_005fcpu_005fsupports-1
[5] https://gcc.gnu.org/onlinedocs/gcc/AArch64-Built-in-Functions.html

Adds two new CMake functions to query the host system:

  * `check_hwcap`,
  * `check_emulator`.

Together, these functions are used to check whether a given set of MLIR
integration tests require an emulator. If yes, then the corresponding
CMake var that defies the required emulator executable is also checked.

`check_hwcap` relies on ELF_HWCAP for discovering CPU features from
userspace on Linux systems. This is the recommended approach for Arm
CPUs running on Linux as outlined in this blog post:

  * https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/runtime-detection-of-cpu-features-on-an-armv8-a-cpu

Other operating systems (e.g. Android) and CPU architectures will
most likely require some other approach. Right now these new hooks are
only used for SVE and SME integration tests.
@banach-space
Copy link
Contributor Author

Thanks for the pointers Cullen, that was very helpful! I've re-implemented this PR - please check the most recent commit:

I don't know how to reliably query hardware,

Have cmake compile an input file, run it, and check the result?

👍🏻 I have found a solution, but as hinted in the commit msg it's quite specific to Arm CPUs running on Linux. I can extend that to Android once/if required, but supporting other non-Arm hardware will require input from experts that actually know it 😅

return (hwcaps & <HWCAP_SPEC>) != 0;
}
]====]
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you'd write some inline ASM performing a simple SVE operation and check that it does not crash, so that you wouldn't depend on any header.

I'm fine with this though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you'd write some inline ASM performing a simple SVE operation and check that it does not crash, so that you wouldn't depend on any header.

I'm fine with this though.

Likewise, perhaps more portable that way, but I can see merits in this approach as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I wanted to use something that's documented - I came across HWCAP and I really like the end result.
  2. I want to avoid distributing and running code that might crash. IMHO, if there's a solution that avoids that then that's better :)

Copy link
Collaborator

@joker-eph joker-eph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, but please wait for @c-rhodes to double check if that works for their use-cases as well.

Thanks!

Copy link
Collaborator

@c-rhodes c-rhodes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few minor comments but also LGTM, cheers!

return (hwcaps & <HWCAP_SPEC>) != 0;
}
]====]
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you'd write some inline ASM performing a simple SVE operation and check that it does not crash, so that you wouldn't depend on any header.

I'm fine with this though.

Likewise, perhaps more portable that way, but I can see merits in this approach as well.

Move new CMake logic to a seperate file, fix typos
@banach-space banach-space merged commit 7b52552 into llvm:main Apr 4, 2024
banach-space added a commit that referenced this pull request Apr 5, 2024
Adds two new CMake functions to query the host system:

  * `check_hwcap`,
  * `check_emulator`.

Together, these functions are used to check whether a given set of MLIR
integration tests require an emulator. If yes, then the corresponding
CMake var that defies the required emulator executable is also checked.

`check_hwcap` relies on ELF_HWCAP for discovering CPU features from
userspace on Linux systems. This is the recommended approach for Arm
CPUs running on Linux as outlined in this blog post:

  * https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/runtime-detection-of-cpu-features-on-an-armv8-a-cpu

Other operating systems (e.g. Android) and CPU architectures will
most likely require some other approach. Right now these new hooks are
only used for SVE and SME integration tests.

This relands #86489 with the following changes:
  * Replaced:
      `set(hwcap_test_file ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/hwcap_check.c)`
    with:
      `set(hwcap_test_file ${CMAKE_BINARY_DIR}/temp/hwcap_check.c)`
    The former would trigger an infinite loop when running `ninja`
    (after the initial CMake configuration).
  * Fixed commit msg. Previous one was taken from the initial GH PR
    commit rather than the final re-worked solution (missed this when
    merging via GH UI).
  * A couple more NFCs/tweaks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants