Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SYCL][E2E] Add logic to react to REQUIRED/UNSUPPORTED in build-only #16725

Draft
wants to merge 11 commits into
base: sycl
Choose a base branch
from

Conversation

ayylol
Copy link
Contributor

@ayylol ayylol commented Jan 21, 2025

Adds logic for evaluating REQUIRES/UNSUPPORTED statements on build-only mode by "ignoring" features in these statements that do not affect the compilation (Everything other than OS, triple, and SDK/libraries).

More precisely, the ability to ignore features is implemented by extending the boolean expressions to use a third boolean state - ignore. If a particular sub-expression includes an ignore, and if its result could be changed by setting that ignore to either true or false, then the result of that sub-expression is set to ignore. For example ignore || true = true, but ignore || false = ignore, this is because in the first example there would be no way to set the ignore such that the result is anything other than true, while in the second example the result is dependent on the "actual" value of the ignore.

If the resulting value of a REQUIRES predicate is ignore we interpret that as true (The requirements were met), on the other hand for UNSUPPORTED predicates we would interpret ignore as false instead (The unsupported criteria was not met).

The triples that can be used for a given test are then selected by evaluating the REQUIRES/UNSUPPORTED with the set of features + the target feature corresponding to each triple (mirroring the way we select devices), while ignoring all features that do not affect compilation.

Similarly to how XFAIL is handled when using multiple devices if we are compiling for multiple triples, and a single triple is marked as XFAIL, then it is treated as unsupported instead.

The available target triples in build-only mode is determined through the use of the sycl_triples lit param (i.e., --param sycl_triples=spir;amd). This is currently set to only spir, as the changes in test markup included in this pr do not take into account building for nvptx and amdgcn triples. In run-only and full mode the available triples are determined via the available devices.

@ayylol
Copy link
Contributor Author

ayylol commented Jan 21, 2025

This pr is the logic from #16637, but only with support for spir64 triple, that way 205 file changes do not need to reviewed at once. Changes to the test markup to support amd and nvidia will be made in future prs.

