Skip to content

Commit 665444c

Browse files
committed
[Downstream change][Multilib] Extend the Multilib system to support an IncludeDirs field. (arm#447)
Downstream issue:arm#446. This patch extends the Multilib yaml format to allow specifying an IncludeDirs for the header path/s per multilib variant. The goal is to enable fine-grained control over header search paths for each multilib variant. This feature is especially useful in setups where header paths deviate from the default bare-metal assumptions. For example, when headers are shared across target triples, it becomes necessary to organise them under target-triple-specific directories to ensure correct resolution. This is already in upstream review llvm/llvm-project#146651. Since the review is pending, a downstream patch is made to enable IncludeDirs support in multilib.yaml [ATFE] Package common multilib headers into target triple directory. (arm#423) The current ATFE package redundantly duplicates identical C and C++ headers across more than 100 ibrary variants, despite the fact that these headers do not vary within a given target triple aside from few. As a result, each variant currently maintains its own include/ and include/c++/v1/ directories, leading to unnecessary duplication. This patch is to refactor the multilib layout to use a single shared header directory per target triple, eliminating redundant copies of identical headers across library variants, while keeping variant-specific headers within each variant’s own include/ directory. Changing the layout of the package structure will require corresponding updates to the ATFE build system, as it currently assumes a variant specific directory hierarchy for headers and libraries during installation and packaging. A new Python script has been introduced that runs after the runtime subproject has been executed for each variant and the non-optimised multilib directories have been generated. This script identifies and extracts common headers from the non-optimised multilibs and creates an optimized multilib directory. In this optimised directory, only the common headers are centralised, while the remaining contents are preserved as-is from the non-optimised layout. A CMake flag called ENABLE_MULTILIB_HEADER_OPTIMISATION has been added to disable this multilib optimisation phase. When this flag is not set, the optimised multilib directory will not be generated. Additionally, the optimisation step is skipped automatically if there are fewer than two multilib variants to build. To support this new layout with a centralised include directory, a new field called IncludeDirs has been added to multilib.yaml. This field specifies a base directory for locating header files which gets added to the header search path sequence by clang. Corresponding changes in Clang: llvm/llvm-project#146651 [ATFE] Prioritize multilib-specific headers over target-level headers. (arm#451) Adjust the header search order so that variant-specific directories (e.g armv6m_soft_nofp_size/include/) are searched before the common target-level directories (e.g include/) in the -internal-isystem list. This ensures that headers tailored for a specific multilib variant take precedence over the generic headers, allowing correct header resolution in the presence of overrides. Previously, the common target-level directory could shadow multilib-specific headers if the same file existed in both paths.
1 parent f276873 commit 665444c

File tree

8 files changed

+342
-15
lines changed

8 files changed

+342
-15
lines changed

arm-software/embedded/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ option(
180180
OFF
181181
)
182182

183+
option(
184+
ENABLE_MULTILIB_HEADER_OPTIMISATION "Enable multilib header optimisation phase"
185+
ON
186+
)
187+
183188
# Previously, the LLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL option was
184189
# called LLVM_TOOLCHAIN_NEWLIB_OVERLAY_INSTALL. Detect a setting of
185190
# that name, in case it's in an existing CMakeCache.txt or command
@@ -646,6 +651,7 @@ if(NOT PREBUILT_TARGET_LIBRARIES)
646651
-DLLVM_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/llvm
647652
-DMULTILIB_JSON=${LLVM_TOOLCHAIN_MULTILIB_JSON}
648653
-DENABLE_VARIANTS=${ENABLE_VARIANTS_PASSTHROUGH}
654+
-DENABLE_MULTILIB_HEADER_OPTIMISATION=${ENABLE_MULTILIB_HEADER_OPTIMISATION}
649655
-DENABLE_PARALLEL_LIB_CONFIG=${ENABLE_PARALLEL_LIB_CONFIG}
650656
-DENABLE_PARALLEL_LIB_BUILD=${ENABLE_PARALLEL_LIB_BUILD}
651657
-DPARALLEL_LIB_BUILD_LEVELS=${PARALLEL_LIB_BUILD_LEVELS}

arm-software/embedded/arm-multilib/CMakeLists.txt

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ set(
4848
fvp/get_fvps.sh"
4949
)
5050
set(FVP_CONFIG_DIR "${TOOLCHAIN_SOURCE_DIR}/fvp/config" CACHE STRING "The directory in which the FVP models are installed.")
51+
option(ENABLE_MULTILIB_HEADER_OPTIMISATION "Enable multilib header optimisation phase" ON)
5152
option(
5253
ENABLE_PARALLEL_LIB_CONFIG
5354
"Run the library variant configuration steps in parallel."
@@ -185,10 +186,12 @@ foreach(lib_idx RANGE ${lib_count_dec})
185186
set(parent_dir_name arm-none-eabi)
186187
endif()
187188
set(destination_directory "${CMAKE_CURRENT_BINARY_DIR}/multilib/${parent_dir_name}/${variant}")
188-
install(
189-
DIRECTORY ${destination_directory}
190-
DESTINATION ${parent_dir_name}
191-
)
189+
if(NOT ENABLE_MULTILIB_HEADER_OPTIMISATION)
190+
install(
191+
DIRECTORY ${destination_directory}
192+
DESTINATION ${parent_dir_name}
193+
)
194+
endif()
192195
set(variant_json_file ${CMAKE_CURRENT_SOURCE_DIR}/json/variants/${variant_json})
193196

194197
# Read info from the variant specific json.
@@ -251,6 +254,8 @@ foreach(lib_idx RANGE ${lib_count_dec})
251254
TEST_EXCLUDE_FROM_MAIN TRUE
252255
)
253256

257+
list(APPEND all_runtime_targets runtimes-${variant})
258+
254259
if(ENABLE_PARALLEL_LIB_CONFIG OR ENABLE_PARALLEL_LIB_BUILD)
255260
# Create additional steps to configure/build the subprojects.
256261
# These are collected to be run together, so that all the
@@ -337,6 +342,11 @@ foreach(lib_idx RANGE ${lib_count_dec})
337342
foreach(flag ${multilib_flags_list})
338343
string(APPEND multilib_yaml_content " - ${flag}\n")
339344
endforeach()
345+
if(ENABLE_MULTILIB_HEADER_OPTIMISATION)
346+
string(APPEND multilib_yaml_content " IncludeDirs:\n")
347+
string(APPEND multilib_yaml_content " - ${parent_dir_name}/${variant}/include\n")
348+
string(APPEND multilib_yaml_content " - ${parent_dir_name}/include\n")
349+
endif()
340350
string(APPEND multilib_yaml_content " Group: stdlibs\n")
341351
else()
342352
# In place of a json, an error message is expected.
@@ -386,7 +396,25 @@ add_custom_command(
386396
)
387397

388398
add_custom_target(multilib-yaml ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/multilib/multilib.yaml)
389-
install(
390-
FILES ${CMAKE_CURRENT_BINARY_DIR}/multilib/multilib.yaml
391-
DESTINATION .
392-
)
399+
if(NOT ENABLE_MULTILIB_HEADER_OPTIMISATION)
400+
install(
401+
FILES ${CMAKE_CURRENT_BINARY_DIR}/multilib/multilib.yaml
402+
DESTINATION .
403+
)
404+
endif()
405+
406+
# Extract common header files which spans across multilib variant directories.
407+
if(ENABLE_MULTILIB_HEADER_OPTIMISATION)
408+
add_custom_target(generate-multilib-common-headers ALL
409+
COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/common-headers-generate.py"
410+
"${CMAKE_CURRENT_BINARY_DIR}/multilib"
411+
"${CMAKE_CURRENT_BINARY_DIR}/multilib-optimised"
412+
COMMENT "Generating common headers"
413+
DEPENDS ${all_runtime_targets} multilib-yaml
414+
)
415+
416+
install(
417+
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/multilib-optimised/"
418+
DESTINATION .
419+
)
420+
endif()
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Identifies and extracts header files that are common across multiple multilib variant directories.
5+
6+
This script scans all variant folders within a multilib target directory
7+
(e.g., `arm-none-eabi/variant1/include`, `arm-none-eabi/variant2/include`, etc.) and compares all
8+
`.h` files. If the same file (by name and content) appears in multiple variants, it will be moved
9+
to a shared include directory at:
10+
11+
<CMAKE_BINARY_DIR>/multilib-optimised/<target>/include/
12+
13+
for the following multilib targets:
14+
- arm-none-eabi
15+
- aarch64-none-elf
16+
17+
Arguments:
18+
<CMAKE_BINARY_DIR>/multilib Path to the CMake build directory containing non optmised multilib.
19+
eg: build/multilib-builds/multilib/picolibc-build/multilib
20+
<CMAKE_BINARY_DIR>/multilib-optimised Path to the CMake build directory where optimised multilib should be generated..
21+
eg: build/multilib-builds/multilib/picolibc-build/multilib-optimised
22+
23+
This is useful to reduce duplication in the toolchain by centralising common headers
24+
that are shared across architecture variants.
25+
"""
26+
import argparse
27+
import os
28+
import filecmp
29+
import shutil
30+
31+
# Define the multilib target dirs which want to process
32+
MULTILIB_TARGET_DIRS = ["arm-none-eabi", "aarch64-none-elf"]
33+
34+
35+
def files_are_identical(f1, f2):
36+
return filecmp.cmp(f1, f2, shallow=False)
37+
38+
39+
def collect_variant_include_paths(input_target_dir):
40+
"""
41+
Extracts each multilib variant and its corresponding include path from the non-optimised multilib directory.
42+
Stores the results to enable later comparison of header contents across different non-optimised multilib variant
43+
include paths.
44+
45+
"""
46+
variant_include_path = {}
47+
for variant in os.listdir(input_target_dir):
48+
include_path = os.path.join(input_target_dir, variant, "include")
49+
if os.path.isdir(include_path):
50+
variant_include_path[variant] = include_path
51+
return variant_include_path
52+
53+
54+
def extract_common_headers_for_targets(args):
55+
if os.path.exists(args.multilib_optimised_dir):
56+
shutil.rmtree(args.multilib_optimised_dir)
57+
58+
for target in MULTILIB_TARGET_DIRS:
59+
input_target_dir = os.path.join(
60+
os.path.abspath(args.multilib_non_optimised_dir), target
61+
)
62+
output_target_dir = os.path.join(
63+
os.path.abspath(args.multilib_optimised_dir), target
64+
)
65+
output_include_dir = os.path.join(output_target_dir, "include")
66+
67+
if not os.path.isdir(input_target_dir):
68+
print(
69+
f"Skipping extracting the common headers for {target}: input path {input_target_dir} not found"
70+
)
71+
continue
72+
73+
variant_includes = collect_variant_include_paths(input_target_dir)
74+
if len(variant_includes) < 2:
75+
print(
76+
f"Skipping extracting the common headers for {target}: not enough variants to compare.At least two variants must be enabled for the multilib header optimisation phase to proceed."
77+
)
78+
# The script always creates the multilib-optimised folder, even when there's only one variant and no
79+
# optimization is applied. In that case, multilib-optimised will just contain a copy of the
80+
# single variant from the non-optimised multilib directory.
81+
if os.path.exists(args.multilib_non_optimised_dir):
82+
shutil.copytree(args.multilib_non_optimised_dir, args.multilib_optimised_dir)
83+
return
84+
85+
# Creating the common include headers for each target
86+
os.makedirs(output_include_dir, exist_ok=True)
87+
88+
# Step 1: compare first two variants and extract the common headers into the targets common include directory
89+
base_dir = list(variant_includes.values())[0]
90+
compare_dir = list(variant_includes.values())[1]
91+
for root, sub_dirs, header_files in os.walk(base_dir):
92+
sub_dir_root = os.path.relpath(root, base_dir)
93+
for header in header_files:
94+
h1 = os.path.join(base_dir, sub_dir_root, header)
95+
h2 = os.path.join(compare_dir, sub_dir_root, header)
96+
if os.path.exists(h2) and files_are_identical(h1, h2):
97+
out_dir = os.path.join(output_include_dir, sub_dir_root)
98+
os.makedirs(out_dir, exist_ok=True)
99+
shutil.copy2(h1, os.path.join(out_dir, header))
100+
101+
# Step 2: Compare all the variants with the new common include. Any headers that do not match
102+
# and do not exit in common include should retain in their respective variant specific directories.
103+
for variant, include_path in variant_includes.items():
104+
for root, sub_dirs, header_files in os.walk(include_path):
105+
sub_dir_root = os.path.relpath(root, include_path)
106+
for header in header_files:
107+
variant_header = os.path.join(include_path, sub_dir_root, header)
108+
common_header = os.path.join(
109+
output_include_dir, sub_dir_root, header
110+
)
111+
if not os.path.exists(common_header) or not files_are_identical(
112+
variant_header, common_header
113+
):
114+
out_dir = os.path.join(
115+
os.path.abspath(args.multilib_optimised_dir),
116+
target,
117+
variant,
118+
sub_dir_root,
119+
"include",
120+
)
121+
os.makedirs(out_dir, exist_ok=True)
122+
shutil.copy2(variant_header, os.path.join(out_dir, header))
123+
124+
# Step3: For each variant, the lib and share directories should be copied from the non-optimised multilib
125+
# directory as it is.
126+
for variant in variant_includes:
127+
remaining_dirs = ["lib", "share"]
128+
for folder in remaining_dirs:
129+
src_dir = os.path.join(input_target_dir, variant, folder)
130+
dst_dir = os.path.join(output_target_dir, variant, folder)
131+
if os.path.exists(src_dir):
132+
# If destination exists, remove it first
133+
if os.path.exists(dst_dir):
134+
shutil.rmtree(dst_dir)
135+
os.makedirs(os.path.dirname(dst_dir), exist_ok=True)
136+
shutil.copytree(src_dir, dst_dir)
137+
else:
138+
print(f"Warning: {src_dir} does not exist and will be skipped.")
139+
140+
# Step4: Copy multilib.yaml file as it is from the non-optimised multilib directoy.
141+
src_yaml = os.path.join(args.multilib_non_optimised_dir, "multilib.yaml")
142+
dst_yaml = os.path.join(args.multilib_optimised_dir, "multilib.yaml")
143+
if os.path.exists(src_yaml):
144+
shutil.copy2(src_yaml, dst_yaml)
145+
else:
146+
raise FileNotFoundError(f"Source yaml '{src_yaml}' does not exist.")
147+
148+
149+
def main():
150+
parser = argparse.ArgumentParser(
151+
description=__doc__,
152+
formatter_class=argparse.RawDescriptionHelpFormatter,
153+
)
154+
parser.add_argument(
155+
"multilib_non_optimised_dir",
156+
help="CMake binary directory containing the non-optimised multilib headers",
157+
)
158+
parser.add_argument(
159+
"multilib_optimised_dir",
160+
help="CMake binary directory where the optimised multilib headers should be generated",
161+
)
162+
args = parser.parse_args()
163+
164+
extract_common_headers_for_targets(args)
165+
166+
167+
if __name__ == "__main__":
168+
main()

clang/include/clang/Driver/Multilib.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ class Driver;
3535
class Multilib {
3636
public:
3737
using flags_list = std::vector<std::string>;
38+
// Downstream issue: #446 (Extend the Multilib system to support an
39+
// IncludeDirs field)
40+
using includedirs_list = std::vector<std::string>;
3841

3942
private:
4043
std::string GCCSuffix;
4144
std::string OSSuffix;
4245
std::string IncludeSuffix;
4346
flags_list Flags;
47+
// Downstream issue: #446 (Extend the Multilib system to support an
48+
// IncludeDirs field)
49+
includedirs_list IncludeDirs;
4450

4551
// Optionally, a multilib can be assigned a string tag indicating that it's
4652
// part of a group of mutually exclusive possibilities. If two or more
@@ -62,6 +68,9 @@ class Multilib {
6268
/// This is enforced with an assert in the constructor.
6369
Multilib(StringRef GCCSuffix = {}, StringRef OSSuffix = {},
6470
StringRef IncludeSuffix = {}, const flags_list &Flags = flags_list(),
71+
// Downstream issue: #446 (Extend the Multilib system to support an
72+
// IncludeDirs field)
73+
const includedirs_list &IncludeDirs = includedirs_list(),
6574
StringRef ExclusiveGroup = {},
6675
std::optional<StringRef> Error = std::nullopt);
6776

@@ -81,6 +90,12 @@ class Multilib {
8190
/// All elements begin with either '-' or '!'
8291
const flags_list &flags() const { return Flags; }
8392

93+
// Downstream issue: #446 (Extend the Multilib system to support an
94+
// IncludeDirs field)
95+
/// Get the include directories specified in multilib.yaml under the
96+
/// 'IncludeDirs' field
97+
const includedirs_list &includeDirs() const { return IncludeDirs; }
98+
8499
/// Get the exclusive group label.
85100
const std::string &exclusiveGroup() const { return ExclusiveGroup; }
86101

clang/lib/Driver/Multilib.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ using namespace llvm::sys;
2929

3030
Multilib::Multilib(StringRef GCCSuffix, StringRef OSSuffix,
3131
StringRef IncludeSuffix, const flags_list &Flags,
32+
// Downstream issue: #446 (Extend the Multilib system to
33+
// support an IncludeDirs field)
34+
const includedirs_list &IncludeDirs,
3235
StringRef ExclusiveGroup, std::optional<StringRef> Error)
3336
: GCCSuffix(GCCSuffix), OSSuffix(OSSuffix), IncludeSuffix(IncludeSuffix),
34-
Flags(Flags), ExclusiveGroup(ExclusiveGroup), Error(Error) {
37+
Flags(Flags),
38+
// Downstream issue: #446 (Extend the Multilib system to support an
39+
// IncludeDirs field)
40+
IncludeDirs(IncludeDirs), ExclusiveGroup(ExclusiveGroup), Error(Error) {
3541
assert(GCCSuffix.empty() ||
3642
(StringRef(GCCSuffix).front() == '/' && GCCSuffix.size() > 1));
3743
assert(OSSuffix.empty() ||
@@ -299,6 +305,9 @@ struct MultilibSerialization {
299305
std::string Dir; // if this record successfully selects a library dir
300306
std::string Error; // if this record reports a fatal error message
301307
std::vector<std::string> Flags;
308+
// Downstream issue: #446 (Extend the Multilib system to support an
309+
// IncludeDirs field)
310+
std::vector<std::string> IncludeDirs;
302311
std::string Group;
303312
};
304313

@@ -350,6 +359,9 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
350359
io.mapOptional("Dir", V.Dir);
351360
io.mapOptional("Error", V.Error);
352361
io.mapRequired("Flags", V.Flags);
362+
// Downstream issue: #446 (Extend the Multilib system to support an
363+
// IncludeDirs field)
364+
io.mapOptional("IncludeDirs", V.IncludeDirs);
353365
io.mapOptional("Group", V.Group);
354366
}
355367
static std::string validate(IO &io, MultilibSerialization &V) {
@@ -359,6 +371,12 @@ template <> struct llvm::yaml::MappingTraits<MultilibSerialization> {
359371
return "the 'Dir' and 'Error' keys may not both be specified";
360372
if (StringRef(V.Dir).starts_with("/"))
361373
return "paths must be relative but \"" + V.Dir + "\" starts with \"/\"";
374+
// Downstream issue: #446 (Extend the Multilib system to support an
375+
// IncludeDirs field)
376+
for (const auto &Path : V.IncludeDirs) {
377+
if (StringRef(Path).starts_with("/"))
378+
return "paths must be relative but \"" + Path + "\" starts with \"/\"";
379+
}
362380
return std::string{};
363381
}
364382
};
@@ -489,7 +507,10 @@ MultilibSet::parseYaml(llvm::MemoryBufferRef Input,
489507
Multilibs.reserve(MS.Multilibs.size());
490508
for (const auto &M : MS.Multilibs) {
491509
if (!M.Error.empty()) {
492-
Multilibs.emplace_back("", "", "", M.Flags, M.Group, M.Error);
510+
// Downstream issue: #446 (Extend the Multilib system to support an
511+
// IncludeDirs field)
512+
Multilibs.emplace_back("", "", "", M.Flags, M.IncludeDirs, M.Group,
513+
M.Error);
493514
} else {
494515
std::string Dir;
495516
if (M.Dir != ".")
@@ -498,7 +519,9 @@ MultilibSet::parseYaml(llvm::MemoryBufferRef Input,
498519
// Multilib constructor. If we later support more than one type of group,
499520
// we'll have to look up the group name in MS.Groups, check its type, and
500521
// decide what to do here.
501-
Multilibs.emplace_back(Dir, Dir, Dir, M.Flags, M.Group);
522+
// Downstream issue: #446 (Extend the Multilib system to support an
523+
// IncludeDirs field)
524+
Multilibs.emplace_back(Dir, Dir, Dir, M.Flags, M.IncludeDirs, M.Group);
502525
}
503526
}
504527

0 commit comments

Comments
 (0)