[Polly] Forward VFS from PassBuilder for IO sandboxing (#188657)

#184545 default-enables the IO sandbox in assert-builds. This causes
Clang using Polly to crash (#188568).

The issue is that `PassBuilder` uses `vfs::getRealFileSystem()` by
default which is considered a IO sandbox violation in the Clang process.
With this PR store the VFS from the `PassBuilder` from the original
`registerPollyPasses` call for creating other `PassBuilder` instances.

This PR also adds infrastructure for running Polly in `clang` (in
addition in `opt`). `opt` does not enable the sandbox such that we need
separate tests using Clang.

Closes: #188568
This commit is contained in:
Michael Kruse
2026-03-28 23:55:05 +01:00
committed by GitHub
parent 191a9a911c
commit 458f1aae8d
12 changed files with 89 additions and 42 deletions

View File

@@ -29,7 +29,7 @@ with worker.run(
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_C_COMPILER_LAUNCHER=ccache",
"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",
"-DLLVM_ENABLE_PROJECTS=polly",
"-DLLVM_ENABLE_PROJECTS=polly;clang",
"-DLLVM_TARGETS_TO_BUILD=X86",
"-DLLVM_ENABLE_LLD=ON",
"-DLLVM_ENABLE_ASSERTIONS=ON",

View File

@@ -30,7 +30,7 @@ with worker.run(
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_C_COMPILER_LAUNCHER=ccache",
"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",
"-DLLVM_ENABLE_PROJECTS=polly",
"-DLLVM_ENABLE_PROJECTS=polly;clang",
"-DLLVM_TARGETS_TO_BUILD=X86",
"-DLLVM_ENABLE_LLD=ON",
"-DLLVM_ENABLE_ASSERTIONS=ON",

View File

@@ -9,14 +9,23 @@
#ifndef POLLY_POLLYINLINER_H
#define POLLY_POLLYINLINER_H
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/Analysis/CGSCCPassManager.h"
#include "llvm/Analysis/LazyCallGraph.h"
#include "llvm/IR/PassManager.h"
namespace llvm {
namespace vfs {
class FileSystem;
}
} // namespace llvm
namespace polly {
class ScopInlinerPass : public llvm::PassInfoMixin<ScopInlinerPass> {
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
public:
ScopInlinerPass();
explicit ScopInlinerPass(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS);
llvm::PreservedAnalyses run(llvm::LazyCallGraph::SCC &C,
llvm::CGSCCAnalysisManager &AM,

View File

@@ -8,7 +8,7 @@ MODULE_PASS("polly-custom", createModuleToFunctionPassAdaptor(PollyFunctionPass(
#ifndef CGSCC_PASS
#define CGSCC_PASS(NAME, CREATE_PASS, PARSER)
#endif
CGSCC_PASS("polly-inline", ScopInlinerPass(), parseNoOptions)
CGSCC_PASS("polly-inline", ScopInlinerPass(FS), parseNoOptions)
#undef CGSCC_PASS
#ifndef FUNCTION_PASS

View File

@@ -459,8 +459,13 @@ parsePollyCustomOptions(StringRef Params) {
/// The IR may still be modified.
static void buildCommonPollyPipeline(FunctionPassManager &PM,
OptimizationLevel Level,
IntrusiveRefCntPtr<vfs::FileSystem> FS,
bool EnableForOpt) {
PassBuilder PB;
PassBuilder PB(
/*TM=*/nullptr,
/*PipelineTuningOptions=*/{},
/*PGOOpt=*/{},
/*PIC=*/nullptr, std::move(FS));
ExitOnError Err("Inconsistent Polly configuration: ");
PollyPassOptions &&Opts =
@@ -475,7 +480,8 @@ static void buildCommonPollyPipeline(FunctionPassManager &PM,
}
static void buildEarlyPollyPipeline(llvm::ModulePassManager &MPM,
llvm::OptimizationLevel Level) {
llvm::OptimizationLevel Level,
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
bool EnableForOpt =
shouldEnablePollyForOptimization() && Level.isOptimizingForSpeed();
if (!shouldEnablePollyForDiagnostic() && !EnableForOpt)
@@ -494,7 +500,7 @@ static void buildEarlyPollyPipeline(llvm::ModulePassManager &MPM,
FPM = FunctionPassManager();
}
buildCommonPollyPipeline(FPM, Level, EnableForOpt);
buildCommonPollyPipeline(FPM, Level, std::move(FS), EnableForOpt);
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
if (DumpAfter)
@@ -504,7 +510,8 @@ static void buildEarlyPollyPipeline(llvm::ModulePassManager &MPM,
}
static void buildLatePollyPipeline(FunctionPassManager &PM,
llvm::OptimizationLevel Level) {
llvm::OptimizationLevel Level,
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
bool EnableForOpt =
shouldEnablePollyForOptimization() && Level.isOptimizingForSpeed();
if (!shouldEnablePollyForDiagnostic() && !EnableForOpt)
@@ -518,7 +525,7 @@ static void buildLatePollyPipeline(FunctionPassManager &PM,
"not supported with NPM",
false);
buildCommonPollyPipeline(PM, Level, EnableForOpt);
buildCommonPollyPipeline(PM, Level, std::move(FS), EnableForOpt);
if (DumpAfter)
PM.addPass(DumpFunctionPass("-after"));
@@ -542,7 +549,8 @@ static llvm::Expected<std::monostate> parseNoOptions(StringRef Params) {
static llvm::Expected<bool>
parseCGPipeline(StringRef Name, llvm::CGSCCPassManager &CGPM,
PassInstrumentationCallbacks *PIC,
ArrayRef<PassBuilder::PipelineElement> Pipeline) {
ArrayRef<PassBuilder::PipelineElement> Pipeline,
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
#define CGSCC_PASS(NAME, CREATE_PASS, PARSER) \
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
auto Params = PassBuilder::parsePassParameters(PARSER, Name, NAME); \
@@ -624,6 +632,7 @@ parseModulePipeline(StringRef Name, llvm::ModulePassManager &MPM,
/// handle LICMed code to make it useful.
void registerPollyPasses(PassBuilder &PB) {
PassInstrumentationCallbacks *PIC = PB.getPassInstrumentationCallbacks();
IntrusiveRefCntPtr<vfs::FileSystem> FS = PB.getVirtualFileSystemPtr();
#define MODULE_PASS(NAME, CREATE_PASS, PARSER) \
{ \
@@ -652,10 +661,10 @@ void registerPollyPasses(PassBuilder &PB) {
return Err(parseFunctionPipeline(Name, FPM, PIC, Pipeline));
});
PB.registerPipelineParsingCallback(
[PIC](StringRef Name, CGSCCPassManager &CGPM,
ArrayRef<PassBuilder::PipelineElement> Pipeline) -> bool {
[PIC, FS](StringRef Name, CGSCCPassManager &CGPM,
ArrayRef<PassBuilder::PipelineElement> Pipeline) -> bool {
ExitOnError Err("Unable to parse Polly call graph pass: ");
return Err(parseCGPipeline(Name, CGPM, PIC, Pipeline));
return Err(parseCGPipeline(Name, CGPM, PIC, Pipeline, FS));
});
PB.registerPipelineParsingCallback(
[PIC](StringRef Name, ModulePassManager &MPM,
@@ -666,10 +675,16 @@ void registerPollyPasses(PassBuilder &PB) {
switch (PassPosition) {
case POSITION_EARLY:
PB.registerPipelineStartEPCallback(buildEarlyPollyPipeline);
PB.registerPipelineStartEPCallback(
[FS](ModulePassManager &MPM, OptimizationLevel Level) {
buildEarlyPollyPipeline(MPM, Level, FS);
});
break;
case POSITION_BEFORE_VECTORIZER:
PB.registerVectorizerStartEPCallback(buildLatePollyPipeline);
PB.registerVectorizerStartEPCallback(
[FS](FunctionPassManager &FPM, OptimizationLevel Level) {
buildLatePollyPipeline(FPM, Level, FS);
});
break;
}
}

View File

@@ -34,7 +34,9 @@ namespace {
/// Inliner implementation that works with both, LPM (using SCC_t=CallGraph) and
/// NPM (using SCC_t=LazyCallGraph::SCC)
template <typename SCC_t> bool runScopInlinerImpl(Function *F, SCC_t &SCC) {
template <typename SCC_t>
bool runScopInlinerImpl(Function *F, SCC_t &SCC,
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
// We do not try to inline non-trivial SCCs because this would lead to
// "infinite" inlining if we are not careful.
if (SCC.size() > 1)
@@ -50,7 +52,11 @@ template <typename SCC_t> bool runScopInlinerImpl(Function *F, SCC_t &SCC) {
return false;
}
PassBuilder PB;
PassBuilder PB(
/*TM=*/nullptr,
/*PipelineTuningOptions=*/{},
/*PGOOpt=*/{},
/*PIC=*/nullptr, std::move(FS));
// Populate analysis managers and register Polly-specific analyses.
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
@@ -96,7 +102,8 @@ template <typename SCC_t> bool runScopInlinerImpl(Function *F, SCC_t &SCC) {
}
} // namespace
polly::ScopInlinerPass::ScopInlinerPass() {
polly::ScopInlinerPass::ScopInlinerPass(IntrusiveRefCntPtr<vfs::FileSystem> FS)
: FS(std::move(FS)) {
if (!polly::PollyAllowFullFunction) {
report_fatal_error(
"Aborting from ScopInliner because it only makes sense to run with "
@@ -113,6 +120,6 @@ PreservedAnalyses polly::ScopInlinerPass::run(llvm::LazyCallGraph::SCC &SCC,
llvm::LazyCallGraph &CG,
llvm::CGSCCUpdateResult &UR) {
Function *F = &SCC.begin()->getFunction();
bool Changed = runScopInlinerImpl(F, SCC);
bool Changed = runScopInlinerImpl(F, SCC, FS);
return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
}

1
polly/test/.clang-format Normal file
View File

@@ -0,0 +1 @@
ColumnLimit: 0

View File

@@ -3,6 +3,7 @@ set(LLVM_SHLIBEXT "${CMAKE_SHARED_MODULE_SUFFIX}")
add_custom_target(check-polly)
set_target_properties(check-polly PROPERTIES FOLDER "Polly/Meta")
set(POLLY_TEST_C_COMPILER "")
if(NOT LLVM_MAIN_SRC_DIR)
find_program(LLVM_OPT NAMES opt HINTS ${LLVM_TOOLS_BINARY_DIR})
find_program(LLVM_FILECHECK NAMES FileCheck HINTS ${LLVM_TOOLS_BINARY_DIR})
@@ -35,6 +36,11 @@ else ()
set(LLVM_NOT "${LLVM_TOOLS_BINARY_DIR}/not")
set(POLLY_TEST_EXTRA_PATHS "")
set(POLLY_TEST_DEPS llvm-config opt LLVMPolly FileCheck not count)
if (TARGET clang)
set(POLLY_TEST_C_COMPILER "${LLVM_TOOLS_BINARY_DIR}/clang${CMAKE_EXECUTABLE_SUFFIX}")
list(APPEND POLLY_TEST_DEPS clang)
endif ()
endif()
if (POLLY_BUNDLED_ISL)

View File

@@ -1,23 +0,0 @@
#define N 10
void foo(float A[restrict], double B[restrict], char C[restrict],
int D[restrict], long E[restrict]) {
for (long i = 0; i < N; i++)
A[i] += B[i] + C[i] + D[i] + E[i];
}
int main() {
float A[N];
double B[N];
char C[N];
int D[N];
long E[N];
for (long i = 0; i < N; i++) {
__sync_synchronize();
A[i] = B[i] = C[i] = D[i] = E[i] = 42;
}
foo(A, B, C, D, E);
return A[8];
}

View File

@@ -4,11 +4,13 @@ import os
import platform
import re
import subprocess
import shlex
import lit.formats
import lit.util
from lit.llvm import llvm_config
from lit.llvm.subst import ToolSubst
# Configuration file for the 'lit' test runner.
@@ -55,6 +57,25 @@ llvm_config.use_default_substitutions()
tool_patterns = ['opt', 'polly-isl-test']
llvm_config.add_tool_substitutions(tool_patterns)
if config.polly_test_c_compiler:
config.suffixes += ['.c']
llvm_polly_link_into_tools = lit.util.pythonize_bool(config.llvm_polly_link_into_tools)
clang_args = []
if not llvm_polly_link_into_tools:
clang_args += [f'-fpass-plugin={config.polly_lib_dir}/LLVMPolly{config.llvm_shlibext}']
llvm_config.add_tool_substitutions([
ToolSubst("%clang",
command = config.polly_test_c_compiler,
extra_args = clang_args,
unresolved = "fatal"
)
])
lit_config.note(f"Using Clang: {config.polly_test_c_compiler} {shlex.join(clang_args)}")
else:
lit_config.note("Clang tests disabled: No compatible Clang found")
# opt knows whether it is compiled with -DNDEBUG.
import subprocess
try:

View File

@@ -4,12 +4,14 @@ config.llvm_src_root = "@LLVM_SOURCE_DIR@"
config.llvm_obj_root = "@LLVM_BINARY_DIR@"
config.llvm_tools_dir = lit_config.substitute("@LLVM_TOOLS_DIR@")
config.llvm_libs_dir = lit_config.substitute("@LLVM_LIBS_DIR@")
config.llvm_shlibext = "@LLVM_SHLIBEXT@"
config.polly_obj_root = "@POLLY_BINARY_DIR@"
config.polly_lib_dir = "@POLLY_LIB_DIR@"
config.target_triple = "@LLVM_TARGET_TRIPLE@"
config.llvm_polly_link_into_tools = "@LLVM_POLLY_LINK_INTO_TOOLS@"
config.targets_to_build = "@TARGETS_TO_BUILD@"
config.extra_paths = "@POLLY_TEST_EXTRA_PATHS@".split(";")
config.polly_test_c_compiler = "@POLLY_TEST_C_COMPILER@"
## Check the current platform with regex
import re

9
polly/test/polly.c Normal file
View File

@@ -0,0 +1,9 @@
// Sanity test for Polly in Clang
// RUN: %clang %s -O2 -c -mllvm -polly -mllvm -polly-process-unprofitable -mllvm -print-pipeline-passes -o %t.o | FileCheck %s
// CHECK: ,polly,
void foo(int *A, int *B, int n) {
for (int i = 0; i < n; ++i)
A[i] += B[i];
}