Comment on lines 122 to 127
self.assertTrue(
E2EExpr.evaluate("!rt_feature", {"rt_feature"}, True, True)
)
self.assertTrue(
E2EExpr.evaluate("!!rt_feature", {"rt_feature"}, True, True)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, there are far too many tests with {"rt_feature"} when run time features are to be ignored. We don't expect the set of "set" features to contain them, so why are we testing that scenario mostly?

I can understand having a couple of tests for completeness/to document behavior, but having too many is counterproductive and might confuse the reader about the purpose of this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of deleting these, I just made the available features list empty. Does that work? otherwise I can delete them.

My intention with these was to show that ignore values propagate up the expression.

@ayylol ayylol marked this pull request as draft January 21, 2025 21:34
Copy link
Contributor

@sarnex sarnex left a comment

Choose a reason for hiding this comment

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

quick initial review, will look more indepth once andrei's feedback is addressed

sycl/test-e2e/TriStateExpr.py Outdated Show resolved Hide resolved
Comment on lines 105 to 107
if self.getMissingRequiredFeaturesFromList(features, test.requires, build_only_mode=True):
continue
if self.getMatchedFromList(features, test.unsupported, build_only_mode=True):
Copy link
Contributor

Choose a reason for hiding this comment

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

these functions are called get but are being used as has it seems, should we rename?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hasMissingRequiredFeatures and hasUnsupportedFeatures? or what were you thinking. One thing to note that brought some confusion in the past is the fact that these dont report the specific features that were missing to pass the expression, rather it returns the clauses that did not pass.

Also should note that getMissingRequiredFeaturesFromList is named after a function of the same name in the lit implementation. As for getMatchedFromList that one doesnt have an exact corresponding function, but there is getUnsupportedFeatures which is similar, but does not have an argument to pass in the features available.

Copy link
Contributor

Choose a reason for hiding this comment

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

do we ever use the return value's contents or is it just empty or not? if its only empty or not i think we should rename to has..., otherwise current is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One small caveat, the getMatchedFromList function is also used for determining XFAILs, so im not entirely sure hasUnsupportedFeatures would be entirely accurate.

This False implies that we're processing UNSUPPORTED, we need to modify the name of this function to indicate that now. Does lit.Test have some name that we can copy?

@aelovikov-intel tagging you here, because I think these comments are asking for the same thing, going to mark the other as resolved and we can continue discussion here

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, keeping it close to lit.Test or whatever the names came from is more important than renaming just because we don't use the result.

Copy link
Contributor

Choose a reason for hiding this comment

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

One small caveat, the getMatchedFromList function is also used for determining XFAILs, so im not entirely sure hasUnsupportedFeatures would be entirely accurate.

In this case, I'd avoid hardcoding False in the body and pass it as an additional parameter. Yes, it's kind of useless, but it would make interface more consistent.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah im not super familiar with the implementation of lit so if the function names are standard then its fine

sycl/test-e2e/format.py Show resolved Hide resolved
sycl/test-e2e/format.py Outdated Show resolved Hide resolved
sycl/test-e2e/lit.cfg.py Outdated Show resolved Hide resolved
sycl/test-e2e/TriStateExpr.py Outdated Show resolved Hide resolved
sycl/test-e2e/format.py Outdated Show resolved Hide resolved
Comment on lines 852 to 854
target = config.backend_to_target[be]
features.add(target)
config.sycl_triples.add(target)
Copy link
Contributor

@Maetveis Maetveis Jan 22, 2025

Choose a reason for hiding this comment

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

Backend is the underlying runtime that sycl is implemented on, right? Isn't it possible to have the same backend support multiple targets?. For example using OpenCL on AMD or NVIDIA (theoretically of course since practically they would have to support the extensions sycl requires).

I think if backend_to_target would be backend_default_target then it leaves the design open to add an override via some option like --param backend_targets_<be>=<target1>,<target2>.
Or maybe this is what architectures is for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think if backend_to_target would be backend_default_target then it leaves the design open to add an override via some option like --param backend_targets_<be>=<target1>,<target2>.
Or maybe this is what architectures is for?

Yeah, something like that could be possible to do by overriding/changing the dictionary used for the mappings. Although I do still prefer the x_to_y naming scheme for these mapping dictionaries, since it groups them together with consistent naming, and I don't think it necessarily stops us from redefining them in the future through parameters like you suggest.

Comment on lines 25 to 31
config.target_to_triple = {
"target-spir": "spir64",
"target-nvidia":"nvptx64-nvidia-cuda",
"target-amd":"amdgcn-amd-amdhsa",
"target-native_cpu":"native_cpu",
}
config.triple_to_target = {v:k for k,v in config.target_to_triple.items()}
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between a "target" and the "triple" it corresponds to, given there's a 1:1 mapping between them? Is target just a short name for the triple? Is it an alias to allow changing the target triples without updating all the tests that refer to it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Short name was part of the motivation (at least for me). Also, Nick mentioned that there is some work to have two different spir triples (spir64 and spirv, I think).

Copy link
Contributor

@aelovikov-intel aelovikov-intel left a comment

Choose a reason for hiding this comment

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

Some minor naming suggestion, otherwise looks great.

@@ -2,8 +2,7 @@
// It tests if the target triples can be specified with any order.
// The test is repeated for per_kernel device code splitting.
//
// REQUIRES: cuda || hip || native_cpu
// REQUIRES: build-and-run-mode
// REQUIRES: target-nvidia || target-amd || target-native_cpu
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// REQUIRES: target-nvidia || target-amd || target-native_cpu
// REQUIRES: (target-nvidia || target-amd || target-native_cpu) && target-spir

"false",
}

def __init__(self, string, variables, build_only_mode, findal_unknown_value):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def __init__(self, string, variables, build_only_mode, findal_unknown_value):
def __init__(self, string, variables, build_only_mode, final_unknown_value):

here and everywhere below.



class TestE2EExpr(unittest.TestCase):
def test_basic(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see this:

  BuildOnly = True
  BuildAndRun = False
  RequiresDirective = True
  UnsupportedOrXFAILDirective = False
  self.assertTrue(E2EExpr.evaluate(<expr>, <features>, BuildOnly, RequiresDirective)

And now that I spelled it out, it can be even better (C++-like syntax, names to be improved):

   auto BuildOnlyRequires = [](auto expr, auto features) { ... };
   ...
   self.assertTrue(BuildOnlyRequires("linux", {"linux", "rt_feature"}))
   self.assertFalse(BuildOnlyUnsupported("rt_feature", {}))

Also, since we want these name improvements in the tests, we might as well want to propagate them all the way to the interfaces somehow.

)
self.assertTrue(
E2EExpr.evaluate(
"rt_feature", {"rt_feature"}, True, final_unknown_value=True
Copy link
Contributor

Choose a reason for hiding this comment

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

should we assert/raise an error instead?

)
self.assertTrue(
E2EExpr.evaluate(
"linux && !(windows || rt_feature)",
Copy link
Contributor

Choose a reason for hiding this comment

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

I have hard time mapping that to a possible REQURIES directive. I think changing "windows" to something non-OS might help, but even then I'm not sure what would be the best example. Same for the next one.

)
self.assertFalse(
E2EExpr.evaluate(
"linux && (windows && rt_feature)",
Copy link
Contributor

Choose a reason for hiding this comment

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

change "windows" to something that can make sense in real world. Same in the next one.

Copy link
Contributor

@sarnex sarnex left a comment

Choose a reason for hiding this comment

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

no high level flags from me, will leave in-depth review to andrei/others who are more familiar with lit's internals

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants