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

Fix and test deduplicated header units #2516

Merged
merged 19 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
01bd46c
print_failures.py: Use `with`.
StephanTLavavej Jan 29, 2022
1abeb69
env.lst: Add /DTEST_TOPO_SORT.
StephanTLavavej Feb 2, 2022
dd62d35
test.cpp: Work around VSO-1471374 (fatal error C1116).
StephanTLavavej Feb 3, 2022
ea94d7b
test.cpp: Work around VSO-1471382 (error C2672).
StephanTLavavej Feb 3, 2022
b0cbbae
custom_format.py: Use formatted string literals.
StephanTLavavej Feb 2, 2022
4280557
custom_format.py: Separate exportHeaderOptions and stlHeaders.
StephanTLavavej Feb 2, 2022
fc420a7
custom_format.py: Extract objFilenames from headerUnitOptions.
StephanTLavavej Feb 2, 2022
a68e2b1
custom_format.py: Simplify headerUnitOptions appending.
StephanTLavavej Feb 2, 2022
737e1b6
custom_format.py: We don't need absolute paths for object files.
StephanTLavavej Feb 2, 2022
5fe58e8
custom_format.py: Add stl_header_units.lib.
StephanTLavavej Feb 2, 2022
a425bfc
custom_format.py: Extract getImportableCxxLibraryHeaders().
StephanTLavavej Feb 2, 2022
fc29126
custom_format.py: Rename to consumeBuiltHeaderUnits, hdr.
StephanTLavavej Feb 3, 2022
d1ff9f2
custom_format.py: Simplify compileTestCppWithEdg.
StephanTLavavej Feb 3, 2022
b25c5d5
custom_format.py: Implement topo sort.
StephanTLavavej Feb 3, 2022
8630f32
header-units.json: Fix use_ansi.h by commenting out.
StephanTLavavej Feb 3, 2022
1025c7f
Merge branch 'main' into topo_sort
StephanTLavavej Feb 10, 2022
09a7d08
Sort cat before catapult.
StephanTLavavej Feb 8, 2022
d946b4f
tests/utils/stl/util.py: Replace CRLFs with LFs.
StephanTLavavej Feb 4, 2022
bd5ad55
__msvc_int128.hpp: Remove unnecessary forward declarations.
StephanTLavavej Feb 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stl/inc/header-units.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"typeinfo",
"unordered_map",
"unordered_set",
"use_ansi.h",
Copy link
Member

Choose a reason for hiding this comment

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

