https://github.com/jofrn updated https://github.com/llvm/llvm-project/pull/199391
>From 22d9a5d9e35be5a1ec21278aad4d979ac2acaa60 Mon Sep 17 00:00:00 2001 From: jofrn <[email protected]> Date: Sat, 23 May 2026 18:16:31 -0700 Subject: [PATCH 1/2] Add --fn flag to llvm-lit to inject select-function pass into opt pipelines Translates --fn=fn0,fn1 into -passes='select-function<fn=fn0;fn=fn1>,...' by rewriting -passes= arguments in RUN lines after substitution. Handles both single and double quoted pass pipelines. --- llvm/utils/lit/lit/LitConfig.py | 2 ++ llvm/utils/lit/lit/TestRunner.py | 19 +++++++++++++++ llvm/utils/lit/lit/cl_arguments.py | 17 ++++++++++++++ llvm/utils/lit/lit/main.py | 1 + .../lit/tests/Inputs/fn-selection/lit.cfg | 7 ++++++ .../lit/tests/Inputs/fn-selection/sample.ll | 2 ++ llvm/utils/lit/tests/fn-selection.py | 23 +++++++++++++++++++ 7 files changed, 71 insertions(+) create mode 100644 llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg create mode 100644 llvm/utils/lit/tests/Inputs/fn-selection/sample.ll create mode 100644 llvm/utils/lit/tests/fn-selection.py diff --git a/llvm/utils/lit/lit/LitConfig.py b/llvm/utils/lit/lit/LitConfig.py index 4be2a0f6d8121..acc8c52ea27c8 100644 --- a/llvm/utils/lit/lit/LitConfig.py +++ b/llvm/utils/lit/lit/LitConfig.py @@ -42,6 +42,7 @@ def __init__( per_test_coverage=False, gtest_sharding=True, update_tests=False, + fnSelection=None, ): # The name of the test runner. self.progname = progname @@ -96,6 +97,7 @@ def __init__( self.gtest_sharding = bool(gtest_sharding) self.update_tests = update_tests self.test_updaters = [diff_test_updater] + self.fnSelection = fnSelection @property def maxIndividualTestTime(self): diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py index 82852f1852705..cc7c93848f09e 100644 --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -1921,6 +1921,23 @@ def _replaceReadFile(match): commandLine = "%s && test -e %s" % (commandLine, filePath) return commandLine + +def _applyFnSelection(script, fn_names): + """Inject select-function pass into opt -passes= pipelines.""" + if not fn_names: + return script + fn_args = ";".join("fn=" + n for n in fn_names) + sel = "select-function<" + fn_args + ">" + out = [] + for cmd in script: + # -passes='...' or -passes="..." + cmd = re.sub(r"""-passes=(['"])""", r"-passes=\1" + sel + ",", cmd) + # -passes=word (unquoted) — wrap in quotes to protect angle brackets + cmd = re.sub(r"-passes=([^'\"\s]\S*)", r"-passes='" + sel + r",\1'", cmd) + out.append(cmd) + return out + + def executeShTest( test, litConfig, useExternalSh, extra_substitutions=[], preamble_commands=[] ): @@ -1955,6 +1972,8 @@ def executeShTest( recursion_limit=test.config.recursiveExpansionLimit, ) + script = _applyFnSelection(script, litConfig.fnSelection) + if useExternalSh: for index, command in enumerate(script): script[index] = _expandLateSubstitutionsExternal(command) diff --git a/llvm/utils/lit/lit/cl_arguments.py b/llvm/utils/lit/lit/cl_arguments.py index bebde4b762b0e..fd8e9f5e26211 100644 --- a/llvm/utils/lit/lit/cl_arguments.py +++ b/llvm/utils/lit/lit/cl_arguments.py @@ -427,6 +427,16 @@ def parse_args(): help="Only run tests with paths matching the given regular expression", default=os.environ.get("LIT_FILTER", ".*"), ) + selection_group.add_argument( + "--fn", + dest="fnSelection", + metavar="NAMES", + type=_comma_list, + default=None, + help="Inject select-function pass into opt commands so only the " + "named functions (and their dependencies) are compiled. " + "NAMES is a comma-separated list of function names.", + ) selection_group.add_argument( "--filter-out", metavar="REGEX", @@ -587,6 +597,13 @@ def _semicolon_list(arg): return arg.split(";") +def _comma_list(arg): + names = [n.strip() for n in arg.split(",") if n.strip()] + if not names: + raise _error("empty function name list") + return names + + def _error(desc, *args): msg = desc.format(*args) return argparse.ArgumentTypeError(msg) diff --git a/llvm/utils/lit/lit/main.py b/llvm/utils/lit/lit/main.py index 77b23bf560c6e..2dcd0dc328957 100755 --- a/llvm/utils/lit/lit/main.py +++ b/llvm/utils/lit/lit/main.py @@ -44,6 +44,7 @@ def main(builtin_params={}): gtest_sharding=opts.gtest_sharding, maxRetriesPerTest=opts.maxRetriesPerTest, update_tests=opts.update_tests, + fnSelection=opts.fnSelection, ) discovered_tests = lit.discovery.find_tests_for_inputs( diff --git a/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg b/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg new file mode 100644 index 0000000000000..3f700409586fc --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg @@ -0,0 +1,7 @@ +import lit.formats + +config.name = "fn-selection" +config.suffixes = [".ll"] +config.test_format = lit.formats.ShTest() +config.test_source_root = None +config.test_exec_root = None diff --git a/llvm/utils/lit/tests/Inputs/fn-selection/sample.ll b/llvm/utils/lit/tests/Inputs/fn-selection/sample.ll new file mode 100644 index 0000000000000..30c01a9469750 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/fn-selection/sample.ll @@ -0,0 +1,2 @@ +; RUN: echo -passes='instcombine,mem2reg' +; RUN: echo -passes="instcombine,mem2reg" diff --git a/llvm/utils/lit/tests/fn-selection.py b/llvm/utils/lit/tests/fn-selection.py new file mode 100644 index 0000000000000..2a11cb7e15bba --- /dev/null +++ b/llvm/utils/lit/tests/fn-selection.py @@ -0,0 +1,23 @@ +# Verify lit's --fn flag injects the select-function pass into -passes= args. + +# --- --fn=foo: single function --- +# RUN: %{lit} -a --fn=foo %{inputs}/fn-selection/sample.ll \ +# RUN: | FileCheck --check-prefix=SINGLE %s +# +# SINGLE: -passes='select-function<fn=foo>,instcombine,mem2reg' +# SINGLE: -passes="select-function<fn=foo>,instcombine,mem2reg" + +# --- --fn=foo,bar: multiple functions --- +# RUN: %{lit} -a --fn=foo,bar %{inputs}/fn-selection/sample.ll \ +# RUN: | FileCheck --check-prefix=MULTI %s +# +# MULTI: -passes='select-function<fn=foo;fn=bar>,instcombine,mem2reg' +# MULTI: -passes="select-function<fn=foo;fn=bar>,instcombine,mem2reg" + +# --- No --fn: passes unchanged --- +# RUN: %{lit} -a %{inputs}/fn-selection/sample.ll \ +# RUN: | FileCheck --check-prefix=NONE %s +# +# NONE-NOT: select-function +# NONE: -passes='instcombine,mem2reg' +# NONE: -passes="instcombine,mem2reg" >From e7101217a21d1e3815bbd5e9654ad42c3a759eaf Mon Sep 17 00:00:00 2001 From: jofrn <[email protected]> Date: Wed, 27 May 2026 20:37:51 -0700 Subject: [PATCH 2/2] refactor to move function-selection from lit core into llvm/test config --- llvm/test/lit.cfg.py | 4 ++++ llvm/utils/lit/lit/LitConfig.py | 2 -- llvm/utils/lit/lit/TestRunner.py | 19 --------------- llvm/utils/lit/lit/cl_arguments.py | 17 -------------- llvm/utils/lit/lit/llvm/fn_selection.py | 23 +++++++++++++++++++ llvm/utils/lit/lit/main.py | 1 - .../lit/tests/Inputs/fn-selection/lit.cfg | 3 +++ llvm/utils/lit/tests/fn-selection.py | 13 ++++++----- 8 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 llvm/utils/lit/lit/llvm/fn_selection.py diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py index 09df1e3fd6281..0cef6ff30191c 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -39,6 +39,10 @@ ) config.test_format = lit.formats.ShTest(not use_lit_shell, extra_substitutions) +from lit.llvm import fn_selection + +fn_selection.install(config, lit_config) + # suffixes: A list of file extensions to treat as test files. This is overriden # by individual lit.local.cfg files in the test subdirectories. config.suffixes = [".ll", ".c", ".test", ".txt", ".s", ".mir", ".yaml", ".spv"] diff --git a/llvm/utils/lit/lit/LitConfig.py b/llvm/utils/lit/lit/LitConfig.py index acc8c52ea27c8..4be2a0f6d8121 100644 --- a/llvm/utils/lit/lit/LitConfig.py +++ b/llvm/utils/lit/lit/LitConfig.py @@ -42,7 +42,6 @@ def __init__( per_test_coverage=False, gtest_sharding=True, update_tests=False, - fnSelection=None, ): # The name of the test runner. self.progname = progname @@ -97,7 +96,6 @@ def __init__( self.gtest_sharding = bool(gtest_sharding) self.update_tests = update_tests self.test_updaters = [diff_test_updater] - self.fnSelection = fnSelection @property def maxIndividualTestTime(self): diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py index cc7c93848f09e..82852f1852705 100644 --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -1921,23 +1921,6 @@ def _replaceReadFile(match): commandLine = "%s && test -e %s" % (commandLine, filePath) return commandLine - -def _applyFnSelection(script, fn_names): - """Inject select-function pass into opt -passes= pipelines.""" - if not fn_names: - return script - fn_args = ";".join("fn=" + n for n in fn_names) - sel = "select-function<" + fn_args + ">" - out = [] - for cmd in script: - # -passes='...' or -passes="..." - cmd = re.sub(r"""-passes=(['"])""", r"-passes=\1" + sel + ",", cmd) - # -passes=word (unquoted) — wrap in quotes to protect angle brackets - cmd = re.sub(r"-passes=([^'\"\s]\S*)", r"-passes='" + sel + r",\1'", cmd) - out.append(cmd) - return out - - def executeShTest( test, litConfig, useExternalSh, extra_substitutions=[], preamble_commands=[] ): @@ -1972,8 +1955,6 @@ def executeShTest( recursion_limit=test.config.recursiveExpansionLimit, ) - script = _applyFnSelection(script, litConfig.fnSelection) - if useExternalSh: for index, command in enumerate(script): script[index] = _expandLateSubstitutionsExternal(command) diff --git a/llvm/utils/lit/lit/cl_arguments.py b/llvm/utils/lit/lit/cl_arguments.py index fd8e9f5e26211..bebde4b762b0e 100644 --- a/llvm/utils/lit/lit/cl_arguments.py +++ b/llvm/utils/lit/lit/cl_arguments.py @@ -427,16 +427,6 @@ def parse_args(): help="Only run tests with paths matching the given regular expression", default=os.environ.get("LIT_FILTER", ".*"), ) - selection_group.add_argument( - "--fn", - dest="fnSelection", - metavar="NAMES", - type=_comma_list, - default=None, - help="Inject select-function pass into opt commands so only the " - "named functions (and their dependencies) are compiled. " - "NAMES is a comma-separated list of function names.", - ) selection_group.add_argument( "--filter-out", metavar="REGEX", @@ -597,13 +587,6 @@ def _semicolon_list(arg): return arg.split(";") -def _comma_list(arg): - names = [n.strip() for n in arg.split(",") if n.strip()] - if not names: - raise _error("empty function name list") - return names - - def _error(desc, *args): msg = desc.format(*args) return argparse.ArgumentTypeError(msg) diff --git a/llvm/utils/lit/lit/llvm/fn_selection.py b/llvm/utils/lit/lit/llvm/fn_selection.py new file mode 100644 index 0000000000000..e99474984f274 --- /dev/null +++ b/llvm/utils/lit/lit/llvm/fn_selection.py @@ -0,0 +1,23 @@ +"""Splice a `select-function<fn=...>` pass at the head of every `-passes=` +pipeline so only the named functions (and their transitive dependencies) are +compiled by `opt`. Driven by `--param fn=NAMES`.""" + +from lit.TestingConfig import SubstituteCaptures + + +def install(config, lit_config): + fn = lit_config.params.get("fn") + if not fn: + return + names = [n.strip() for n in fn.split(",") if n.strip()] + if not names: + return + sel = "select-function<" + ";".join("fn=" + n for n in names) + ">" + # -passes='...' / -passes="..." — splice select-function after the quote + config.substitutions.append( + (r"""-passes=(['"])""", SubstituteCaptures(r"-passes=\1" + sel + ",")) + ) + # -passes=word (unquoted) — wrap to protect angle brackets + config.substitutions.append( + (r"-passes=([^'\"\s]\S*)", SubstituteCaptures(r"-passes='" + sel + r",\1'")) + ) diff --git a/llvm/utils/lit/lit/main.py b/llvm/utils/lit/lit/main.py index 2dcd0dc328957..77b23bf560c6e 100755 --- a/llvm/utils/lit/lit/main.py +++ b/llvm/utils/lit/lit/main.py @@ -44,7 +44,6 @@ def main(builtin_params={}): gtest_sharding=opts.gtest_sharding, maxRetriesPerTest=opts.maxRetriesPerTest, update_tests=opts.update_tests, - fnSelection=opts.fnSelection, ) discovered_tests = lit.discovery.find_tests_for_inputs( diff --git a/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg b/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg index 3f700409586fc..2298433995293 100644 --- a/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg +++ b/llvm/utils/lit/tests/Inputs/fn-selection/lit.cfg @@ -1,7 +1,10 @@ import lit.formats +from lit.llvm import fn_selection config.name = "fn-selection" config.suffixes = [".ll"] config.test_format = lit.formats.ShTest() config.test_source_root = None config.test_exec_root = None + +fn_selection.install(config, lit_config) diff --git a/llvm/utils/lit/tests/fn-selection.py b/llvm/utils/lit/tests/fn-selection.py index 2a11cb7e15bba..68237ae016a56 100644 --- a/llvm/utils/lit/tests/fn-selection.py +++ b/llvm/utils/lit/tests/fn-selection.py @@ -1,20 +1,21 @@ -# Verify lit's --fn flag injects the select-function pass into -passes= args. +# Verify --param fn=NAMES splices select-function into -passes= pipelines via +# lit.llvm.fn_selection (which is also wired into llvm/test/lit.cfg.py). -# --- --fn=foo: single function --- -# RUN: %{lit} -a --fn=foo %{inputs}/fn-selection/sample.ll \ +# --- --param fn=foo: single function --- +# RUN: %{lit} -a --param fn=foo %{inputs}/fn-selection/sample.ll \ # RUN: | FileCheck --check-prefix=SINGLE %s # # SINGLE: -passes='select-function<fn=foo>,instcombine,mem2reg' # SINGLE: -passes="select-function<fn=foo>,instcombine,mem2reg" -# --- --fn=foo,bar: multiple functions --- -# RUN: %{lit} -a --fn=foo,bar %{inputs}/fn-selection/sample.ll \ +# --- --param fn=foo,bar: multiple functions --- +# RUN: %{lit} -a --param fn=foo,bar %{inputs}/fn-selection/sample.ll \ # RUN: | FileCheck --check-prefix=MULTI %s # # MULTI: -passes='select-function<fn=foo;fn=bar>,instcombine,mem2reg' # MULTI: -passes="select-function<fn=foo;fn=bar>,instcombine,mem2reg" -# --- No --fn: passes unchanged --- +# --- No --param: passes unchanged --- # RUN: %{lit} -a %{inputs}/fn-selection/sample.ll \ # RUN: | FileCheck --check-prefix=NONE %s # _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