This is pre-existing, but it might be better to skip these in the python code instead of commenting them out here, as comments are not valid in json (but are in json5, we could just say this is json5 and parsers need to support comments.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fine for these early tests, but I really don't love the custom json comment handling.

Yeah, it's definitely a hack and I wish there were a built-in way to ignore comments.

This is pre-existing, but it might be better to skip these in the python code instead of commenting them out here

The Python code could maintain a separate skip list. However, (with the exception of version, yvals.h, and yvals_core.h) the headers that are commented out in header-units.json need to be skipped by all users, not just the test harness, because those headers are variously incompatible with being treated as header units.

as comments are not valid in json (but are in json5, we could just say this is json5 and parsers need to support comments.

By agreement with the compiler and build system teams, the format of header-units.json is the unofficial "JSON with comments" extension (I am not familiar with json5 but if it's valid in that format, that's good too - I assume that json5 is a superset so it will continue to remain valid).

Of course we could simply omit the ineligible headers, but then it would be difficult to see why they were missing (without a separate file explaining so), which is why I asked for comments to be allowed.

// "use_ansi.h", // internal header, incompatible with being a separate header unit
"utility",
"valarray",
"variant",
Expand Down
294 changes: 193 additions & 101 deletions tests/std/tests/P1502R1_standard_library_header_units/custom_format.py
Original file line number Diff line number Diff line change
@@ -1,129 +1,221 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

import json
import os
import re

from stl.test.format import STLTestFormat, TestStep
from stl.test.tests import TestType


# Print noisy progress messages that are useful when working on this script.
noisyProgress = False


CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
# P1502R1_standard_library_header_units/test.cpp cites the definition of "importable C++ library headers".
def getImportableCxxLibraryHeaders():
return [
'algorithm',
'any',
'array',
'atomic',
'barrier',
'bit',
'bitset',
'charconv',
'chrono',
'codecvt',
'compare',
'complex',
'concepts',
'condition_variable',
'coroutine',
'deque',
'exception',
'execution',
'filesystem',
'format',
'forward_list',
'fstream',
'functional',
'future',
'initializer_list',
'iomanip',
'ios',
'iosfwd',
'iostream',
'istream',
'iterator',
'latch',
'limits',
'list',
'locale',
'map',
'memory_resource',
'memory',
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
'mutex',
'new',
'numbers',
'numeric',
'optional',
'ostream',
'queue',
'random',
'ranges',
'ratio',
'regex',
'scoped_allocator',
'semaphore',
'set',
'shared_mutex',
'source_location',
'span',
'spanstream',
'sstream',
'stack',
'stdexcept',
'stop_token',
'streambuf',
'string_view',
'string',
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
'strstream',
'syncstream',
'system_error',
'thread',
'tuple',
'type_traits',
'typeindex',
'typeinfo',
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
'unordered_map',
'unordered_set',
'utility',
'valarray',
'variant',
'vector',
'version',
]


def loadJsonWithComments(filename):
# This is far from a general-purpose solution (it doesn't attempt to handle block comments like /**/
# and comments appearing within strings like "cats // dogs"), but it's sufficient for header-units.json.
comment = re.compile('//.*')
with open(filename) as file:
replacedLines = [re.sub(comment, '', line) for line in file]
return json.loads(''.join(replacedLines))


def getAllHeaders(headerUnitsJsonFilename):
buildAsHeaderUnits = loadJsonWithComments(headerUnitsJsonFilename)['BuildAsHeaderUnits']

# We want to build everything that's mentioned in header-units.json, plus all of the
# headers that were commented out for providing macros that control header inclusion.
return sorted(set(buildAsHeaderUnits + ['version', 'yvals.h', 'yvals_core.h']))
Copy link
Member

Choose a reason for hiding this comment

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

we're already doing custom per-header stuff here, so again, we could keep the list of stuff to skip here (or in a separate json dict key) instead of using comments.



class CustomTestFormat(STLTestFormat):
def getBuildSteps(self, test, litConfig, shared):
stlHeaders = [
'algorithm',
'any',
'array',
'atomic',
'barrier',
'bit',
'bitset',
'charconv',
'chrono',
'codecvt',
'compare',
'complex',
'concepts',
'condition_variable',
'coroutine',
'deque',
'exception',
'execution',
'filesystem',
'format',
'forward_list',
'fstream',
'functional',
'future',
'initializer_list',
'iomanip',
'ios',
'iosfwd',
'iostream',
'istream',
'iterator',
'latch',
'limits',
'list',
'locale',
'map',
'memory_resource',
'memory',
'mutex',
'new',
'numbers',
'numeric',
'optional',
'ostream',
'queue',
'random',
'ranges',
'ratio',
'regex',
'scoped_allocator',
'semaphore',
'set',
'shared_mutex',
'source_location',
'span',
'spanstream',
'sstream',
'stack',
'stdexcept',
'stop_token',
'streambuf',
'string_view',
'string',
'strstream',
'syncstream',
'system_error',
'thread',
'tuple',
'type_traits',
'typeindex',
'typeinfo',
'unordered_map',
'unordered_set',
'utility',
'valarray',
'variant',
'vector',
'version',
]

outputDir, outputBase = test.getTempPaths()
sourcePath = test.getSourcePath()

compileTestCppWithEdg = False
if '/BE' in test.flags:
compileTestCppWithEdg = True
test.flags.remove('/BE')

if '/BE' in test.compileFlags:
compileTestCppWithEdg = True
compileTestCppWithEdg = '/BE' in test.compileFlags
if compileTestCppWithEdg:
test.compileFlags.remove('/BE')

exportHeaderOptions = ['/exportHeader', '/headerName:angle', '/Fo', '/MP']
headerUnitOptions = []
for header in stlHeaders:
exportHeaderOptions.append(header)
# This is a list of compiler options to consume the header units that we've built so far.
consumeBuiltHeaderUnits = []

# Output files:
objFilenames = []

if '/DTEST_TOPO_SORT' in test.compileFlags: # Build deduplicated header units:
# Compiler options, common to both scanning dependencies and building header units.
clOptions = ['/exportHeader', '/headerName:angle', '/translateInclude', '/Fo', '/MP']

# Store the list of headers to build.
allHeaders = getAllHeaders(os.path.join(litConfig.cxx_headers, 'header-units.json'))

# Generate JSON files that record how these headers depend on one another.
if noisyProgress:
print('Scanning dependencies...')
cmd = [test.cxx, *test.flags, *test.compileFlags, *clOptions, '/scanDependencies', '.\\', *allHeaders]
yield TestStep(cmd, shared.execDir, shared.env, False)

# The JSON files also record what object files will be produced.
# IFC filenames and OBJ filenames follow different patterns. For example:
# <filesystem> produces filesystem.ifc and filesystem.obj
# <xbit_ops.h> produces xbit_ops.h.ifc and xbit_ops.obj
# We can easily synthesize IFC filenames, but it's easier to get the OBJ filenames from the JSON files.

# This dictionary powers the topological sort.
# Key: Header name, e.g. 'bitset'.
# Value: List of dependencies that remain to be built, e.g. ['iosfwd', 'limits', 'xstring'].
remainingDependencies = {}

# Read the JSON files, storing the results in objFilenames and remainingDependencies.
for hdr in allHeaders:
with open(os.path.join(outputDir, f'{hdr}.module.json')) as file:
jsonObject = json.load(file)
objFilenames.append(jsonObject['rules'][0]['primary-output'])
# TRANSITION, VSO-1466711 fixed in VS 2022 17.2 Preview 2
# os.path.basename(req['source-path']) should be req['logical-name']
dep = [os.path.basename(req['source-path']) for req in jsonObject['rules'][0]['requires']]
remainingDependencies[hdr] = dep

# Build header units in topologically sorted order.
while len(remainingDependencies) > 0:
# When a header has no remaining dependencies, it is ready to be built.
readyToBuild = [hdr for hdr, dep in remainingDependencies.items() if len(dep) == 0]

# Each layer can be built in parallel.
if noisyProgress:
print('Building deduplicated header units:', ' '.join(readyToBuild))
cmd = [test.cxx, *test.flags, *test.compileFlags, *clOptions, *consumeBuiltHeaderUnits, *readyToBuild]
yield TestStep(cmd, shared.execDir, shared.env, False)

# Update remainingDependencies by doing two things.
# hdr, dep is the current key-value pair.
# First, keep `if len(dep) > 0`. (Otherwise, we just built hdr.)
# Second, filter dep, eliminating anything that appears in readyToBuild. (If we're left with
# an empty list, then hdr will be ready to build in the next iteration.)
remainingDependencies = {
hdr: [d for d in dep if d not in readyToBuild]
for hdr, dep in remainingDependencies.items() if len(dep) > 0
}

headerUnitOptions.append('/headerUnit:angle')
headerUnitOptions.append('{0}={0}.ifc'.format(header))
# Add compiler options to consume the header units that we just built.
for hdr in readyToBuild:
consumeBuiltHeaderUnits += ['/headerUnit:angle', f'{hdr}={hdr}.ifc']
else: # Build independent header units:
stlHeaders = getImportableCxxLibraryHeaders()
exportHeaderOptions = ['/exportHeader', '/headerName:angle', '/Fo', '/MP']
for hdr in stlHeaders:
consumeBuiltHeaderUnits += ['/headerUnit:angle', f'{hdr}={hdr}.ifc']
objFilenames.append(f'{hdr}.obj')

if not compileTestCppWithEdg:
headerUnitOptions.append(os.path.join(outputDir, header + '.obj'))
if noisyProgress:
print('Building independent header units...')
cmd = [test.cxx, *test.flags, *test.compileFlags, *exportHeaderOptions, *stlHeaders]
yield TestStep(cmd, shared.execDir, shared.env, False)

cmd = [test.cxx, *test.flags, *test.compileFlags, *exportHeaderOptions]
# For convenience, create a library file containing all of the object files that were produced.
libFilename = 'stl_header_units.lib'
if noisyProgress:
print('Creating library...')
cmd = ['lib.exe', '/nologo', f'/out:{libFilename}', *objFilenames]
yield TestStep(cmd, shared.execDir, shared.env, False)

if compileTestCppWithEdg:
test.compileFlags.append('/BE')

if TestType.COMPILE in test.testType:
cmd = [test.cxx, '/c', sourcePath, *test.flags, *test.compileFlags, *headerUnitOptions]
cmd = [test.cxx, '/c', sourcePath, *test.flags, *test.compileFlags, *consumeBuiltHeaderUnits]
elif TestType.RUN in test.testType:
shared.execFile = outputBase + '.exe'
cmd = [test.cxx, sourcePath, *test.flags, *test.compileFlags, *headerUnitOptions, '/Fe' + shared.execFile,
'/link', *test.linkFlags]
shared.execFile = f'{outputBase}.exe'
cmd = [test.cxx, sourcePath, *test.flags, *test.compileFlags, *consumeBuiltHeaderUnits, libFilename,
f'/Fe{shared.execFile}', '/link', *test.linkFlags]

if noisyProgress:
print('Compiling and running test...')
yield TestStep(cmd, shared.execDir, shared.env, False)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ PM_CL="/MD"
PM_CL="/MDd"
PM_CL="/MT"
PM_CL="/MTd"
RUNALL_CROSSLIST
PM_CL="/DTEST_TOPO_SORT"
PM_CL=""
# RUNALL_CROSSLIST
# PM_CL=""
# PM_CL="/analyze:only /analyze:autolog-" # TRANSITION, works correctly but slowly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,13 @@ int main() {
assert(!ep);
}

#ifndef TEST_TOPO_SORT // TRANSITION, VSO-1471382 (error C2672: 'count_if': no matching overloaded function found)
{
puts("Testing <execution>.");
constexpr int arr[]{11, 0, 22, 0, 33, 0, 44, 0, 55};
assert(count(execution::par, begin(arr), end(arr), 0) == 4);
}
#endif // ^^^ no workaround ^^^

{
puts("Testing <filesystem>.");
Expand Down Expand Up @@ -319,6 +321,7 @@ int main() {
assert(!f.is_open());
}

#ifndef TEST_TOPO_SORT // TRANSITION, VSO-1471374 (fatal error C1116: unrecoverable error importing module)
{
puts("Testing <functional>.");
function<int(int, int)> f{multiplies{}};
Expand All @@ -329,6 +332,7 @@ int main() {
assert(b(3) == 33);
static_assert(b(3) == 33);
}
#endif // ^^^ no workaround ^^^

{
puts("Testing <future>.");
Expand Down Expand Up @@ -861,6 +865,7 @@ int main() {
assert(this_thread::get_id() != thread::id{});
}

#ifndef TEST_TOPO_SORT // TRANSITION, VSO-1471374 (fatal error C1116: unrecoverable error importing module)
{
puts("Testing <tuple>.");
constexpr tuple<int, char, double> t{1729, 'c', 1.25};
Expand All @@ -871,6 +876,7 @@ int main() {
static_assert(get<char>(t) == 'c');
static_assert(get<double>(t) == 1.25);
}
#endif // ^^^ no workaround ^^^

{
puts("Testing <type_traits>.");
Expand Down
Loading